4 min read
How this site runs on my NAS with zero open ports
This portfolio is served from a NAS in my home, yet my router has no open ports. Here is how Cloudflare Tunnel and Docker make that work, and the rule that accidentally blocked Google.
Every page of this site comes from a small box in my home: a UGREEN DXP4800 Plus running Debian 12. Yet if you scan my home internet connection, you will not find a single open port. This post explains how that works, why I built it this way, and the mistake that briefly hid the site from Google.

The usual way, and why I avoided it
The classic way to host from home is port forwarding: tell the router to send traffic on port 443 to the server. It works, but it puts your home IP address in public DNS and leaves a door open that scanners on the internet will knock on all day. One unpatched service behind that door and the whole home network is exposed.
I wanted the opposite: nothing at home listening to the internet at all.
Turning the connection around
The trick is to reverse the direction. A small program called cloudflared runs on the NAS and opens an outbound connection to Cloudflare, the same way your browser connects to a website. Routers allow outbound connections by default, so no port forwarding is needed.
When you visit aniq.novafortress.my, this is the path:
- Your browser connects to the nearest Cloudflare data centre over HTTPS.
- Cloudflare applies its security rules, then sends the request down the tunnel my NAS already opened.
- cloudflared hands it to the website container, which answers back through the same tunnel.
Two side effects I like: my home IP address never appears in DNS, because every record points at Cloudflare, and attacks are absorbed by Cloudflare before they reach my connection.
The "How this page reached you" panel on the home page shows this path live, with your real round trip time replayed in slow motion.

What runs on the NAS
The NAS runs about 29 Docker containers across 11 projects, including a school management system with real users. This website is one small stack:
- web: the Next.js site. It is the only container with a port, and that port is bound to 127.0.0.1, so even devices on my own Wi-Fi cannot reach it directly. Only cloudflared can.
- backend: a Hono API for the contact form, the blog and the admin.
- db: PostgreSQL for messages, posts and site content.
- docker-proxy: a read-only window into Docker, so the site can show how many containers are running without the website ever touching the Docker socket.
The CPU, memory and network numbers come straight from the NAS kernel and update live.
Locking down the admin
The site has an admin dashboard where I edit projects and read messages. It lives on its own hostname and is protected twice:
- Cloudflare Access sits in front of it. Before the login page even loads, Cloudflare asks for my email and sends a one-time PIN.
- The admin has its own login, rate limiting, and a session cookie that only works on that hostname.
The public address does not just hide the admin, it refuses to serve it. Try /admin there and you get a 404.
The rule that blocked Google
Most of my home services should only be reachable from Malaysia, so I have a Cloudflare firewall rule that blocks other countries. When I submitted this site to Google Search Console, the answer came back: "Blocked due to access forbidden (403)".
Google's crawler visits from the United States, so my own geo-fencing rule was blocking it. Cloudflare's security events showed exactly which rule matched. The fix was one extra condition: the rule now skips this hostname, so the portfolio is reachable worldwide while everything else at home stays geo-fenced.
Lesson learned: when you add a security rule, check who else it catches.
Checking that it really is zero
"Zero open ports" is a claim, so I check it:
- The router has no port forwarding, no DMZ, and UPnP is turned off.
- A full nmap scan of all 65,535 TCP ports on my home IP, run from outside my network, finds nothing open. Every port is filtered.
- Every DNS record for the domain is proxied through Cloudflare.
What I would do differently
If I started again, I would put Cloudflare Access in front of the admin on day one, and write the geo-fencing rule with exceptions in mind from the start. Next on my list is automatic nightly backups, because a homelab is only as good as its last restore.
Questions about any of this? The contact form at the bottom of the home page comes straight to my phone.