self-hosting with pfsense, cloudflare, and nginx proxy manager - network setup explained
getting a self-hosted service onto the public internet involves more moving parts than just opening a port on your router. here's the full path a request takes from a browser to a service running on my homelab, and how pfSense, Cloudflare, and Nginx Proxy Manager work together.
the problem with self-hosting on home internet
home internet connections have two annoying properties: the public IP address changes (dynamic IP), and there's a router sitting in front of everything doing NAT. neither of these is a problem if you're just browsing the web. they're both problems if you want to self-host a website or media server from home.
the stack
browser
└── Cloudflare (DNS + proxy)
└── pfSense (port forward 443 → internal server)
└── Nginx Proxy Manager (SSL termination, routing)
└── internal service
four layers, each doing something specific.
cloudflare
Cloudflare sits in front of the domain. all DNS for thenug.net runs through them, and the records are set to proxied — meaning traffic goes to Cloudflare's edge first, not directly to my IP.
this does a few things:
- hides the real public IP (Cloudflare's IP is what's publicly visible)
- handles DDoS mitigation and bot filtering at the edge
- provides the SSL certificate the browser sees
the public IP is dynamic, so a small container called cloudflare-ddns runs on the home server. it polls for the current public IP every 5 minutes and updates the DNS A records via the Cloudflare API if anything changed. no more manually updating DNS when the ISP rotates the IP.
pfsense firewall and port forwarding
pfSense runs on a Protectli Vault - a fanless mini PC with multiple Intel NICs, purpose-built for running pfSense or OPNsense as a home firewall appliance. no moving parts means it runs silent and cool 24/7. it replaced a consumer router and the difference in control and reliability is night and day.
pfSense handles the firewall and port forwarding. it has two rules that matter here:
- port 443 (HTTPS) → internal server
- port 80 (HTTP) → internal server (needed for Let's Encrypt certificate validation)
everything else is blocked. no other ports are forwarded. the internal network isn't reachable from outside beyond these two.
nginx proxy manager
NPM runs on the internal server and is the actual entry point for all traffic. it handles two things:
SSL termination — NPM holds the Let's Encrypt certificates and decrypts HTTPS traffic. from NPM inward, everything runs over plain HTTP on the local network. the certificates auto-renew via Let's Encrypt.
routing by hostname — a single IP and port can serve multiple services because NPM inspects the Host header and routes accordingly:
thenug.net → blog container on port 8080
jelly.thenug.net → Jellyfin on port 8096
jellyseerr.thenug.net → Jellyseerr on port 5055
adding a new external service is just: add a DNS record in Cloudflare, add a proxy host in NPM pointing to the internal IP and port, enable SSL. done.
nat hairpinning
one quirk of this setup: accessing https://jelly.thenug.net from inside the same network usually doesn't work. the request goes out to Cloudflare, comes back to the public IP, hits pfSense — and pfSense doesn't loop it back in. this is called NAT hairpinning, and most consumer/prosumer routers handle it inconsistently.
the fix is to use internal IPs directly when on the LAN. pfSense can also be configured with NAT reflection to make external URLs work internally, but it adds complexity for not much gain.
why not just use a VPS
cloud hosting would simplify some of this - no dynamic IP, no port forwarding, no hairpinning. but running it on hardware you own means:
- no monthly cost beyond electricity
- full control over what's exposed
- the setup is actually interesting to build and understand
the whole stack costs nothing beyond hardware that was already here. a dedicated pfSense appliance like the Protectli Vault is the one investment worth making - it gives you enterprise-grade firewall features (VLANs, traffic shaping, IDS/IPS) at a one-time cost instead of a monthly subscription.

Fanless, 4x Intel GbE Ports - runs pfSense/OPNsense
Purpose-built for self-hosting, VLANs, and homelab networking
view on amazonaffiliate link - helps support the blog
eli5
your home doesn't have a permanent street address — it changes every so often (dynamic IP). so instead of giving people your address, you hire a post office (Cloudflare) that everyone can find. they know where to forward your mail, and whenever your address changes, a small robot (cloudflare-ddns) calls the post office and updates the forwarding record. when mail actually arrives at your door, a security guard (pfSense) checks that it came in the right way, then passes it to a receptionist (Nginx Proxy Manager) who figures out which room it belongs in based on what's written on the envelope. the rooms are things like the blog, Jellyfin, Jellyseerr. each one gets its own door, but they all come in through the same front entrance.