Traefik vs Nginx vs Caddy: Which Reverse Proxy for Self-Hosting in 2026?
TL;DR
All three are solid open source reverse proxies — the choice comes down to your use case. Caddy wins for simplicity (automatic HTTPS in one line, 70K stars). Nginx wins for raw performance (8,450 req/s, 15–60MB RAM, 34% of the web). Traefik wins for Docker-heavy environments (automatic service discovery via container labels, no config file needed). For most self-hosters starting out: use Caddy. For production VPS with known services: use Nginx. For dynamic Docker/Kubernetes setups: use Traefik.
Key Takeaways
- Nginx: Fastest (8,450 req/s), lightest (15–60MB RAM), steepest learning curve — manual HTTPS setup required
- Caddy: Easiest (automatic HTTPS, 3-line config), 70.6K GitHub stars, 40MB baseline RAM — ideal for beginners and home labs
- Traefik: Best Docker integration (auto-discovery via labels), 62.1K stars, 50–200MB RAM — designed for microservices and dynamic environments
- Performance order: Nginx > Caddy > Traefik for raw throughput
- Simplicity order: Caddy > Traefik > Nginx for setup time
Quick Comparison
| Feature | Nginx | Caddy | Traefik |
|---|---|---|---|
| GitHub Stars | 29.6K (official) | ~70.6K | ~62.1K |
| Latest Version | 1.28.2 | v2.11.1 | v3.6.10 |
| Automatic HTTPS | ❌ (manual certbot) | ✅ Zero-config | ✅ Via ACME |
| Docker auto-discovery | ❌ | ❌ (plugin avail.) | ✅ Native |
| Config format | nginx.conf | Caddyfile / JSON | YAML/TOML + labels |
| HTTPS req/s | 8,450 | 7,920 | 7,340 |
| Memory baseline | 15–60MB | 40MB | 50–200MB |
| Learning curve | Steep | Gentle | Moderate |
| Market share | 34% of all websites | Growing | Growing (cloud-native) |
Nginx: The Performance Champion
Nginx has been the web's workhorse since 2004. It powers 34% of all websites on earth — more than any other server. With 20+ years of production hardening, it's the default choice when you prioritize performance, stability, and resource efficiency.
What Makes Nginx Great
Raw performance. Nginx handles 8,450 HTTPS requests/second at 42% CPU in benchmarks — the highest throughput of any mainstream reverse proxy. Its event-driven, async architecture handles thousands of concurrent connections on minimal hardware.
Smallest memory footprint. At 15–60MB baseline, Nginx is the only choice for servers with less than 512MB RAM. A busy proxy handling tens of millions of requests per day has been observed using under 15MB RAM and 10% CPU.
Proven stability. Nginx 1.28.2 (the current stable release as of March 2026) includes a TLS security fix and QUIC performance improvements. The stable branch is conservatively maintained — it just works.
Massive ecosystem. 20 years of documentation, tutorials, StackOverflow answers, and community modules. Whatever problem you have, someone has solved it.
Basic Nginx Reverse Proxy Config
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name app.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name app.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# SSL via certbot:
certbot --nginx -d app.yourdomain.com
Docker Compose with Nginx
services:
nginx:
image: nginx:1.28
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
restart: unless-stopped
Tip: Nginx Proxy Manager adds a web UI on top of Nginx — a great choice if you want Nginx's performance without touching nginx.conf directly.
Nginx Limitations
- Manual HTTPS: You must configure Let's Encrypt certificates yourself (certbot or manual). No automatic renewal out of the box.
- No dynamic discovery: Adding a new Docker service requires editing the config and reloading. No labels, no auto-detection.
- Learning curve: nginx.conf syntax isn't hard, but it's dense. Server blocks, location blocks, proxy headers — there's a right way to do each.
Caddy: The Beginner's Best Friend
Caddy reimagined what a web server should be. Its headline feature is automatic HTTPS — get a Let's Encrypt certificate with zero configuration. It also has the most concise config language of any reverse proxy: a config that takes 50 lines in nginx.conf becomes 3 lines in a Caddyfile.
What Makes Caddy Great
Automatic HTTPS with zero config. Caddy automatically requests and renews Let's Encrypt certificates for any domain you configure. No certbot, no cron jobs, no renewal scripts. It just handles it.
Caddyfile simplicity. The entire reverse proxy config for one site:
app.yourdomain.com {
reverse_proxy localhost:3000
}
That's it. HTTPS is automatic. Headers are set correctly. Logs are enabled. Caddy handles the rest.
HTTP/3 and modern protocols. Caddy has native HTTP/3 (QUIC) support out of the box — nginx requires manual configuration for this.
70.6K GitHub stars — the highest of any of the three, indicating massive community interest and adoption in modern projects.
Caddy Docker Compose
services:
caddy:
image: caddy:latest
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data # Certs stored here
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
# Caddyfile — proxy three apps, all with automatic HTTPS:
api.yourdomain.com {
reverse_proxy api:3000
}
app.yourdomain.com {
reverse_proxy frontend:8080
}
docs.yourdomain.com {
reverse_proxy docs:4000
}
Caddy with Docker Labels (via Plugin)
If you want Docker auto-discovery like Traefik, the caddy-docker-proxy plugin adds label-based routing:
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:latest
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
myapp:
image: myapp:latest
labels:
caddy: app.yourdomain.com
caddy.reverse_proxy: "{{upstreams 3000}}"
Caddy Limitations
- Smaller ecosystem than Nginx — fewer third-party modules, less community content
- Higher memory under load — can use 2–3× more CPU/RAM than Nginx under sustained high traffic
- Performance ceiling — 7,920 req/s vs Nginx's 8,450; fine for most self-hosters, but Nginx is faster
- Less battle-tested at extremely high scale (though v2.11 is production-ready for normal workloads)
Traefik: The Docker-Native Reverse Proxy
Traefik was built for modern containerized environments. Its killer feature is automatic service discovery — you add labels to your Docker containers and Traefik automatically routes traffic to them without editing any config file. Add a new service, update a label, Traefik picks it up instantly.
What Makes Traefik Great
Zero-touch Docker integration. No config file changes when services change. Traefik watches Docker events and reconfigures routing on the fly:
services:
myapp:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
That's the entire routing config. Traefik picks it up automatically.
Web dashboard. Traefik provides a real-time dashboard at port 8080 showing all routes, services, and middlewares. Useful for debugging and visibility.
Middleware ecosystem. Rate limiting, IP allowlisting, basic auth, Fail2Ban integration, Cloudflare real IP — all available as Traefik middlewares that apply via labels.
Native Kubernetes. Traefik is widely used as a Kubernetes Ingress controller — the same tool scales from home lab to production cluster.
Traefik Docker Compose
services:
traefik:
image: traefik:v3.6
command:
- "--api.insecure=true" # Dashboard (don't use in production without auth)
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=you@yourdomain.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
- "8080:8080" # Dashboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_certs:/letsencrypt
restart: unless-stopped
volumes:
traefik_certs:
Now any container with Traefik labels gets automatic HTTPS:
myapp:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
HTTP → HTTPS Redirect Middleware
traefik:
command:
# ... other flags ...
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
Rate Limiting + Basic Auth Middleware
labels:
- "traefik.http.middlewares.auth.basicauth.users=user:$$apr1$$..."
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.routers.myapp.middlewares=auth,ratelimit"
Traefik Limitations
- Highest resource usage: 50–200MB RAM (vs Nginx's 15–60MB) — overhead from continuous service discovery
- Slowest throughput: 7,340 req/s vs Nginx's 8,450 — significant difference under high load
- Complexity for simple setups: If you have 3 static services, Traefik's label-based config is overkill
- Docker socket access: Requires
/var/run/docker.sockmounted — this is a security consideration
Performance Benchmarks
Benchmark data from community testing (HTTPS reverse proxy, typical workloads):
| Proxy | HTTPS req/s | Latency (avg) | CPU usage | Memory |
|---|---|---|---|---|
| Nginx 1.28 | 8,450 | 11.83ms | 42% | 15–60MB |
| Caddy v2.11 | 7,920 | 12.63ms | 48% | 40MB |
| Traefik v3.6 | 7,340 | 13.62ms | 53% | 50–200MB |
TLS handshakes/second (important for high connection-rate workloads):
| Proxy | TLS handshakes/s |
|---|---|
| Nginx | 8,000 |
| Caddy | 6,500 |
| Traefik | 5,000 |
Bottom line: All three handle more than enough for typical self-hosted workloads. The differences matter at scale — 10,000+ concurrent users — not for a team of 50 or a home lab.
Decision Guide
Choose Nginx if:
→ Performance is critical (APIs, high-traffic sites)
→ Resource-constrained VPS (<1GB RAM)
→ You have a static set of services that rarely changes
→ You want maximum ecosystem and documentation
→ You're comfortable with config files
Choose Caddy if:
→ You want automatic HTTPS with zero manual cert management
→ You're new to reverse proxies (gentlest learning curve)
→ Your services are mostly static/known upfront
→ Home lab or personal projects
→ Config simplicity matters more than raw performance
Choose Traefik if:
→ You run 10+ Docker containers that change frequently
→ You want infrastructure-as-code via Docker labels
→ You're building toward Kubernetes
→ You want a web dashboard for visibility
→ Automatic service discovery matters more than performance
Use Nginx Proxy Manager if:
→ You want Nginx's performance + a web UI (no config files)
→ Beginners who don't want to touch the command line at all
→ Great middle ground: performance + usability
Config Size Comparison
The same setup (HTTPS reverse proxy for one app) in all three:
Nginx (~25 lines):
server {
listen 80;
server_name app.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name app.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Caddy (2 lines):
app.yourdomain.com {
reverse_proxy localhost:3000
}
Traefik (Docker labels, 6 label lines + setup):
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
The Caddyfile wins on simplicity. Traefik labels win for dynamic Docker environments. Nginx wins for explicit control.
Methodology
- GitHub stars: Verified March 2026
- Benchmark data: Community benchmarks (Traefik Forums, blog.tjll.net, ZeonEdge.com — 2025–2026)
- Version data: Official release pages and GitHub releases
- Market share: W3Techs Web Technology Survey (March 2026)
See all open source server and infrastructure tools at OSSAlt.com/categories/infrastructure.