<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cloudflare on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/cloudflare/</link><description>Recent content in Cloudflare on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 22 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/cloudflare/index.xml" rel="self" type="application/rss+xml"/><item><title>Tunnels — remote network access</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/</guid><description>&lt;p&gt;Approaches for accessing a private network (home lab, office) from outside, when you don&amp;rsquo;t have a static public IP and may be behind NAT or CGNAT.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="first-check-if-youre-behind-cgnat"&gt;First: check if you&amp;rsquo;re behind CGNAT
&lt;/h2&gt;&lt;p&gt;Run on a device inside the network:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl ifconfig.me
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compare the result to the WAN IP shown in your router. If they match — you have a real public IP (possibly dynamic). If they differ — you&amp;rsquo;re behind CGNAT, and port forwarding will not work regardless of router config.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="tailscale"&gt;Tailscale
&lt;/h2&gt;&lt;p&gt;WireGuard-based mesh VPN. Each device gets a node in a private overlay network coordinated by Tailscale&amp;rsquo;s control plane. Connections are peer-to-peer where possible; relay servers (DERP) handle cases where direct traversal fails.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -fsSL https://tailscale.com/install.sh | sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tailscale up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Subnet routing&lt;/strong&gt;: one node (e.g. a Pi on the LAN) advertises the local subnet. Other Tailscale nodes route through it — the whole LAN becomes reachable, not just that one node.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tailscale up --advertise-routes&lt;span style="color:#f92672"&gt;=&lt;/span&gt;192.168.1.0/24
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Exit node&lt;/strong&gt;: route all internet traffic through a Tailscale node — useful on untrusted networks.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;Yes (coordination server)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Free tier&lt;/td&gt;
 &lt;td&gt;100 devices&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hostable&lt;/td&gt;
 &lt;td&gt;Yes — &lt;a class="link" href="https://github.com/juanfont/headscale" target="_blank" rel="noopener"
 &gt;Headscale&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The coordination server must be reachable to establish new connections. Existing sessions survive outages. Headscale replaces the coordination server while reusing the same clients.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="cloudflare-tunnel"&gt;Cloudflare Tunnel
&lt;/h2&gt;&lt;p&gt;The device on your private network runs &lt;code&gt;cloudflared&lt;/code&gt;, which holds an outbound connection to Cloudflare&amp;rsquo;s edge. Cloudflare proxies inbound traffic through it. No open ports, no public IP needed.&lt;/p&gt;
&lt;p&gt;Works for SSH, HTTP, and other TCP services. Native SSH support: access via &lt;code&gt;cloudflared access ssh&lt;/code&gt; or a browser terminal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel login
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel create homelab
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel route dns homelab ssh.yourdomain.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel run homelab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;Yes — traffic passes through Cloudflare&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Requires&lt;/td&gt;
 &lt;td&gt;Domain on Cloudflare&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cost&lt;/td&gt;
 &lt;td&gt;Free (Zero Trust free tier)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Traffic passes through Cloudflare&amp;rsquo;s infrastructure. For SSH this means an encrypted session transiting a third party&amp;rsquo;s edge.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="wireguard-self-hosted"&gt;WireGuard (self-hosted)
&lt;/h2&gt;&lt;p&gt;WireGuard server on a node in the private network. Remote clients connect with a config containing the server&amp;rsquo;s public key and endpoint. Encrypted point-to-point, no intermediary.&lt;/p&gt;
&lt;p&gt;Requires a real public IP at the WAN, UDP port forwarding on the router (default 51820), and DDNS if the IP is dynamic.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/wireguard/wg0.conf (server)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[Interface]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Address&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10.0.0.1/24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ListenPort&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;51820&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PrivateKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;server-private-key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[Peer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PublicKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;client-public-key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;AllowedIPs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10.0.0.2/32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;Yes (UDP 51820)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Full ownership, no external dependency, very fast. Key distribution is manual unless you layer a management tool on top (e.g. wg-easy, Netbird).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="port-forwarding--ddns"&gt;Port forwarding + DDNS
&lt;/h2&gt;&lt;p&gt;Forward a port on the router to the target host. Use a DDNS provider (DuckDNS, Cloudflare, etc.) to keep a hostname pointed at the dynamic IP.&lt;/p&gt;
&lt;p&gt;Exposes a service directly to the internet. Minimum hardening for SSH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Key-only auth (&lt;code&gt;PasswordAuthentication no&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Non-standard port&lt;/li&gt;
&lt;li&gt;fail2ban or equivalent&lt;/li&gt;
&lt;li&gt;Regular review of auth logs&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;Yes (TCP)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;DDNS provider only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Low–Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="comparison"&gt;Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Tailscale&lt;/th&gt;
 &lt;th&gt;Cloudflare Tunnel&lt;/th&gt;
 &lt;th&gt;WireGuard&lt;/th&gt;
 &lt;th&gt;Port forward&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CGNAT&lt;/td&gt;
 &lt;td&gt;Works&lt;/td&gt;
 &lt;td&gt;Works&lt;/td&gt;
 &lt;td&gt;Fails&lt;/td&gt;
 &lt;td&gt;Fails&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Traffic via third party&lt;/td&gt;
 &lt;td&gt;Metadata only&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hostable&lt;/td&gt;
 &lt;td&gt;Yes (Headscale)&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Low&lt;/td&gt;
 &lt;td&gt;Low–Med&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;td&gt;Low–Med&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="which-to-use"&gt;Which to use
&lt;/h2&gt;&lt;p&gt;Start with &lt;strong&gt;Tailscale&lt;/strong&gt; if you want the least friction and aren&amp;rsquo;t sure whether you&amp;rsquo;re behind CGNAT. Subnet routing covers the entire LAN through a single node.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; if you have a domain on Cloudflare and want browser-accessible SSH or HTTP without client software.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;WireGuard&lt;/strong&gt; if you have a confirmed public IP and want no third-party in the data path.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;port forwarding&lt;/strong&gt; only if you already have DDNS working and want no new software — it has the highest hardening overhead.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;For a hardened SSH entry point → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bastion/" &gt;Bastion / jump server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For exposing internal web services via public URLs → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunneled-reverse-proxy/" &gt;Tunneled reverse proxy platforms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>