Introduction
Choosing a reverse proxy is one of those decisions that seems trivial until you're debugging TLS certificate renewals at 2 AM or trying to figure out why your service discovery isn't propagating. If you're running microservices, your reverse proxy is the front door to everything - and picking the wrong one can cost you weeks of configuration headaches.
Here, we'll do a hands-on comparison of the three most popular reverse proxies in the modern DevOps stack: Nginx, Traefik, and Caddy. We'll look at configuration complexity, automatic TLS, service discovery, performance characteristics, and when each one makes sense.
No fluff. Just real configs, real trade-offs, and a clear recommendation based on your use case.
The Contenders at a Glance
Before we dive in, here's a quick snapshot:
| Feature | Nginx | Traefik | Caddy |
|---|---|---|---|
| Language | C | Go | Go |
| Config Format | Custom DSL | YAML/TOML/Labels | Caddyfile/JSON |
| Auto TLS | Via Certbot | Built-in (Let's Encrypt) | Built-in (Let's Encrypt + ZeroSSL) |
| Service Discovery | Manual/scripted | Native (Docker, K8s, Consul) | Limited (on_demand_tls) |
| Hot Reload | nginx -s reload |
Automatic | Automatic |
| Dashboard | Nginx Plus only | Built-in | Via API |
| Memory Footprint | ~2-5 MB | ~30-50 MB | ~20-40 MB |
Nginx has been the default choice for over a decade. Traefik was built specifically for dynamic microservice environments. Caddy focuses on simplicity and security-by-default.
Configuration Complexity
This is where the differences hit you first. Let's set up a simple reverse proxy with TLS for two services: an API on port 3000 and a frontend on port 8080.
Nginx:
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
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;
}
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Plus you need Certbot installed, cron jobs for renewal, and you'll reload Nginx manually after any change.
Traefik (docker-compose labels):
services:
traefik:
image: traefik:v3.1
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme:/acme
api:
image: my-api:latest
labels:
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.services.api.loadbalancer.server.port=3000"
frontend:
image: my-frontend:latest
labels:
- "traefik.http.routers.frontend.rule=Host(`app.example.com`)"
- "traefik.http.routers.frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.frontend.loadbalancer.server.port=8080"
Caddy (Caddyfile):
api.example.com {
reverse_proxy localhost:3000
}
app.example.com {
reverse_proxy localhost:8080
}
That's it. Caddy automatically provisions TLS certificates, redirects HTTP to HTTPS, and enables HTTP/2. Two lines per service.
Automatic TLS and Certificate Management
This is Caddy's killer feature and Traefik's strong suit. With Nginx, you're on your own.
Caddy obtains certificates from Let's Encrypt and ZeroSSL automatically. It handles renewal, OCSP stapling, and even falls back to a secondary CA if the primary is down. You literally do nothing - just put a domain name in the Caddyfile and it works.
Traefik supports Let's Encrypt via ACME with HTTP-01 and DNS-01 challenges. DNS-01 is crucial if you're behind a load balancer or need wildcard certificates. Configuration looks like:
certificatesResolvers:
letsencrypt:
acme:
email: ops@example.com
storage: /acme/acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
Nginx requires Certbot or a similar ACME client as a separate process. You need to configure it, set up renewal hooks, and make sure the reload happens after certificate updates. It works, but it's another moving part to maintain.
Verdict: If automatic TLS matters to you (it should), Caddy wins on simplicity and Traefik wins on flexibility. Nginx is manageable but requires more operational overhead.
Service Discovery and Dynamic Configuration
This is where Traefik pulls ahead for container-heavy environments.
Traefik natively watches Docker labels, Kubernetes Ingress resources, Consul catalogs, etcd, and more. When a new container spins up with the right labels, Traefik picks it up immediately - no restart, no reload, no config file change. This is incredibly powerful for dynamic environments where services scale up and down frequently.
Caddy has an API for dynamic configuration but doesn't natively integrate with container orchestrators the way Traefik does. You can use caddy-docker-proxy (community plugin) to get Docker label support, but it's not first-party.
Nginx is static by default. You change a file, you reload. Tools like consul-template or nginx-proxy (jwilder) exist to generate configs dynamically, but they're external tools bolted on.
For Kubernetes specifically, all three have Ingress controller implementations, but Traefik's is the most mature for dynamic environments and ships as the default ingress controller in K3s.
Performance Benchmarks
Raw performance matters less than you think for most workloads, but here are realistic numbers from common benchmarking setups:
Requests per second (simple HTTP proxy, wrk with 100 connections):
| Proxy | RPS (HTTP) | RPS (HTTPS) | p99 Latency |
|---|---|---|---|
| Nginx | ~45,000 | ~32,000 | 2.1 ms |
| Traefik | ~28,000 | ~22,000 | 3.8 ms |
| Caddy | ~30,000 | ~24,000 | 3.2 ms |
Nginx is faster, period. It's written in C with an event-driven architecture that has been optimized for 20+ years. If you're proxying tens of thousands of requests per second and every millisecond counts, Nginx is the right choice.
But for most applications - handling hundreds to low thousands of RPS - the difference between 3ms and 2ms latency at p99 is irrelevant. You'll hit bottlenecks in your application code, database, or network long before the proxy becomes the issue.
Middleware and Advanced Features
All three support common middleware patterns, but the implementation differs.
Rate limiting:
# Nginx
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
}
}
# Traefik (middleware via labels)
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=10"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=20"
- "traefik.http.routers.api.middlewares=ratelimit"
# Caddy
api.example.com {
rate_limit {remote.ip} 10r/s
reverse_proxy localhost:3000
}
Authentication, compression, headers, CORS - all three handle these well. Traefik has the concept of middleware chains that you can compose and reuse across routers, which is elegant for complex setups. Nginx has the most extensive third-party module ecosystem. Caddy has a growing plugin system but fewer options overall.
When to Use Each
Choose Nginx when:
- You need maximum raw performance (high-traffic APIs, CDN edge nodes)
- You have a static infrastructure that doesn't change often
- Your team already knows Nginx configuration well
- You need advanced features like TCP/UDP proxying, custom Lua scripting, or GeoIP
- You're running on resource-constrained hardware
Choose Traefik when:
- You're running Docker or Kubernetes with frequently changing services
- You want automatic service discovery without config file management
- You need the built-in dashboard for visibility into routing
- You're using multiple orchestration platforms (Docker + Consul, for example)
- Your microservices scale up and down dynamically
Choose Caddy when:
- You want the simplest possible setup with automatic HTTPS
- You're a small team that doesn't want to maintain proxy configuration
- You're running a handful of services that don't change often
- You want a single binary with no external dependencies
- You value security defaults (HTTPS everywhere, modern TLS settings out of the box)
Migration Tips
If you're switching from Nginx to Traefik or Caddy, here's what to watch for:
Upstream health checks - Nginx does passive health checks by default; Traefik and Caddy have active health check options that behave differently. Test failover behavior.
Websocket proxying - Nginx needs explicit
proxy_set_header UpgradeandConnectionheaders. Caddy and Traefik handle websockets transparently.Custom error pages - Each proxy handles error responses differently. Test your 502/503 pages after migration.
Logging format - If you're parsing access logs with Loki/ELK, your log format will change. Update your parsers before switching.
Certificate storage - If migrating to Traefik or Caddy, your existing Let's Encrypt certificates from Certbot won't transfer. The new proxy will obtain fresh certificates automatically, but be aware of Let's Encrypt rate limits (50 certificates per registered domain per week).
Need Help with Your DevOps?
Setting up and maintaining reverse proxies, load balancers, and TLS infrastructure is just one piece of the DevOps puzzle. At InstaDevOps, we handle your entire infrastructure - from proxy configuration to CI/CD pipelines, monitoring, and Kubernetes management - starting at $2,999/month.
Book a free 15-minute consultation to discuss your infrastructure needs: https://calendly.com/instadevops/15min
Top comments (0)