Nginx Proxy Manager vs Caddy vs Traefik: Which Reverse Proxy for Your Homelab?
A direct comparison of the three most common reverse proxies for self-hosting. Which one to pick based on how you actually run your homelab.
This post contains affiliate links. If you buy through them, I earn a small commission at no extra cost to you.
Once you’ve decided your homelab needs a reverse proxy, the next question is which one. The community has converged on three answers: Nginx Proxy Manager, Caddy, and Traefik.
All three handle HTTPS. All three route by hostname. All three run fine in Docker. All three are free.
So why do people argue about them on r/selfhosted every other week?
Because the differences are real once you actually live with one for six months. The “which is best” debate is mostly people projecting their own setup onto everyone else. This piece breaks down what each one is actually good at, where each one fights you, and which one to pick based on how you run your stack — not based on which Reddit thread you read last.
I run Nginx Proxy Manager in my own homelab. I’ve test-driven the other two. I’ll be honest about all three.
The three contenders, in one sentence each
- Nginx Proxy Manager (NPM) — a web UI wrapped around nginx. You click to add a host, click to request a cert, done.
- Caddy — a modern web server that handles HTTPS automatically with a small, readable config file.
- Traefik — a dynamic reverse proxy that auto-discovers services from Docker labels and reconfigures itself when containers come and go.
That sentence-level summary is most of what matters. The rest of this article unpacks why each of those design choices leads to a very different day-to-day experience.
How you actually configure each one
This is the biggest practical difference between them.
Nginx Proxy Manager is configured entirely through a web UI at port 81. You log in, click “Add Proxy Host,” fill in domain name + forward IP + forward port, click the SSL tab, click “Request Let’s Encrypt cert,” save. That’s the workflow for every service you ever add.
Pro: zero config files to maintain. Faster than the alternatives if you’re adding services one at a time.
Con: there’s no git diff of your routing setup. Backing up NPM means backing up the whole data/ and letsencrypt/ directories. Restoring on a new host means restoring those directories and pointing NPM at them. Workable, but it’s not declarative.
Caddy is configured with a single Caddyfile. The whole config for ten services fits on one screen:
jellyfin.home.example.com {
reverse_proxy 192.168.1.50:8096
}
vaultwarden.home.example.com {
reverse_proxy vaultwarden:80
}
paperless.home.example.com {
reverse_proxy paperless:8000
}
Pro: the config is the source of truth. Drop the Caddyfile in git and your whole routing setup is versioned. Adding a service is two lines. HTTPS is automatic — no checkbox, no SSL tab, just type the hostname and Caddy figures it out.
Con: every change requires editing the file and reloading. If you’re allergic to the terminal, this is friction. But there are exactly three things to learn (reverse_proxy, tls, and the site-block syntax), so it’s about an hour of upfront learning total.
Traefik is configured by attaching labels to your Docker containers:
services:
jellyfin:
image: jellyfin/jellyfin
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyfin.rule=Host(`jellyfin.home.example.com`)"
- "traefik.http.routers.jellyfin.tls=true"
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
Pro: services configure themselves when you docker compose up. Spin up a new container with the right labels, Traefik picks it up, requests a cert, and starts routing. No central config file to update.
Con: every service’s compose file gets noisier. You’re trading one place to look (Caddyfile) for label blocks scattered across every compose file you own. If you ever need to route to a service that isn’t a Docker container — like Proxmox’s web UI on a host, or a Synology NAS — you have to use Traefik’s “file provider” config alongside the Docker labels, and now you have two configuration systems.
The HTTPS story
All three handle Let’s Encrypt. All three handle wildcard certs via DNS challenge. All three auto-renew. There is no functional gap here.
But there’s a difference in how much you have to think about it.
| NPM | Caddy | Traefik | |
|---|---|---|---|
| HTTP-01 cert request | Click checkbox | Automatic | Add label |
| DNS-01 wildcard cert | Manual setup per host | One block in Caddyfile | Config file + label |
| Auto-renewal | Yes | Yes | Yes |
| LAN-only services with valid certs | Needs DNS challenge setup | Same | Same |
Caddy makes HTTPS literally invisible. You write a hostname, Caddy gets a cert. There is no “request certificate” step because Caddy assumes you want one.
NPM makes you click the SSL tab on every host. After the third or fourth service, this gets tedious — but it’s a one-time setup per service, so it’s not a daily annoyance.
Traefik requires the most upfront setup for HTTPS — you have to define a “cert resolver” in your Traefik config and reference it from every label block. Once it’s set up, it’s automatic. But the setup is meaningfully more work than Caddy or NPM.
For wildcard certs (one cert covering *.home.example.com), all three work, but Caddy’s syntax is by far the cleanest:
*.home.example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
@jellyfin host jellyfin.home.example.com
handle @jellyfin {
reverse_proxy 192.168.1.50:8096
}
}
If you want to set up wildcard certs in your own homelab, the wildcard SSL guide walks through it for NPM specifically.
What about authentication?
A reverse proxy by itself doesn’t authenticate anyone. To put a login screen in front of your services, you bolt on something like Authelia or Authentik. All three reverse proxies support this through forward-auth middleware, but the integration story differs.
- NPM — supports
auth_requestdirectives in custom nginx config blocks. Works, but the syntax is annoying. The Authelia guide has the setup that I run on my own NPM-fronted services. - Caddy — has a
forward_authdirective that takes about three lines per protected host. Cleaner than NPM by a wide margin. - Traefik — has a built-in
forwardAuthmiddleware that you reference from your container labels. Cleanest of the three for protecting many services at once.
This is the area where Traefik genuinely earns its complexity for some people. If you have 20 services and want auth on all of them, defining the middleware once in Traefik and labeling each container is less work than configuring NPM’s custom blocks per host.
For two or three services? It’s a wash.
Container discovery and Docker integration
This is where Traefik’s design philosophy pays off — if you’re running purely on Docker.
In a pure-Docker setup, Traefik watches the Docker socket. Spin up a container with the right labels, Traefik configures itself. Tear the container down, Traefik forgets it. This is genuinely magical for someone running a CI/CD pipeline that ships ephemeral preview environments, or anyone whose stack changes weekly.
NPM and Caddy don’t do this. With both, every new service requires either editing a config file (Caddy) or clicking through a UI (NPM). The “manual step” friction is real if you’re spinning up containers constantly.
But here’s the thing: most homelabs aren’t ephemeral. You set up Jellyfin once. You set up Vaultwarden once. You set up Paperless once. The “I add 30 services a month” use case is a vanishingly small slice of self-hosters. For everyone else, the Traefik dynamic-discovery advantage is theoretical.
When each one breaks
Software comparisons that pretend each option is flawless are useless. Here’s where each of these three actually fights you.
NPM:
- Custom nginx directives have to go in the “Advanced” tab as raw config — and the syntax errors aren’t surfaced clearly. You’ll save a host, watch nginx fail to reload, and have to hunt the bad line in the error log.
- The default credentials (
[email protected]/changeme) are widely known. Several homelabs have been compromised because someone exposed NPM’s port 81 and forgot to change the password. - Backing up the NPM data directory is awkward because of how Let’s Encrypt accounts are stored. Restoring on a new host can require manually re-requesting some certs.
Caddy:
- Some plugins (like the Cloudflare DNS challenge module) require a custom Caddy build, which means either using the prebuilt
caddy:builderimage or building your own. Documented, but more work than dropping in the official image. - Logging is verbose by default and the format isn’t great for tail-following. You’ll want to configure structured logging early.
- The Caddyfile syntax is simple but unforgiving. A misplaced
}will silently break a site block, and the error message points at a different line than the actual problem.
Traefik:
- The label syntax is verbose and easy to get wrong. A typo in a label name doesn’t fail loudly — the route just doesn’t appear, and you spend 20 minutes wondering why.
- Major version upgrades have historically broken config formats (v1 to v2 to v3 each broke things). Read upgrade notes carefully.
- Routing to non-Docker services requires the “file provider” config in addition to Docker labels, which means you’re maintaining two parallel configuration systems for one reverse proxy.
The decision matrix
| Use case | Pick |
|---|---|
| First reverse proxy ever, want a UI | NPM |
| Comfortable in a terminal, want a clean config | Caddy |
| Heavy Docker user, services come and go often | Traefik |
| Routing to a mix of Docker and non-Docker services | Caddy or NPM |
| Want HTTPS to “just work” with zero ceremony | Caddy |
| Adding 1-2 services a year | NPM (UI is faster than editing files) |
| Want everything in git, including routing | Caddy |
| Already using Authelia and want clean auth integration | Caddy or Traefik |
What I actually run
NPM. I set it up two years ago, the UI made the initial setup faster than learning Caddy syntax, and once it was running I had no reason to switch. The “Advanced” tab quirks bite me once every few months and I shrug them off.
If I were starting from scratch today, knowing what I know now, I’d pick Caddy. The Caddyfile-in-git workflow is the right answer for a homelab that grows over time, and the HTTPS-by-default behavior eliminates an entire category of “did I check the SSL box” mistakes.
I would not pick Traefik unless I was running a setup that genuinely changes daily. For a 10-service homelab where the services are stable, Traefik’s overhead isn’t worth its dynamic-discovery superpower.
If you’re new to reverse proxies, here’s the honest path:
- Install NPM to get a working setup in one evening
- Run it for a few months and learn what a reverse proxy actually does for you
- If you outgrow NPM (mostly: you want config in git), migrate to Caddy
- Don’t touch Traefik unless you’re running a setup that demands dynamic discovery
The NPM setup guide walks through the install if you want to start there. Once it’s working, also read why I don’t open ports — the reverse proxy choice matters less than the question of whether you’re exposing it to the internet at all. For most people, the answer is “no, use Tailscale” — and then Tailscale plus any of these three is plenty.
There is no wrong answer here. There’s only the answer that matches how you actually want to spend your evenings.