Your service is running. Now you need something in front of it: TLS termination, routing, rate limiting, maybe auth headers. Three tools come up in every conversation — Nginx, Caddy, and Cloudflare. They solve the same surface-level problem but with very different tradeoffs. Picking the wrong one costs you either operational complexity or flexibility you'll need in six months.
What we're actually comparing
These tools aren't equivalents. Nginx and Caddy are software reverse proxies you deploy and operate yourself. Cloudflare is a managed edge network — your traffic goes through their infrastructure before reaching your origin. That distinction matters for latency, security posture, and what you can control.
| Nginx | Caddy | Cloudflare | |
|---|---|---|---|
| Hosting | Self-hosted | Self-hosted | Managed (SaaS) |
| TLS | Manual (certbot) | Automatic | Automatic |
| Config format | Custom DSL | Caddyfile or JSON | Dashboard / Terraform |
| Performance ceiling | Very high | High | Depends on plan |
| Pricing | Free | Free (BSL license) | Free to expensive |
Let's go through each with real configuration examples.
Nginx: the workhorse that never surprises you
Nginx has been the default reverse proxy for over a decade. It's fast, well-documented, and every DevOps engineer knows how to debug it. The configuration syntax is verbose but explicit — which is either a feature or a flaw depending on your tolerance for ceremony.
A typical Nginx config for proxying a Go or Python backend with HTTPS:
server {
listen 80;
server_name api.example.com;
return 301 https://$host$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://127.0.0.1:8080;
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;
proxy_read_timeout 30s;
proxy_buffering off;
}
}
You'll still need certbot and a systemd timer for certificate renewal. Add nginx_status and scrape it with Prometheus for metrics. Add limit_req_zone for rate limiting. Everything is possible but nothing is automatic.
Where Nginx wins: high-traffic production systems where you need precise control, environments where your team already operates it, and anywhere institutional knowledge matters more than convenience.
Where it hurts: new projects where you don't want to think about certs, or any setup where ops overhead isn't justified by the traffic.
Caddy: automatic HTTPS and a sane config format
Caddy's main selling point is automatic TLS via ACME (Let's Encrypt by default). Zero-touch certificate issuance and renewal. That alone eliminates a common failure mode — the cert that expires because the renewal cron job was silently broken for two weeks.
The equivalent reverse proxy setup in a Caddyfile:
api.example.com {
reverse_proxy localhost:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
}
log {
output file /var/log/caddy/access.log
format json
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
X-Frame-Options DENY
-Server
}
}
No certbot. No cron. No ssl_certificate path to manage. Caddy handles OCSP stapling, automatic HTTP→HTTPS redirects, and certificate renewal out of the box. The security headers block is also zero-cost to add — no separate module needed.
Caddy also exposes a JSON API for dynamic config updates, which is useful if you're building a control plane on top of it. The Caddyfile compiles to JSON internally, so both interfaces are first-class.
One thing to know: Caddy changed its license from Apache 2.0 to the BSL (Business Source License) in 2024. Free for most use cases, but read the terms if you're distributing it as part of a commercial product.
Where Caddy wins: developer-operated production systems, small teams, projects where TLS lifecycle management shouldn't be anyone's job, and situations where you want security headers with zero boilerplate.
Where it hurts: very high throughput (Nginx's event loop still has an edge at extreme load), or organizations with compliance requirements around third-party license review.
Cloudflare: when the edge is the product
Cloudflare isn't a reverse proxy in the traditional sense — it's a CDN/WAF/edge network that can act like one. Your DNS points to Cloudflare, and they proxy requests to your origin. The security features are substantial: DDoS protection, bot management, WAF rules, and certificate management happen at their edge, not on your server.
For many teams, this changes the security conversation. Instead of hardening your origin's surface area yourself, you're managing Cloudflare rules. The tradeoff is dependency on their availability and pricing, and your traffic passes through their infrastructure — which is fine until it isn't, or until your compliance team asks where TLS terminates.
The free tier covers most small production workloads. But meaningful features — Advanced Bot Protection, custom WAF rules at scale, Argo routing — require paid plans that add up quickly.
If you're using Cloudflare, lock your origin to accept connections only from Cloudflare's published IP ranges, and enable authenticated origin pull certificates. Cloudflare being in the path doesn't replace origin hardening — it just moves the perimeter. For a practical checklist of what to configure on both layers, see our security hardening checklists.
Where Cloudflare wins: global audiences needing CDN, teams without dedicated ops, or anyone who needs DDoS mitigation without running their own infrastructure.
Where it hurts: latency-sensitive applications where an extra network hop matters, regulated environments where data sovereignty means traffic can't traverse a third-party network, or teams that need full observability into raw request data at the edge.
The decision matrix
Don't choose based on what's familiar. Choose based on what breaks in your specific failure mode:
| Scenario | Pick |
|---|---|
| Small to medium production, team of 1–5 | Caddy |
| High-traffic, ops team present | Nginx |
| Global audience, no ops overhead | Cloudflare |
| mTLS between internal services | Nginx or Caddy |
| WAF + DDoS without dedicated infra | Cloudflare |
| Compliance requires self-hosted TLS termination | Nginx or Caddy |
| Side project or internal tool | Caddy |
| Already running Nginx in prod | Keep Nginx |
There's rarely a wrong answer, only a wrong context. The mistake teams make is choosing based on habit — using Nginx because "everyone uses Nginx" or defaulting to Cloudflare because the free tier is convenient.
The takeaway
In 2026, Caddy has earned its place as the default for most developer-operated production systems. Automatic TLS, a clean config format, sensible defaults for security headers, and enough performance for the vast majority of workloads. Nginx is still the right call when you need maximum control, extreme throughput, or have a team already operating it. Cloudflare belongs in your stack when you need global edge features and are comfortable with the external dependency and pricing model.
The right question isn't "which is best" — it's "which failure mode am I most willing to own."
I run AYI NEDJIMI Consultants, a cybersecurity consulting firm. We publish free security hardening checklists — PDF and Excel.
Top comments (0)