Putting a website online without opening a single port
My site runs on a Raspberry Pi at home, but my router has no ports open. An Nmap scan proves the firewall does what I think it does.
- Goal
- Host a public website at home without exposing my home network
- Result
- 65,531 ports filtered
- Tools
- Cloudflare Tunnel, UFW, Nmap
The usual way, and why I didn't
Normally, to host a website at home you'd forward a port on your router, opening a door from the whole internet into your house. Instead the Pi runs a Cloudflare Tunnel: it connects out to Cloudflare, and visitors reach my site through that connection. Nothing comes in through my router.
The web server itself only listens on the Pi (127.0.0.1), so even devices on my own network can't reach it directly. The tunnel is the only way in.
Mistake I made: the site worked on my Mac but nowhere else. The DNS record was called http.haydnhomelab.com, because I'd typed "http" in the subdomain box. Fixing it and clearing my devices' saved "not found" answers sorted it.
Proving the firewall works
UFW blocks everything coming in by default and only allows what I've opened. I scanned every TCP port from another device on my network:
$ sudo nmap -p- <the pi> Not shown: 65531 filtered tcp ports (no-response) PORT STATE SERVICE 22/tcp open ssh 53/tcp open domain 80/tcp open http 443/tcp open https
Only the four ports I opened at the time showed up. The rest are filtered, not closed: the firewall drops the packets without replying, so a scanner can't even tell if anything is there. I scanned over Tailscale too, and that matched my rules for Tailscale.
What I learned
- Outbound tunnels are a big shift: you can publish a service without accepting inbound connections.
- "Filtered" vs "closed" matters. Silence gives an attacker less information.
- Scan yourself after every firewall change. It's the only real proof.
How I used AI: Claude guided me through setting up the tunnel and UFW. I ran the Nmap scans myself and checked the results against my rules. The full, unedited notes are in my build log.