My router forwards exactly one port to the internet. UDP, for WireGuard. That's it.
No port for the file storage. No port for the router in front of my services. No port for anything. A port scan from outside finds one thing it could try itself against, and that one thing only talks to devices holding the right key.
Getting there took two evenings of debugging, a phone battery that died twice as fast as it should have, and a tunnel that cheerfully displayed "connected" while passing zero packets. Here's all of it.
The moment the forwarding list got too long
After sorting out single sign-on (that's its own story — the short version: I locked myself out of my entire homelab), one login covered everything. But the port forwarding list on my router still read like an invitation.
File storage forwarded on 443. A second service on a custom port, because the first one had taken the obvious one. An SSH port I'd opened "just quickly" for an emergency and then forgotten to close.
Every open forward is a bet. I'm betting the service behind it is always patched. I'm betting there's no zero-day. I'm betting I never fat-finger a firewall rule.
Three bets I didn't want to keep taking. So: forwards out entirely, and exactly one encrypted way in instead.
What I built
The idea is simple, and that's the strength of it.
The router has one open UDP port for WireGuard and nothing else. Anyone who wants to reach my homelab first builds a WireGuard tunnel to the router — and is then, as far as the network is concerned, sitting in the same network as a device plugged in at home. The routing layer in front of my services (with the identity provider behind it) is reachable at its internal address exactly as before, but only through the tunnel, never from the open internet.
Every device — laptop, phone, my partner's tablet — gets its own key pair and its own peer entry in the router's WireGuard config. No shared credentials. If a device is lost or sold, I delete that one entry and nothing else is affected.
Same thinking as with SSO: not one shared secret for everyone, but an individual key per access path that can be revoked on its own.
Mistake 1: I accidentally built a full tunnel and broke my phone's internet
Setting up the first client config on my phone, I set AllowedIPs to 0.0.0.0/0, because that's what the first guide I found said to do.
What that actually means: not just the traffic to my homelab, but every last bit of the phone's internet traffic now went through the tunnel to my router at home — and only then back out to the internet from there.
On mobile data, the result was noticeably slower browsing, because every request took a detour through my apartment first, and a battery that drained about twice as fast, because the tunnel stayed up permanently for things that had nothing to do with my homelab.
It took me two days to notice that "my phone's internet has been sluggish lately" and "I set up WireGuard yesterday" were the same event.
The lesson: AllowedIPs is the knob between split tunnel (only traffic destined for the homelab network goes through) and full tunnel (literally everything). For day-to-day access to your own services you almost always want split tunnel — put your own subnet in there, not 0.0.0.0/0.
Full tunnel is a real and legitimate configuration. It's just a deliberate choice, not a default you copy out of a guide.
Mistake 2: "connected", and not a single packet got through
Out on mobile data — behind the carrier-grade NAT most mobile networks use — WireGuard completed the handshake cleanly. Then, after a few minutes of sitting idle, the connection outward was effectively dead. The app still said "active". No ping, no page load, nothing.
No error. No message. Just silence.
I blamed the router first. Then DNS. Then, eventually, I found the detail that the first guides I'd read barely mentioned:
Mobile carrier NAT forgets open connections far faster than a home router does, and WireGuard, by default, sends nothing to keep the mapping warm when there's no data to move. No traffic, no packets, and the NAT entry quietly expires. The client has no idea — from its point of view the tunnel is still established.
The lesson: set PersistentKeepalive to a low value (25 seconds is the common choice) for any client that sits behind NAT — which in practice is every mobile device. It sends a tiny packet during idle periods so the NAT mapping stays open and the tunnel is genuinely reachable, not just "connected according to the display".
That distinction — reachable versus shows as connected — is worth internalizing. It shows up in a lot more places than WireGuard.
The setup, condensed
Same result, without the two evenings:
- Open exactly one port. UDP for WireGuard on the router. Everything else that was previously forwarded: closed.
- One key pair per device. No shared setup, every device gets its own peer entry so individual access can be revoked individually.
-
Scope
AllowedIPsdeliberately to your homelab subnet (split tunnel), not0.0.0.0/0— unless routing all your traffic is what you actually want. -
Set
PersistentKeepalive = 25on every device behind NAT — that's essentially every phone. - Hand over internal DNS, so hostnames on the homelab network resolve over the tunnel too, not just raw IPs.
- Test from real mobile data, not from your own Wi-Fi. Mistake 2 only surfaces out there; at home it would never have shown up.
- Keep the routing layer and the identity provider exactly as they are. The tunnel replaces the way in. It does not replace authentication behind it.
That last point matters. A tunnel is a network control, not an access control. Anything on the other side that was worth protecting before is still worth protecting.
What changed
The difference isn't "faster" or "more convenient." It's invisible, and that's the whole point.
From the outside, my homelab shows no service port at all anymore — just one encrypted tunnel endpoint. There's nothing there for an automated scan to work with, and getting through requires a key I handed out myself, to a device I can revoke individually.
The old setup wasn't reckless. It was just a pile of small bets I'd stopped keeping track of.
I write about self-hosting in more depth — one homelab component per issue, always including what broke — in my German-language newsletter Souveränes Homelab: souveraenes-homelab.beehiiv.com. The English write-ups land here.
Määäx
Top comments (0)