Every self-hosted setup eventually hits the same wall: you’ve got four, six, ten services running on different ports, and typing http://10.0.0.50:8096 from memory has stopped being funny. A reverse proxy fixes that - one entry point, real hostnames, automatic TLS certificates, and a single place to manage access instead of juggling port numbers. The question isn’t whether you need one. It’s which one.

There are more options than these three, but Nginx Proxy Manager, Traefik, and Caddy cover the vast majority of homelab setups for good reason - they’re mature, well documented, and each represents a genuinely different philosophy about how a reverse proxy should work. Picking the wrong one isn’t fatal, but re-migrating a dozen services to a different proxy after the fact is a weekend you don’t get back.

The three philosophies

Nginx Proxy Manager (NPM) is nginx with a web UI bolted on top. You add a proxy host through a browser form, pick a domain, toggle Let’s Encrypt, save. It’s the easiest one to understand because you never have to look at a config file unless you want to.

Traefik is built around automatic service discovery. Point it at Docker (or Kubernetes, or a handful of other backends) and it watches for containers with the right labels, then wires up routing and certificates on its own, with zero manual proxy-host creation. The tradeoff is that the “labels in your compose file” model has a real learning curve before it clicks.

Caddy sits in between. It’s config-file-driven like Traefik, but the config format (Caddyfile) is deliberately simple, and it does automatic HTTPS by default, no separate cert-manager container, no ACME plugin to configure - it just works the moment you give it a domain.

None of these are wrong choices. They’re optimized for different things, and the right one depends on how you actually run your homelab.

Nginx Proxy Manager: the one you don’t have to think about

NPM’s entire pitch is accessibility. You get a web UI, a login screen, and a list of proxy hosts you manage by clicking “Add Proxy Host,” filling in a domain and a target IP:port, and hitting save. Let’s Encrypt integration is a checkbox. Basic auth, access lists, and custom nginx config snippets are all available through the UI if you need them, but you’re never required to touch a terminal after initial setup.

What it costs you: every new service is a manual step. Ten services means ten proxy hosts you created by hand, and if you rebuild a service with a new port, you’re back in the UI updating it. That’s fine at homelab scale (a dozen services, one person managing it), but it stops scaling gracefully somewhere past that, and it’s genuinely annoying if you spin services up and down often for testing.

What it’s good at: nothing to memorize, a real UI for viewing certificate status and access lists at a glance, and it’s forgiving of mistakes - you can see exactly what’s configured without parsing a file. If you’re new to reverse proxies generally, this is the lowest-friction way to get one running and understand what a reverse proxy is actually doing before you move to something more automated.

Resource footprint: light. It’s a single Docker container (nginx plus a small Node.js management app and a SQLite database), comfortable on an LXC with a fraction of a CPU core and a few hundred MB of RAM.

Traefik: the one that watches Docker for you

Traefik’s core idea is that your reverse proxy configuration shouldn’t live somewhere separate from the services it’s routing to - it should live in the service definition. Add labels to a container in your docker-compose.yaml (hostname, port, TLS settings), and the moment that container starts, Traefik sees it and wires up routing automatically. Tear the container down, the route disappears. No separate proxy-host step, ever.

This is the right model if you’re already managing services through Docker Compose and redeploying or rebuilding things reasonably often - the config-in-the-compose-file pattern means your routing lives in version control alongside the service itself, not in a separate system you have to remember to update.

What it costs you: the learning curve is real. Understanding entrypoints, routers, middlewares, and how they compose together takes longer than NPM’s point-and-click model, and the documentation, while thorough, assumes you already understand reverse proxy concepts. Debugging a misconfigured label set means reading Traefik’s dashboard and logs, not clicking through a UI form. Budget a genuine weekend to get comfortable with it if you’re new to the concept.

What it’s good at: once it clicks, adding a new service is often zero extra work beyond the labels you’d add anyway. It also handles more complex routing scenarios cleanly - path-based routing, weighted load balancing across multiple instances, middleware chains for auth or rate limiting - in ways that are awkward to bolt onto NPM after the fact.

Resource footprint: light, comparable to NPM. It’s a single binary/container with no database dependency (routing state comes from the Docker API or config files directly), which is arguably lighter than NPM’s UI-plus-database stack.

Caddy: automatic HTTPS without the ceremony

Caddy’s whole reputation rests on one thing: point it at a domain, and it gets a valid TLS certificate and keeps it renewed, with zero additional configuration. No separate Certbot container, no ACME DNS challenge setup for the common case, no cert-expiry monitoring you have to remember to set up. It just works, and it was the first major reverse proxy to make that the default rather than an add-on.

The Caddyfile format is genuinely simple - a domain, a reverse_proxy directive pointing at your backend, done. It reads closer to plain English than nginx or Traefik’s config syntax, which makes it approachable for anyone comfortable editing a text file but not wanting to learn a full config language.

What it costs you: no built-in Docker service discovery the way Traefik has it (there’s a community plugin, caddy-docker-proxy, that adds label-based config, but it’s not the default experience the way it is with Traefik). Out of the box, you’re still manually adding a block to the Caddyfile per service, closer to NPM’s manual-entry model, just as a text file instead of a UI.

What it’s good at: the automatic HTTPS is genuinely the best of the three with zero configuration for the standard case, and the config file, once you’re editing it directly, is faster to work in than clicking through NPM’s UI for anything beyond the basics. It’s also a legitimately fast, well-engineered proxy under the hood, not just a friendly config format wrapped around something else.

Resource footprint: light, single Go binary, no database, similar footprint to Traefik.

Which one to actually run

Pick NPM if: you’re new to reverse proxies, you manage services by hand rather than through Docker Compose, or you just want a UI you can glance at instead of a config file you have to remember the syntax for. It’s the right default for most people’s first reverse proxy.

Pick Traefik if: you already run everything through Docker Compose and redeploy services often. The label-based automatic discovery pays for itself once you’re past the initial learning curve, and it scales cleanly as your service count grows without more manual proxy-host work per service.

Pick Caddy if: you want the simplest possible HTTPS story and are comfortable editing a text config file instead of clicking through a UI. It’s a strong middle ground between NPM’s accessibility and Traefik’s automation, especially if Traefik’s label system feels like more than you need.

None of these are permanent decisions in the sense that migrating later is impossible, but it is real work - every service’s routing gets redefined in whichever new system you pick, and DNS/certificate transitions need care so you don’t drop TLS for a service mid-migration. Worth getting this choice right the first time rather than treating it as disposable.

A note on what a reverse proxy doesn’t do

None of these three protect you from a compromised service being reachable on your LAN, and none of them replace a firewall or VLAN segmentation. A reverse proxy controls how traffic gets routed and terminates TLS - it’s not a security boundary by itself. If you’re exposing anything to the public internet through one of these (rather than just cleaning up internal LAN access), pair it with access lists, basic auth or a proper identity provider (Authelia, Authentik) for anything sensitive, and don’t assume “it’s behind a reverse proxy” means “it’s secured.”

Whichever one you pick, the actual payoff is the same: real hostnames instead of port numbers, one place for TLS instead of per-service certificate management, and a setup that looks like it was built on purpose instead of accumulated. Start with whichever of the three matches how you already manage your services - that’s a better predictor of which one you’ll stick with than any feature comparison.