Category
Networking, DNS and remote access
Every service you add is another certificate, another port and another login form. One proxy in front of all of them collapses that into one thing to get right.
Every service you add brings a port, a certificate problem and a login form. Handled one at a time, that is a growing pile of things to keep patched and a growing number of ways to be wrong. Handled once, at the edge, it is a single file you can read end to end.
That is the whole shape of this category. One proxy terminating TLS for everything, one way in from outside, and a resolver that makes internal names work. Get those three right and most of the rest of self-hosting stops being a networking problem.
The default: Caddy in front, WireGuard for the way in#
Caddy terminates TLS for every service on the box. It idles around 25 MB, scores 1 on ops load, and a working site with automatic HTTPS, HTTP redirect, OCSP stapling and unattended renewal is three lines:
jellyfin.example.com {
reverse_proxy jellyfin:8096
}For internal names it mints its own local CA, so https://nas.internal stops throwing warnings. The cost is modules: the stock binary and official image ship no DNS provider plugins, so wildcard certificates mean building with xcaddy and rebuilding on every upgrade.
For remote access, wg-easy gives you a WireGuard server with a QR code per device. Treat v15 as a different product from v14: there is no migration path at all, the latest tag still points at v14, and the licence changed to AGPL-3.0-only.
Two situations where those defaults are wrong.
Your containers change most weeks and you redeploy stacks constantly. Then Traefik, which builds its routing table from Docker labels so a new service routes itself. Budget 60 MB idle and ops 3, and know that pasted v2 snippets fail silently on v3: matcher values need backticks, Headers became Header, IPWhiteList became IPAllowList, and a stale label produces a 404 from whichever router did match rather than an error.
Both ends are behind CGNAT. A WireGuard server needs a reachable endpoint. Headscale instead runs the coordination server for official Tailscale clients, so you get NAT traversal and MagicDNS without your device inventory living at a vendor. It is pre-1.0, you must upgrade one minor at a time, and v0.29.3 documents a Tailscale client floor of v1.80.0.
DNS blocking is not security#
Pi-hole and AdGuard Home are worth running. They cut ads, kill a lot of telemetry, and make an entire household's browsing quieter for 60 to 100 MB. What they do not do is protect anything. They see only queries that arrive on port 53, and browser DoH, phone private-DNS profiles and TVs with hardcoded resolvers bypass them entirely. If you want them enforced, block outbound 53 and 853 to everything except your resolver at the firewall. That is a firewall control, not a Pi-hole feature.
Pick AdGuard Home when you want DoH, DoT and DoQ built in without a cloudflared sidecar. Pick Pi-hole for group-based per-device rules and the larger list ecosystem. Pick Technitium DNS Server only when you actually own a domain and need authoritative zones, DNSSEC signing or split horizon, and know that creating a primary zone for example.com makes that server authoritative for every name under it, so any public record you did not copy in returns NXDOMAIN, mail included.
The mistakes people actually make#
- Exposing an app's own login. Nginx Proxy Manager's port 81 ships
admin@example.com/changemewith no rate limiting. Technitium's console isadmin/adminon 5380. These are documented publicly and scanned for within hours. - Bind-mounting
acme.jsonthat does not exist yet. Docker creates a directory with that name, Traefik cannot persist certificates, and it re-requests them every restart until Let's Encrypt rate limits you. Runtouch acme.json && chmod 600 acme.jsonfirst. - Forgetting websockets. Nginx Proxy Manager has websocket support off per host by default. The app loads, live updates never arrive, and the nginx error log stays empty.
- Editing config that is read once. Technitium's
DNS_SERVER_*variables apply only when no config file exists. AdGuard Home overwrites its own YAML while running. Pi-hole v6 renamed every Docker variable and ignores the old ones silently. - Leaving the Docker socket writable. Traefik with socket access is a compromise path to host root. Mount it
:roat minimum, and prefer a socket proxy that whitelists only the container and event endpoints.
What to read next#
Reverse proxy and TLS is the build instructions for the default above, and Reverse proxy generator writes the config for you. Remote access without port forwarding covers the VPN choice properly, DNS for self-hosters covers subdomains and split horizon, and A security baseline for a home server is where the firewall rules that make DNS filtering stick actually live. For head-to-head calls: Caddy vs Traefik, Caddy vs NPM, Pi-hole vs AdGuard Home and Headscale vs wg-easy. Check Port conflict checker before you add anything that wants 53, 80 or 443.
Every networking tool we profile#
Sorted by operational load, lightest commitment first. Every row links to a full profile with the resource numbers, the upgrade traps and the restore path.
| Tool | Ops load | Idle RAM | Licence | Identity | Backup shape |
|---|---|---|---|---|---|
| Caddy | 1, Set and forget | 25 MB | Apache-2.0 | Not applicable | File copy |
| AdGuard Home | 2, Light | 60 MB | GPL-3.0 | Local accounts only | Embedded key-value store |
| Nginx Proxy Manager | 2, Light | 100 MB | MIT | Local accounts only | SQLite backup |
| Pi-hole | 2, Light | 100 MB | EUPL-1.2 | Local accounts only | SQLite backup |
| wg-easy | 2, Light | 90 MB | AGPL-3.0-only | Native OIDC | SQLite backup |
| Headscale | 3, Moderate | 40 MB | BSD-3-Clause | Native OIDC | SQLite backup |
| Technitium DNS Server | 3, Moderate | 130 MB | GPL-3.0 | Native OIDC | File copy |
| Traefik | 3, Moderate | 60 MB | MIT | Auth proxy only | File copy |
Profiles#
Caddy
Web server and reverse proxy that obtains and renews its own TLS certificates
ops 2/5 · 60 MBAdGuard Home
DNS blocker with encrypted upstreams, per-client rules and its own DoH/DoT server
ops 2/5 · 100 MBNginx Proxy Manager
Web UI for nginx reverse proxy hosts and Let's Encrypt certificates
ops 2/5 · 100 MBPi-hole
Network-wide DNS blocker with per-device groups and a full query log
ops 2/5 · 90 MBwg-easy
WireGuard server with a web UI for adding clients and scanning QR codes
ops 3/5 · 40 MBHeadscale
Self-hosted reimplementation of the Tailscale control server for official Tailscale clients
ops 3/5 · 130 MBTechnitium DNS Server
Authoritative and recursive DNS server with DNSSEC signing, DHCP and a real web console
ops 3/5 · 60 MBTraefik
Reverse proxy that builds its routing table from Docker labels, with ACME built in
Head to head in this category#
- Caddy vs Nginx Proxy ManagerA GUI you click against a file you can copy. The comparison is really about backup and reproducibility.
- Caddy vs TraefikStatic config you can read against dynamic discovery from Docker labels, with the v3 syntax trap spelled out.
- Headscale vs wg-easyMesh coordination with NAT traversal against one UDP port you forward yourself, plus the case for not self-hosting at all.
- Pi-hole vs AdGuard HomeOne ships encrypted DNS and per-client policy in the binary. The other needs a second daemon for half of that.
Guides for this category#
- DNS, subdomains and split horizon for self-hostersThe naming scheme decision, wildcard records, split-horizon resolution, and the rebinding protection that breaks all of it.
- Remote access without port forwardingFour routes into your home network, what CGNAT does to three of them, and the mistake almost everyone makes first.
- Reverse proxy and TLS, done properlyOne proxy in front of everything: the ACME challenge decision, the headers that matter, and working Caddy and Traefik config.
Questions#
Do I need to open ports on my router?
No, and usually you should not. A WireGuard tunnel or a mesh VPN gets you to your services from anywhere without any inbound port, which removes the entire class of attacks that start with a scanner finding your IP. Use wg-easy if you have a public IP and one admin handing out configs, or Headscale if devices sit behind CGNAT on both ends and need NAT traversal. Open 80 and 443 only for services you genuinely intend strangers to reach, and put a reverse proxy plus an auth gate in front of those.
Is Pi-hole a security tool?
No. Pi-hole and AdGuard Home only see queries that reach them on port 53. Firefox's DNS-over-HTTPS, Chrome's secure DNS, iOS and Android private DNS profiles, and smart TVs with hardcoded resolvers walk straight past, and the dashboard just shows a suspiciously quiet device. They block ads and telemetry domains, which is worth doing. They do not stop an exploit, they do not authenticate anyone, and a device that already has malware will resolve whatever it likes over 443. Treat DNS filtering as hygiene, not as a control.
Which reverse proxy should I run?
Caddy, unless your containers change weekly. Caddy idles near 25 MB, scores 1 on ops load, and a working HTTPS site is three lines with automatic certificate renewal. Traefik idles near 60 MB and scores 3, and earns that when routes should appear from Docker labels without you editing anything. Nginx Proxy Manager is the honest answer if you want a web form instead of a text file, at the cost of a project that is effectively one maintainer with a v3 rewrite open since 2021.
Why does my wildcard certificate fail with Caddy?
Because wildcards need the DNS-01 challenge and the official Caddy binary and Docker image ship zero DNS provider modules. You have to rebuild with xcaddy build --with github.com/caddy-dns/cloudflare or use the caddy:builder image in a two-stage Dockerfile, and rebuild on every Caddy upgrade. caddy add-package exists but is experimental and swaps the binary on disk, which a container recreate throws away. Also mount a persistent /data volume before you point real DNS at it, or repeated redeploys will get you rate limited by Let's Encrypt.
Can I run two reverse proxies for redundancy?
Not with Traefik and Let's Encrypt. The documentation says plainly that multiple instances cannot share the file-based ACME store, because nothing coordinates which instance receives a given challenge. Two replicas on a shared volume clobber each other's acme.json. The documented answers are cert-manager or Traefik's commercial distributed store. For a home network, the more useful redundancy is a second DNS resolver, since name resolution failing takes down everything at once while a proxy restart takes seconds.
Should I put my apps' own login pages on the internet?
No. Application logins are the weakest link you own: Nginx Proxy Manager ships admin@example.com / changeme on port 81 with no rate limiting, Technitium defaults to admin / admin, Calibre-Web to admin / admin123. Those defaults are published, and scanners find new hosts within hours. Put a VPN or a forward-auth gate in front, publish only the services that must be public, and change every default credential during the first session rather than after DNS is pointed at the box.
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.