My homelab setup without public internet exposure

6 min read Original article ↗

This post explains how I operate my homelab with no public WAN exposure, using WireGuard to stay permanently connected to my home network from all my devices, while only routing selected subnets instead of all traffic.

Why this matters

Simply because any publicly exposed service increases the attack surface, even when protected by TLS, authentication layers or access control lists.

By avoiding WAN exposure entirely, the homelab behaves like a private network extension rather than a public service, thus from the outside, nothing exists.

Why WireGuard instead of a Cloudflare tunnel?

First: WireGuard doesn’t route traffic, it routes identities bound to IP ranges. The AllowedIPs option defines which subnets are cryptographically associated with each peer, making it trivial to route only the homelab subnets from a phone or laptop when outside, without tunneling all internet traffic.

Cloudflare Tunnel (and similar solutions) are convenient and powerful, but they introduce trade-offs, because using WireGuard:

  • The VPN endpoint is fully under my control (on my OpenWrt router)
  • No third-party infrastructure is involved
  • No downtime caused by third-party outages (example: Cloudflare outage on November 18, 2025)
  • No external authentication, DNSor routing dependencies
  • Traffic flows directly between my devices and my home network
  • The trust chain is explicit and minimal (keys and endpoints only)
  • A bug in a Docker container has no public attack exposure

Explanation

From the client perspective, there is no functional difference between a public hostname and a private IP, as long as a network path exists (aside from a few milliseconds of latency).

With this setup, all my devices behave as if they were always inside my home network, this allows me to consistently use my home Pi-hole as DNS resolver and to read or generate content entirely locally, without relying on any externally exposed service, for example:

  • Pi-hole + Unbound for DNS
  • RSS feed reading with FreshRSS
  • RSS feed generation via RSS-Bridge for sites without native feeds
  • Password management with Vaultwarden (or Bitwarden)

Alongside these core services, I also run a set of complementary services that don’t require continuous interaction or persistent connections, but benefit from being consistently reachable:

  • Immich
  • Linkding
  • Homebridge
  • Paperless
  • n8n
  • ChangeDetection
  • NAS, BentoPDF, Nodecast TV, Dockhand and similar tools

My main concern was whether these services could remain fully usable while accepting no inbound connections from the WAN at all. In particular, I initially assumed that some components would require a publicly reachable endpoint to work correctly.

For example, I thought RSS-Bridge would need a public IP or hostname to generate feeds consumable by FreshRSS, and that client applications such as Reeder would implicitly depend on public URLs.

In reality, this assumption was wrong. As long as both services live inside the same private network, they can communicate without any issue. RSS-Bridge can generate feeds on a local address, and FreshRSS can consume them directly, even without TLS.

As long as a device is connected via WireGuard, local IPs behave exactly like public ones, while remaining completely unreachable from the WAN.

For example, RSS-Bridge can generate URLs such as:

http://192.168.1.7:8087/?action=display&bridge=....

which can be added to FreshRSS without any limitation.

Vaultwarden follows the same model: it can be managed entirely over the private network using self-generated certificates. Since everything runs locally, TLS certificates are created and installed on each device, without relying on any public CA.

Essential: WireGuard split tunnel

WireGuard chart

WireGuard server is running on my nanoPi R5S router with OpenWrt, see my other “homelab setup” posts: Homelab | etcetera

⚠️

To avoid subnet overlap, you can also use another subnet for your home, such as 192.168.118.0/24 or 172.16.0.0/12

OpenWrtWG OpenWrtWG2 OpenWrtWG1

The entire setup is built around having WireGuard always active on all devices.

This doesn’t mean routing all traffic through the home connection, instead, only traffic destined for the home network subnet is forwarded through the tunnel, while all other traffic follows the normal internet path.

In practice, this is achieved using WireGuard’s AllowedIPs setting. Rather than acting as a traditional routing table, AllowedIPs defines which IP ranges are cryptographically associated with a peer and therefore sent through the tunnel.

Put simply: you instruct WireGuard to forward only traffic destined for private subnets such as 192.168.0.0/16 to the home network. Everything else bypasses the WireGuard tunnel entirely.

WireGuard macOS

Here’s the config for copy/paste:

[Interface]
PrivateKey = 
ListenPort = 51820
Address = 10.4.0.4/32
DNS = 192.168.1.4
MTU = 1420

[Peer]
PublicKey =
AllowedIPs = 192.168.0.0/16
Endpoint = your.ddns:51820
PersistentKeepalive = 25

iOS:

WireGuard iOS

⚠️

Then you obviously need to keep the On Demand connection active on all devices.

It’s always the DNS…

Like many others, my ISP assigns me a dynamic public IP address, so to reach my home network from the outside, I rely on a DDNS service configured directly on the router.

With this setup, the only real weakness I have encountered (maybe 2-3 times in 5–6 years?) is not WireGuard itself, but name resolution. Either the home IP was not updated correctly on the DDNS endpoint, or the dynamic DNS provider experienced a temporary outage. In both cases, the result was simple: the VPN endpoint hostname no longer resolved to the correct IP, and I could not connect home.

To mitigate the issue it’s enough to configure two DDNS providers on the router and rely on a simple fallback strategy: either keep an alternative endpoint in the WireGuard configuration, or manually resolve the current IP with:

dig +short your_ddns.com

…and connect directly using the resolved address.

DDNS OpenWrt

DNS, always the Pi-hole

With this configuration, all devices always use the home DNS resolver, in my case the nanoPi R4S running Pi-hole with Unbound.

This is achieved by simply specifying the DNS server in the WireGuard configuration, so that all DNS queries are automatically routed to the home network through the WireGuard tunnel, regardless of where the device is connected from.

There is, of course, some additional latency (maybe 30–40ms), but in practice this is negligible: browsers cache DNS responses, and overall performance often improves thanks to ad and tracker blocking, along with the obvious privacy benefits.

Vaultwarden

Vaultwarden requires access via a hostname and TLS to be considered secure, so I created a local DNS record in Pi-hole (Settings → Local DNS Records) mapping the service to a private hostname: vaultwarden.lan → 192.168.1.7

Pi-Hole

Once the hostname was in place, I generated my own local Certificate Authority, called Server Archive Casa Root CA and used it to create the TLS certificates used by the Vaultwarden container:

archive:/home/vaultwarden/certs# ls
cert.pem  key.pem

The root certificate (ca.crt) was then installed on macOS and iOS. One very important step is to explicitly trust the certificate, installing it alone is not sufficient. On macOS:

macOS certificates

On iOS/iPadOS trust must be enabled under: Settings → General → About → Certificate Trust Settings

iOS certificates

Once this is done, the official Bitwarden app works without any issues, using the local domain registered in Pi-hole (vaultwarden.lan:8443):

Vaultwarden app

For RSS feeds, I use FreshRSS, paired with the excellent client app Reeder Classic.

Reeder

Another service that is fundamental for me is RSS-Bridge, which can generate RSS feeds from websites that don’t provide them natively.

Once a feed is generated, it can be imported into Reeder or via the FreshRSS web interface. Everything works exactly like an external feed, since both services run on the same private network and can communicate without any issue.

Feed bridge

Conclusion

This setup does not aim to make self-hosted services safer on the internet. It removes self-hosted services from the internet entirely, and my devices always behave as if they were connected to the home network. And perhaps this is what a homelab really is: services for my devices, not for the internet.