Traefik vs Caddy vs Nginx Proxy Manager: Reverse Proxy Showdown 2026
Every self-hoster hits the same wall: you’ve got a dozen services running on different ports, and you want clean URLs with HTTPS. You need a reverse proxy. The question is which one.
Traefik, Caddy, and Nginx Proxy Manager are the three most popular choices in the self-hosting world. They all solve the same core problem — route app.yourdomain.com to the right container with automatic SSL — but they do it very differently.
This isn’t a “they’re all great!” comparison. Each one has real strengths and real trade-offs. Let’s figure out which one actually fits your setup.
The Quick Answer
If you want to skip the deep dive:
- Nginx Proxy Manager → You’re new to self-hosting and want a web UI
- Caddy → You want dead-simple config files with automatic HTTPS
- Traefik → You run many containers and want auto-discovery via Docker labels
Now let’s get into why.
At a Glance
| Feature | Traefik | Caddy | Nginx Proxy Manager |
|---|---|---|---|
| Config method | Docker labels + YAML/TOML | Caddyfile or JSON | Web UI |
| Auto HTTPS | ✅ Let’s Encrypt / ZeroSSL | ✅ Let’s Encrypt / ZeroSSL | ✅ Let’s Encrypt (manual per host) |
| Docker auto-discovery | ✅ Native | ❌ Manual config | ❌ Manual via UI |
| Web dashboard | ✅ Read-only | ❌ None built-in | ✅ Full management UI |
| HTTP/3 (QUIC) | ✅ Experimental | ✅ Default | ❌ Not supported |
| Memory usage | ~50-80 MB | ~20-40 MB | ~100-150 MB (3 containers) |
| Config reload | Hot reload | Hot reload | Requires UI save |
| Learning curve | Steep | Low | Very low |
| Written in | Go | Go | Node.js + Nginx |
| License | MIT | Apache 2.0 | MIT |
| Best for | Dynamic container environments | Simple to mid-size setups | Beginners, GUI lovers |
Nginx Proxy Manager: The Visual Approach
What It Is
Nginx Proxy Manager (NPM) wraps the battle-tested Nginx web server in a web-based management UI. You add proxy hosts through a form, click a button for SSL, and you’re done. No config files to edit.
Setup
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81" # Admin UI
volumes:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 30s
timeout: 10s
volumes:
npm_data:
npm_letsencrypt:
Default login: [email protected] / changeme. You’ll be prompted to change it immediately.
Adding a Service
- Go to Proxy Hosts → Add Proxy Host
- Enter domain name:
jellyfin.yourdomain.com - Forward hostname:
jellyfin(container name) or IP - Forward port:
8096 - Click SSL tab → Request Let’s Encrypt certificate → Force SSL
- Save
That’s it. No YAML, no labels, no config files.
Strengths
- Lowest barrier to entry. If you can fill out a web form, you can set up a reverse proxy.
- Visual certificate management. See all your SSL certs, expiry dates, and renewal status.
- Access lists built in. Restrict services to specific IPs or add basic HTTP auth without touching config files.
- Custom Nginx config. You can add raw Nginx directives per host for advanced use cases.
Weaknesses
- No auto-discovery. Every new container needs a manual entry in the UI. At 20+ services, this gets tedious.
- No HTTP/3. NPM is built on Nginx, and the underlying version doesn’t ship with QUIC support.
- Database dependency. NPM stores config in SQLite/MariaDB, adding a failure point vs. flat config files.
- Stale development periods. The project has had gaps in maintenance. Check the GitHub pulse before committing.
- Three containers. The full stack runs Nginx, a Node.js management app, and a database — heavier than a single binary.
Caddy: The Config File Sweet Spot
What It Is
Caddy is a modern web server written in Go that treats HTTPS as the default, not an add-on. Its config language (the Caddyfile) is remarkably concise — a reverse proxy rule is two lines.
Setup
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
healthcheck:
test: ["CMD", "caddy", "validate", "--config", "/etc/caddy/Caddyfile"]
interval: 30s
timeout: 10s
volumes:
caddy_data:
caddy_config:
Adding a Service
Edit your Caddyfile:
Reload without downtime:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
HTTPS certificates are automatic. No extra config. No certbot. No cron jobs.
Strengths
- Simplest config syntax. Two lines per service. Compare that to Nginx’s 15-line server blocks.
- Automatic HTTPS by default. Every site gets a certificate. You have to explicitly opt out of HTTPS.
- HTTP/3 out of the box. QUIC support is enabled by default — no experimental flags.
- Single binary, low memory. ~20-40 MB RAM. No database, no extra containers.
- Hot reload. Change the Caddyfile and reload without dropping connections.
- JSON API. Full programmatic control via REST API for automation.
Weaknesses
- No Docker auto-discovery. You must manually add each service to the Caddyfile. Community plugins like
caddy-docker-proxyexist but add complexity. - No built-in dashboard. You’re managing everything through text files.
- Smaller plugin ecosystem. Nginx and Traefik have larger communities and more middleware options.
- Less familiar. If your team knows Nginx config syntax, Caddy’s format is one more thing to learn.
Traefik: The Docker-Native Powerhouse
What It Is
Traefik is a cloud-native reverse proxy that automatically discovers services from Docker, Kubernetes, and other orchestrators. You configure routing through Docker labels on your containers — Traefik watches the Docker socket and creates routes in real time.
Setup
services:
traefik:
image: traefik:v3
container_name: traefik
restart: unless-stopped
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "[email protected]"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
healthcheck:
test: ["CMD", "traefik", "healthcheck"]
interval: 30s
timeout: 10s
volumes:
traefik_letsencrypt:
Adding a Service
Add labels to your container’s docker-compose.yml:
services:
jellyfin:
image: jellyfin/jellyfin:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyfin.rule=Host(`jellyfin.yourdomain.com`)"
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
That’s it. Restart the container, Traefik detects it, creates the route, and provisions an SSL certificate. No central config file to update.
Strengths
- True auto-discovery. New container with the right labels? Traefik picks it up automatically. Remove the container? Route disappears. This is transformative at scale.
- Dashboard. Real-time view of all routers, services, middlewares, and their health status.
- Middleware ecosystem. Rate limiting, IP whitelisting, basic auth, header manipulation, circuit breakers, retry logic — all composable and stackable.
- Multi-provider support. Docker, Kubernetes, Consul, file-based, ECS — Traefik integrates with everything.
- Canary deployments and load balancing. Weighted routing, sticky sessions, health checks — production-grade features if you need them.
Weaknesses
- Steep learning curve. Routers, services, middlewares, entrypoints, providers, resolvers — the concept model is deep. Expect to spend a few hours reading docs before your first successful deploy.
- Verbose labels. Four labels per service minimum. At 30 containers, that’s a lot of label management spread across many compose files.
- Docker socket access. Traefik needs read access to the Docker socket, which is a security consideration. Mitigate with a Docker socket proxy.
- Higher memory. ~50-80 MB base, growing with the number of routes and middleware chains.
- Debugging is harder. When something doesn’t route correctly, the debug output is verbose and the error messages aren’t always clear.
Head-to-Head: Real Scenarios
Scenario 1: “I just started self-hosting with 3-5 services”
Winner: Caddy. Write a 10-line Caddyfile and forget about it. NPM also works great here, but you’re learning a UI that you’ll outgrow. Caddy scales naturally as you add services.
Scenario 2: “I run 20+ containers and add new ones weekly”
Winner: Traefik. Auto-discovery eliminates the manual step of updating a central config every time you spin up a new container. The upfront investment in learning Traefik pays back quickly at this scale.
Scenario 3: “I want my non-technical family member to manage proxy hosts”
Winner: Nginx Proxy Manager. The web UI is genuinely intuitive. Point-and-click SSL certificates, visual host management, and no terminal required.
Scenario 4: “Performance and resource usage matter”
Winner: Caddy. Lowest memory footprint, HTTP/3 by default, single binary. Traefik is close behind. NPM’s multi-container architecture is the heaviest.
Scenario 5: “I’m running Kubernetes”
Winner: Traefik. It’s the default ingress controller in k3s for a reason. Native Kubernetes Ingress and IngressRoute support. Caddy has community K8s integrations, but Traefik is the established choice here.
Scenario 6: “I need advanced middleware (rate limiting, auth, headers)”
Winner: Traefik. The middleware system is composable and powerful. Caddy handles common cases well but Traefik’s middleware library is deeper. NPM supports custom Nginx directives, but you’re back to writing config files at that point.
Performance Comparison
For most self-hosters, all three handle traffic without breaking a sweat — you’re rarely pushing more than a few hundred concurrent connections at home.
That said, the differences matter more for:
- Latency-sensitive services (game streaming, real-time apps): Caddy’s HTTP/3 and lightweight architecture give it a slight edge.
- Many concurrent routes (50+): Traefik’s dynamic routing handles large service counts without config reload delays.
- Resource-constrained hosts (Raspberry Pi, small VPS): Caddy’s 20-40 MB footprint leaves more room for your actual services.
Security Considerations
All three support automatic HTTPS, but their security postures differ:
| Aspect | Traefik | Caddy | NPM |
|---|---|---|---|
| Docker socket access | Required (use socket proxy) | Not needed | Not needed |
| Default HTTPS | Opt-in via labels | On by default | Per-host toggle |
| Security headers | Middleware config | Caddyfile directives | Custom Nginx config |
| Auth middleware | Built-in (BasicAuth, ForwardAuth) | Built-in (basic_auth) | Access lists + basic auth |
| Vulnerability surface | Go binary | Go binary | Node.js + Nginx + SQLite |
Key takeaway: Caddy’s “secure by default” philosophy means you’re less likely to accidentally expose an unencrypted service. Traefik’s Docker socket requirement is a known attack vector — always use a socket proxy in production.
Migration Tips
Already using one and thinking about switching?
From NPM to Caddy
Export your proxy host list from the NPM database, then create a Caddyfile entry for each host. The mapping is straightforward: domain → reverse_proxy container:port.
From NPM to Traefik
Add Traefik labels to each container’s compose file. The biggest change is shifting from centralized config to distributed labels. Do it one service at a time — run both in parallel during migration.
From Caddy to Traefik
Convert each Caddyfile block into Docker labels. You’ll gain auto-discovery but lose the simplicity of the Caddyfile. Worth it if you’re regularly spinning up new services.
From Traefik to Caddy
Strip labels from your compose files and create corresponding Caddyfile entries. The config will be dramatically shorter. Consider this if you found Traefik’s complexity isn’t justified by your scale.
Which One Should You Pick?
Stop overthinking it. Here’s the decision tree:
- Are you brand new to self-hosting and want a GUI? → Nginx Proxy Manager
- Do you prefer config files and want minimal complexity? → Caddy
- Do you run many containers with frequent changes? → Traefik
- Are you on Kubernetes? → Traefik
- Do you just want something that works with the least effort? → Caddy
All three are production-capable. All three handle HTTPS automatically. The “wrong” choice still works — you’ll just be slightly annoyed by the things your proxy doesn’t do well.
Pick one, deploy it, and move on to the services that actually matter. You can always switch later.
Further Reading
- Running Caddy Server: The Zero-Config HTTPS Reverse Proxy — our deep-dive Caddy setup guide
- Traefik Reverse Proxy: Complete Setup Guide — full Traefik walkthrough with Docker
- Setting Up Nginx Proxy Manager — beginner’s guide to NPM
- The Complete Self-Hosting Security Stack — securing your services behind the proxy
- Running Authentik vs Authelia: SSO Comparison 2026 — authentication middleware for any of these proxies