Homelab Networking from Scratch: DNS, Reverse Proxy, VPN, and SSL
Set up homelab networking the right way: internal DNS, reverse proxy with HTTPS, wildcard SSL, and secure remote access via Tailscale or Cloudflare.
Most homelab guides show you how to spin up a single container and reach it on a raw IP and port. That works for one service. It does not scale to ten, does not give you HTTPS, and does not let you reach anything securely from outside your house.
This guide covers the four-layer networking stack that actually works at homelab scale: internal DNS, a reverse proxy with HTTPS, wildcard SSL certificates, and secure remote access. I run this exact stack at home on a Proxmox cluster with Technitium DNS, Nginx Proxy Manager, a Cloudflare wildcard cert, and Tailscale for remote access. The full picture is in my homelab stack. If you are just getting started, the homelab beginner’s guide is the place to begin before tackling networking.
Each layer in this guide has its own dedicated step-by-step walkthrough. The goal here is to show you how the layers connect, in what order to tackle them, and why skipping steps creates problems that are miserable to debug later.
Quick Answer: The Build Order
- Internal DNS: replace your router DNS with a local server so services get real hostnames
- Reverse proxy: install Nginx Proxy Manager to route hostnames to containers and handle cert issuance
- Wildcard SSL: one cert covers all your subdomains, issued via Cloudflare DNS challenge
- Remote access: Tailscale for internal services, Cloudflare Tunnel for anything public-facing
- Lock it down: SSO via Authelia in front of public services, audit what is actually exposed
The order matters. Setting up the reverse proxy before DNS means configuring NPM using raw IPs for a week, then having to redo all of it. Getting SSL working before the reverse proxy is misconfigured is a good way to burn through Let’s Encrypt rate limits. Do it in this order and each step builds cleanly on the last.
Layer 1: Internal DNS
Your router’s DNS tells devices on your network how to find websites on the public internet. It knows nothing about your homelab services.
Without internal DNS, every service is reached by IP and port: http://192.168.1.10:8096. With internal DNS, it’s https://jellyfin.yourdomain.com. The difference sounds cosmetic. It is not. You cannot get a valid SSL certificate for a bare IP. You cannot bookmark something cleanly when the port changes. And debugging network issues with raw IPs is genuinely painful.
The fix is a local DNS server running on your network. I use Technitium DNS. It handles internal zones (so jellyfin.homelab.lan resolves to your server IP), upstream forwarding to Cloudflare or Google for public domains, and split DNS so your domain’s internal and external records stay separate.
# Technitium DNS via Docker Compose
mkdir -p ~/docker/technitium && cd ~/docker/technitium
services:
technitium:
image: technitium/dns-server:latest
container_name: technitium
hostname: dns-server
restart: unless-stopped
ports:
- "5380:5380/tcp"
- "53:53/udp"
- "53:53/tcp"
volumes:
- ./data:/etc/dns
Once it is running, point your router’s DHCP DNS setting to the IP of the machine running Technitium. Every device on your network will now resolve DNS through your local server.
Technitium vs Pi-hole vs AdGuard Home: all three work, but they solve slightly different problems. Technitium is the most capable for internal DNS zones, split DNS, and running as a full recursive resolver. Pi-hole and AdGuard Home are excellent at ad blocking. If you want both, you can run Pi-hole upstream of Technitium, or use Technitium’s built-in block lists. My preference is Technitium for the control it gives over internal zones.
Full walkthrough: Technitium DNS setup guide and the Pi-hole / AdGuard comparison.
Layer 2: Reverse Proxy
A reverse proxy sits in front of all your services and routes traffic based on hostname. You hit https://jellyfin.yourdomain.com, the proxy knows that means port 8096 on your media server, and forwards the request. Every service gets a clean URL. Every service gets HTTPS. One open port (443) handles everything instead of a different port per service.
Nginx Proxy Manager is the right choice for almost every homelab. It has a web UI, handles Let’s Encrypt certificate issuance automatically, and requires zero nginx config file editing. Caddy and Traefik are solid alternatives with different trade-offs. See the reverse proxy comparison if you want to evaluate options before committing.
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "81:81"
- "443:443"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
The admin UI runs on port 81. Default credentials are [email protected] / changeme. Change them immediately.
For each service you want to expose, create a proxy host: hostname (jellyfin.yourdomain.com), forward to the internal IP and port, and enable SSL. NPM handles the rest.
Full walkthrough: Nginx Proxy Manager setup guide.
Layer 3: Wildcard SSL
At this point, every service could technically get its own Let’s Encrypt certificate. That works. It also means one HTTP-01 challenge (which requires port 80 open) per subdomain, separate renewal cycles for each cert, and rate-limit headaches if you have a dozen services.
The better approach is a single wildcard certificate for *.yourdomain.com. One cert covers every subdomain you create, now or later. Renewal is a single operation. And the issuance method, DNS-01 challenge via Cloudflare, requires zero open ports. Your server reaches out to the Cloudflare API to prove domain ownership. Nothing inbound needs to change.
In NPM, go to SSL Certificates, request a new Let’s Encrypt cert, check “Use DNS Challenge,” select Cloudflare, and paste in your Cloudflare API token. The cert issues in about 30 seconds.
Once you have the wildcard cert, assign it to every proxy host. NPM will use the single cert for all of them and renew it automatically before it expires.
Full walkthrough: Wildcard SSL certificate setup guide.
Layer 4: Remote Access
Getting HTTPS working inside your house is one problem. Reaching your services from outside your house is a different one. There are three main approaches, and they have meaningfully different security trade-offs.
Option comparison
| Method | Open ports on router | Public internet exposure | Setup complexity |
|---|---|---|---|
| Port forwarding | Yes (443, 80) | Direct | Low |
| Tailscale | None | None | Low |
| Cloudflare Tunnel | None | Via Cloudflare edge | Medium |
Port forwarding is the oldest approach and the one to avoid if you have a better option. It exposes your server directly to the public internet. Every open port is a target. If you run anything with a login page on a forwarded port, your logs will fill up with brute-force attempts within days.
Tailscale requires no open ports. You install Tailscale on your homelab server and on your phone or laptop. The devices form a private encrypted mesh network (Tailnet), and you can reach your homelab services as if you were sitting at home. This is the right default for personal access to internal services.
# Install Tailscale on your homelab server
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
With Tailscale subnet routing enabled, you can expose your entire home network range (192.168.1.0/24 or whatever yours is) to the Tailnet, meaning every device on your home network is reachable from anywhere you have Tailscale running.
# Enable subnet routing
sudo tailscale up --advertise-routes=192.168.1.0/24
Cloudflare Tunnel is the right choice when you want something publicly reachable. It creates an outbound tunnel from your server to Cloudflare’s edge, and Cloudflare handles the public-facing URL. No ports open, no direct exposure. Combine it with Cloudflare Access for auth at the edge before traffic ever reaches your server.
Full walkthroughs: Tailscale homelab setup, Tailscale subnet routing, WireGuard self-managed VPN, and Cloudflare Tunnel setup.
Layer 5: Lock It Down
The networking stack above gets services online and reachable. The security layer keeps them from being exploited.
For anything publicly accessible: put Authelia in front of it. Authelia is a single-sign-on layer that sits between Nginx Proxy Manager and your services. Requests hit NPM, NPM checks Authelia, Authelia verifies identity, then the request passes through. This means even if a service has a weak login of its own (or no login at all), Authelia catches it first.
VLANs and network segmentation: If you have IoT devices, guest devices, or untrusted hardware on your network, putting them on a separate VLAN prevents a compromised smart bulb from being on the same broadcast domain as your NAS. The VLAN setup guide and the Proxmox networking guide cover both the theory and the config for Proxmox bridge and VLAN setups.
The security baseline: the homelab security guide covers the full audit list: what should and should not be publicly exposed, how to audit your open ports, SSH hardening, and firewall rules.
Full walkthrough: Authelia SSO setup guide.
Common Mistakes
Setting up DNS after everything else. This is the one that costs the most time to fix. If you configure NPM proxy hosts using raw IPs because DNS is not running yet, you will redo all of it when DNS comes online. Do DNS first.
Using a .lan or .local domain for your internal network. These look clean but you cannot get a valid TLS certificate for them. Let’s Encrypt requires a real registered domain for DNS-01 challenges. A real domain is $10/year. Use it.
Port forwarding when Tailscale exists. There is almost no reason to forward ports to a homelab server in 2026. Tailscale is free, takes five minutes to set up, and exposes nothing to the public internet. The only reason to forward ports is if you are running a game server or something else that requires direct inbound connections from arbitrary clients.
Wildcard cert rate-limit failures. Let’s Encrypt has strict rate limits per registered domain. If you try to issue a cert, fail, and retry repeatedly, you can burn through your limit and be locked out for a week. Test with the Let’s Encrypt staging environment first. NPM makes this easy: there is a “staging” checkbox in the cert request UI.
Forgetting split DNS. If your domain resolves to your home’s public IP from inside your network, you will hit NAT loopback issues on some routers. The fix is split DNS: your internal DNS server returns the internal IP for your domain, and the public DNS returns the public IP. Technitium makes this configuration straightforward with its zone settings.
Reverse Proxy Options
If you want to evaluate alternatives to NPM before committing:
| Proxy | UI | Config style | Best for |
|---|---|---|---|
| Nginx Proxy Manager | Web UI | Click-through | Beginners, most homelabs |
| Caddy | Config file | Caddyfile (simple) | Clean syntax, auto-HTTPS by default |
| Traefik | Config file / labels | Docker labels or YAML | Docker-native, auto-discovery |
NPM is the default here because the UI removes most of the friction for first-time setups. Caddy is the right move once you are comfortable with config files and want something lighter. Traefik shines when you are running a lot of Docker containers and want zero-touch proxy config via labels. See the full comparison and the reverse proxy deep dive.
Where to Go from Here
If you work through this stack in order, by the end you will have every homelab service reachable at a clean HTTPS URL from inside your network, accessible from anywhere via Tailscale, and locked behind SSO if you exposed anything publicly. That covers the networking layer for most homelabs.
The homelab stack page is where I keep the full list of what I actually run, updated as it changes. The beginner’s guide is the place to go if you are still working out hardware and want to understand what the full setup looks like before diving into networking. Each spoke guide linked above has the step-by-step instructions for its own layer. This guide is the map; those are the directions.