<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cloudflare | Sergio Saucedo</title><link>https://seulsale.com/tags/cloudflare/</link><atom:link href="https://seulsale.com/tags/cloudflare/index.xml" rel="self" type="application/rss+xml"/><description>Cloudflare</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sun, 15 Mar 2026 00:00:00 +0000</lastBuildDate><image><url>https://seulsale.com/media/sharing.png</url><title>Cloudflare</title><link>https://seulsale.com/tags/cloudflare/</link></image><item><title>Zero Open Ports: Publishing Homelab Services with Cloudflare Tunnel</title><link>https://seulsale.com/post/zero-open-ports-cloudflare-tunnel/</link><pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate><guid>https://seulsale.com/post/zero-open-ports-cloudflare-tunnel/</guid><description>&lt;p&gt;Port forwarding is how homelabs get owned. Every forwarded port is a doorbell on the public internet, ringing straight into your house, answered by whatever software you last remembered to patch. My rule for the homelab is simple: &lt;strong&gt;zero open inbound ports&lt;/strong&gt;, ever.&lt;/p&gt;
&lt;h2 id="how-the-traffic-flows"&gt;How the traffic flows&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" target="_blank" rel="noopener"&gt;Cloudflare Tunnel&lt;/a&gt; inverts the connection. A lightweight daemon (&lt;code&gt;cloudflared&lt;/code&gt;) runs inside the network and dials &lt;em&gt;out&lt;/em&gt; to Cloudflare&amp;rsquo;s edge. Public hostnames — &lt;code&gt;grafana.example.com&lt;/code&gt;, &lt;code&gt;monitor.example.com&lt;/code&gt; — are DNS records pointing at the tunnel, and Cloudflare routes each one to an internal address the daemon can reach:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana.example.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://monitoring-vm:3000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sites.example.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://caddy:80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;service: http_status:404 # explicit default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;drop everything else&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The router does nothing. There is nothing to scan. From outside, my house looks like a device that never answers.&lt;/p&gt;
&lt;h2 id="the-layers-in-order"&gt;The layers, in order&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare edge&lt;/strong&gt; — TLS termination, DDoS absorption, and the only surface an attacker can see.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The tunnel&lt;/strong&gt; — outbound-only; a connector VM runs &lt;code&gt;cloudflared&lt;/code&gt; as a systemd service, and a second connector runs in Docker for a separate domain serving static sites through Caddy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The service&amp;rsquo;s own auth&lt;/strong&gt; — everything published still has a login (Grafana, Beszel, the Proxmox UI). A tunnel hides the network, not a weak password.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything else stays private&lt;/strong&gt; — admin surfaces I don&amp;rsquo;t need public (the control dashboard, SSH to most machines) live only on Tailscale, the overlay VPN layered across every node.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the services that should be &lt;em&gt;really&lt;/em&gt; private — SSH in particular — Cloudflare Access sits in front of the tunnel hostname, requiring an identity check before the connection ever reaches my network.&lt;/p&gt;
&lt;h2 id="lessons-learned"&gt;Lessons learned&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;End the ingress list with an explicit 404.&lt;/strong&gt; The default catch-all is the difference between &amp;ldquo;I exposed three services&amp;rdquo; and &amp;ldquo;I exposed whatever happened to respond.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One tunnel per trust domain.&lt;/strong&gt; My personal-brand services and my throwaway static sites run on separate tunnels with separate credentials; a leaked token compromises one, not both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth-gated is not the same as private.&lt;/strong&gt; A public login page is still public. If you wouldn&amp;rsquo;t want the URL in a search index, put it behind Access or keep it VPN-only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor from inside.&lt;/strong&gt; Blackbox-exporter probes both the internal address and the public hostname, so I can tell &amp;ldquo;service is down&amp;rdquo; apart from &amp;ldquo;tunnel is down&amp;rdquo; at a glance in Grafana.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result: friends reach the Minecraft server, recruiters reach my dashboards, and my router&amp;rsquo;s port-forwarding table has been empty for a year.&lt;/p&gt;</description></item></channel></rss>