Caddy and nginx — A Comparison
Caddy and nginx — A Comparison
Reverse proxy, HTTPS termination, and static file serving show up in nearly every web service. nginx has been the long-standing standard, and Caddy carved out its place by emphasizing automatic HTTPS and simple configuration. This piece covers the origins of both tools, what automatic HTTPS means, syntax differences, neighboring tools (traefik, HAProxy), and the distinction between reverse proxy and load balancer.
1. About the Two Tools
| Tool | First release | Note |
|---|---|---|
| nginx | 2004, Igor Sysoev | Written in C. Event-driven. Acquired by F5 in 2019. |
| HAProxy | 2001 (1.0), Willy Tarreau | Written in C. Started as an L4 / L7 load balancer. |
| Caddy | 2015, Matt Holt | Written in Go. Major redesign in v2 (2020). Automatic HTTPS as a first-class feature. |
| traefik | 2015, Containous (now Traefik Labs) | Written in Go. Dynamic configuration based on container labels and service discovery. |
The foundation of automatic HTTPS — the ACME protocol (RFC 8555, 2019) and Let's Encrypt (in production from 2016). Before 2016, certificate issuance and renewal were manual.
2. Caddy's Automatic HTTPS
Just write a domain in the Caddyfile and Caddy issues and renews the certificate as an ACME client. It auto-handles HTTP-01 or TLS-ALPN-01 challenges.
example.com {
reverse_proxy 127.0.0.1:8080
}
This one line handles 80 → 443 redirects, certificate issuance, the renewal schedule, and HTTPS termination. Doing the same with nginx requires certbot, a renewal cron, and two server blocks (80 and 443).
3. Caddyfile vs nginx.conf
# Caddyfile
api.example.com {
reverse_proxy /v1/* 127.0.0.1:8080
encode zstd gzip
log
}
# nginx.conf
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;
location /v1/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
gzip on;
access_log /var/log/nginx/access.log;
}
nginx allows more explicit and fine-grained control. Caddy comes with strong sensible defaults (Host header, X-Forwarded-For auto-set, and so on).
4. nginx's Strengths
- A huge pool of operations cases and tutorials.
- Very fine-grained modules and directives.
- A wide range of third-party modules (extensions like OpenResty).
- Powerful L7 caching (
proxy_cache).
5. Caddy's Strengths
- Automatic HTTPS as the standard.
- Single binary (Go), simple to start.
- Dynamic configuration through a JSON API.
- Module system (plug-ins) that adds features at build time.
6. traefik's place
In container environments (Docker / Compose, Kubernetes) it watches labels and resources to auto-update routes. Discovery-based configuration rather than static files. The common contrast — Caddy for simple proxying on a single host, traefik for dynamic routing in container environments.
7. Reverse Proxy vs Load Balancer
| Concept | Meaning |
|---|---|
| Reverse proxy | Receives client requests, forwards to internal servers, and returns responses. Caching, compression, SSL termination. |
| Load balancer | Distributes requests across multiple backends. L4 (TCP) or L7 (HTTP). |
Reverse proxies usually do load balancing too. HAProxy started as a load balancer; nginx and Caddy started as reverse proxies and cover both. Managed cloud services like ELB / ALB / NLB occupy the same place.
8. Caddy Single-host Operation Example
{
email admin@example.com
}
example.com {
reverse_proxy 127.0.0.1:3000
}
api.example.com {
reverse_proxy 127.0.0.1:8080
@ratelimit {
path /login
}
rate_limit @ratelimit 5r/m # plugin required
}
static.example.com {
root * /var/www/static
file_server
encode zstd gzip
}
9. nginx Single-host Operation Example
Issue and renew certificates with certbot:
# Linux
sudo certbot --nginx -d example.com
# auto-renew via systemd timer or cron
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
10. Common Pitfalls
Reachability on port 80 — ACME HTTP-01 challenges have to land on 80. Make sure firewalls and cloud security groups open both 80 and 443.
Rate limits and ACME quotas — Let's Encrypt has per-domain issuance limits. Registering wildcard certs or unrelated domains carelessly can trigger blocks.
X-Forwarded-For trust — if the application behind the reverse proxy doesn't trust the header, it sees the proxy IP as the client IP. Caddy sets it automatically; nginx needs explicit configuration.
The slash difference in proxy_pass (nginx) — proxy_pass http://up; and proxy_pass http://up/; differ in URL composition. A frequent cause of production incidents.
HTTP/2 and WebSocket — header forwarding and upgrade handling differ in some environments. Both proxies allow explicit configuration.
Closing thoughts
For small-to-mid single-host operations, Caddy's automatic HTTPS plus sensible defaults provide the biggest value. Unless you genuinely need nginx's deep tuning options (large-scale caching, OpenResty Lua), the path that ends in one Caddyfile line wins on operational cost. That said, nginx's vast pool of operations cases is a separate value of its own.
Next
- loopback-ssh-tunnel
- single-server-philosophy
Refer to Caddy, Caddy on GitHub, nginx, traefik, HAProxy, Let's Encrypt, and RFC 8555 ACME.