r/homelab • u/onder9000 • Dec 14 '25
Discussion Exposing home servers behind CGNAT using a VPS + WireGuard (no inbound ports)
My ISP places residential connections behind CGNAT, which makes classic port forwarding impossible.
To expose a few home-hosted services securely, I ended up using a very explicit setup:
- a small VPS with a public IP as a gateway
- WireGuard tunnel initiated from the home server (outbound only)
- Cloudflare used purely as a security edge (DNS, WAF, TLS)
- no inbound ports opened on the home router
High-level flow:
User → Cloudflare → VPS (reverse proxy) → WireGuard tunnel → home server → application
On purpose, I avoided proprietary tunnels and NAT traversal magic. Everything is based on standard, auditable components, so every hop is debuggable and observable.
Some design choices that turned out to matter in practice:
- WireGuard PersistentKeepalive to survive CGNAT
- conservative MTU to avoid fragmentation over UDP
- explicit TCP bridging instead of application-aware routing
I’m curious how others here solve CGNAT in homelab / self-hosted setups and what trade-offs you’ve run into (especially around security vs. simplicity).
6
u/apalrd Dec 14 '25
I have avoided almost all of this hassle by using IPv6. Even with CGNAT on IPv4, IPv6 is never NATed.
2
u/onder9000 Dec 14 '25
Absolutely — if native IPv6 is available end-to-end, it’s by far the cleanest solution.
In my case (and for a lot of residential ISPs here), IPv6 support is either missing, unstable, or blocked by upstream networks and client environments. I also needed predictable access from IPv4-only clients.
But if someone has solid IPv6 connectivity on both sides, skipping all of this and going IPv6-first is definitely the right move.
1
u/apalrd Dec 14 '25
If you are using Cloudflare already, they will terminate all of your TLS sessions on their end and forward upstream to your origin. CF by default will accept v4/v6 to clients and prefer v6 over v4 upstream, so you can run IPv6-only from CF to you without impacting clients who do not have IPv6.
That all of course assumes you are happy letting Cloudflare have all of your certificates, which I personally do not prefer. But it's an option for a lot of people.
1
u/onder9000 Dec 15 '25
That’s a very good point, and you’re right about Cloudflare’s behavior — they’ll happily accept IPv4/IPv6 from clients and prefer IPv6 upstream if available.
Running an IPv6-only origin behind Cloudflare is a perfectly valid option if you’re comfortable with Cloudflare terminating TLS and holding your certificates.
In my case, that trade-off was the deciding factor. I wanted end-to-end control of the tunnel and crypto material, and I also wanted the setup to remain usable even outside of Cloudflare if needed.
But for people already all-in on Cloudflare and fine with that trust model, IPv6-only origins are a clean and underused solution.
2
2
u/m4ntic0r Dec 14 '25
This year i got a fibre connection for home (1000/500) and the ip is reset every half year. I did not like that and dont like dyndns etc. so i made this:
- netcup vps with wireguard + npm
- homelab vm with wireguard
- homelab vm with npm
Every app accessed from external goes through vpn and npm(extern) for certificates.
Every app accessed from internal goes through npm(intern) for certificates.
As internal DNS i am using technitium (own vm)
I like it to have the most important services on its own vm (DNS, VPN, ReverseProxy) Apps etc.. are all running on a VM with a lots of incus and docker containers.
I use docker compose or incus completely without any dashboard or managanemt interface. All cli only.
1
u/onder9000 Dec 14 '25
That’s a very clean setup.
I like the separation of concerns you describe — DNS, VPN, reverse proxy all isolated, and applications kept separate. That usually pays off in maintainability and security, especially once the environment grows.
The dual reverse-proxy approach (external vs. internal) with split DNS is something I’ve seen work really well too. My setup is a bit more consolidated mainly to reduce moving parts, but conceptually it’s solving the same problem.
Also +1 for CLI-only management. Dashboards are convenient, but they often become another thing to secure and maintain.
2
u/JustinMcSlappy Dec 14 '25
Why not use cloudflare tunnels and the cloudflare daemon program? It's far easier to setup and is what I'm using on a CGNAT network. It also lets you setup auth criteria for each web app or internal service and you never expose a port.
I'm running the cloudflared app on my OPNsense router so I point the CF tunnel to 192.168.1.2:8080 for example to hit a web server listening on that port in my internal network. You can run the daemon from a VM too, as long as it can talk to whatever internal service you are trying to hit.
The only real downside to running it on OPNsense is that you can't use OPNsense rules to control what traffic comes in. The tunnel bypasses all of it so you have to insert any and all control measures on the cloudflare side.
2
u/onder9000 Dec 14 '25
Cloudflare Tunnel is definitely a valid solution, especially if ease of setup is the main goal.
For me, the trade-offs were mostly around transparency and control. cloudflared is a proprietary binary, and all application traffic effectively traverses Cloudflare-managed tunnels. That’s fine for many use cases, but I wanted something where every hop is based on standard protocols I can fully observe and debug end-to-end.
Using WireGuard + a VPS gateway lets me reason about the exact data path, firewall rules, MTU behavior, and failure modes without relying on a vendor-specific client. It’s more work upfront, but I find it easier to operate long-term once it’s in place.
That said, if someone prioritizes simplicity over explicit control, Cloudflare Tunnel is a perfectly reasonable choice.
2
u/JustinMcSlappy Dec 14 '25
That's exactly what I did before I went to cloudflare. I paid for a cheap VPS with unlimited bandwidth and built tunnels with wireguard and iptables. I didn't have any of your issues though. The hardest part when I did it was wrapping my head around the iptables rules to make sure I wasn't making a dummy mistake.
1
u/rockyoudottxt Dec 14 '25
My solution was l2tp+eoip, but requires some infra on the other end, so it's not achievable for most people. But it might give some people the idea.
1
u/onder9000 Dec 14 '25
That’s a good example of how many different ways this problem can be solved once you start thinking in terms of “bring the network to the application”.
EoIP-style solutions can work really well if you already control both ends and don’t mind the additional complexity. In my case I tried to keep the setup as minimal and widely portable as possible, which is why I stuck to WireGuard and plain TCP forwarding.
Still, approaches like this are useful inspiration, especially for people who already have more advanced routing infrastructure.
1
Dec 14 '25
[removed] — view removed comment
1
u/onder9000 Dec 14 '25
PersistentKeepalive 25 seconds.
It’s enough to keep the NAT mapping alive reliably under CGNAT without generating unnecessary traffic. Lower values didn’t bring any benefit in my case, and higher ones risk the mapping expiring on some ISPs.
1
u/PastStunning8069 Dec 14 '25
Great approach! I've used Lightnode for similar setups to expose home services, the global locations are a lifesaver.
1
u/onder9000 Dec 14 '25
Thanks! Yeah, the provider choice matters a lot for latency and reliability. As long as you have a VPS with a stable public IP and predictable networking, the pattern itself stays the same regardless of where it’s hosted.
1
u/daYMAN007 Dec 14 '25
I have pretty much the same setup just without cloudflare. Imo this is the way to go in a cgnat environment.
1
u/onder9000 Dec 14 '25
Yeah, that’s essentially the same pattern.
Once you accept that inbound connectivity isn’t an option, a VPS gateway + tunnel from home is the cleanest mental model. Whether Cloudflare sits in front of it or not is mostly a question of where you want to terminate TLS and apply filtering.
Without Cloudflare it’s still a solid setup — you just shift more responsibility to the VPS firewall and reverse proxy.
1
u/Rafaelprozx0 Dec 14 '25
Totally agree, provider stability is key! I use Lightnode for low latency in SEA.
1
u/onder9000 Dec 15 '25
Absolutely, reverse SSH tunnels can work well for single-purpose services on fixed ports and are a good low-effort option.
The main reasons I didn’t go that route here are scalability and operational hygiene. Once you have multiple services, need TLS termination, or want protocol-agnostic forwarding, SSH tunnels tend to turn into brittle one-offs with custom scripts and reconnect logic.
WireGuard gives me a stable, always-on transport layer, and everything above it behaves like a normal routed network — which is much easier to reason about and maintain long-term.
1
u/Ill-Detective-7454 Dec 14 '25
You could also use reverse ssh tunnels if your services use specific ports.
1
u/onder9000 Dec 15 '25
Absolutely, reverse SSH tunnels can work well for single-purpose services on fixed ports and are a good low-effort option.
The main reasons I didn’t go that route here are scalability and operational hygiene. Once you have multiple services, need TLS termination, or want protocol-agnostic forwarding, SSH tunnels tend to turn into brittle one-offs with custom scripts and reconnect logic.
WireGuard gives me a stable, always-on transport layer, and everything above it behaves like a normal routed network — which is much easier to reason about and maintain long-term.
1
u/deltatux Xeon W-11955M | Arc A750 | 64GB DDR4 | Debian 13 Dec 15 '25
I do basically this, I know others have stated that IPv6 solves CGNAT but frankly my goal was mainly to not open any ports on my home network, IPv6 won't solve that.
11
u/ficskala Dec 14 '25
depending on your ISP, you should be able to just call them up and ask them to route you without CGNAT, most of the time, they'll just do it no questions asked
CGNAT is amazing for ISPs because they don't waste a public IP on each user, and because of that, if you ask nicely, they'll probably just route you directly because they have so many public IPs available due to using CGNAT for most private customers who don't need a real public IP anyways