A practical, production‑ready comparison of Nginx and Caddy for use as an application gateway / reverse proxy: TLS automation, HTTP/3, performance, caching, observability, and secure defaults—plus copy‑paste configs for Node, Python, PHP, and more.
✍️ Short Summary
If you want the easiest automatic HTTPS and HTTP/3 out of the box with clean configs, choose Caddy. If you need battle‑tested performance, built‑in reverse‑proxy caching, and fine‑grained modules (rate limits, connection limits, geo/IP controls) on every distro, choose Nginx. Both excel as secure app gateways for APIs, SSR apps, and static sites.
📎 Table of Contents
-
Quick Answer: When to Pick Which
-
Feature Comparison Matrix
-
Architecture Basics
-
Install & First Reverse Proxy
-
TLS & Certificates
-
HTTP/2, HTTP/3, WebSockets, gRPC
-
Performance & Caching
-
Security Hardening Checklist
-
Cloudflare / CDN Integration
-
Logging & Observability
-
Migration Cheat‑Sheet (Nginx ⇄ Caddy)
-
Troubleshooting Tips
-
✅ Conclusion / Next Steps
-
🔗 Related Articles
1) Quick Answer: When to Pick Which
Situation / Requirement | Choose Nginx | Choose Caddy |
---|---|---|
Zero‑touch HTTPS for many sites | ◻️ Works (with Certbot) | ✅ Automatic by default |
HTTP/3 (QUIC) today | ◻️ Available in newer builds; extra config may be needed | ✅ On by default in most builds |
Reverse‑proxy caching built‑in | ✅ proxy_cache is mature | ◻️ Use a plugin or CDN (not core) |
Fine‑grained rate/conn limits | ✅ limit_req / limit_conn | ◻️ Plugins or CDN |
Very low memory footprint | ✅ Extremely lean | ◻️ Slightly higher (Go runtime), still modest |
Simplest config syntax | ◻️ Verbose but powerful | ✅ Human‑friendly Caddyfile |
Enterprise ubiquity & docs | ✅ Long‑standing standard | ◻️ Growing fast, great docs |
Dynamic/on‑the‑fly config | ◻️ nginx -s reload (no drop) |
✅ Admin API; live reloads |
Rule of thumb:
-
Prefer Caddy if you value auto‑HTTPS + HTTP/3 and concise configs with great defaults.
-
Prefer Nginx if you need built‑in caching, kernel‑level tuning, and broad distro packaging.
2) Feature Comparison Matrix
Capability | Nginx | Caddy |
---|---|---|
Automatic HTTPS | Via Certbot / ACME clients | Built‑in ACME, OCSP stapling, automatic renewals |
HTTP/2 | ✅ | ✅ |
HTTP/3 (QUIC) | ✅ in recent mainline builds; check your distro | ✅ enabled by default in most builds |
Reverse‑proxy cache | ✅ proxy_cache | ◻️ Plugin/edge CDN |
Rate/conn limiting | ✅ limit_req / limit_conn | ◻️ Plugin/edge CDN |
WebSockets | ✅ | ✅ |
gRPC / h2c | ✅ | ✅ (via reverse_proxy with HTTP/2) |
Brotli/gzip | ✅ (zlib; Brotli via module) | ✅ (automatic compression; Brotli support in many builds) |
Config ergonomics | Block/location directives | Caddyfile (simple) or JSON API |
Live reload | nginx -s reload |
Auto reload on file change + Admin API |
Real‑IP/CDN headers | realip module | trusted_proxies / header pass‑through |
Observability | Access/error logs, exporters | JSON logs, structured fields, metrics module |
⚠️ Note: Caching and advanced rate‑limits are where Nginx still has the edge without plugins.
3) Architecture Basics
A typical app‑gateway (reverse proxy) sits in front of one or more upstream services (Node.js/PM2, Django/Uvicorn, PHP‑FPM, Rails/Puma, Go, etc.), terminating TLS, handling HTTP/2/3, setting headers, and enforcing security.
Internet ⇄ (CDN/WAF) ⇄ Nginx/Caddy ⇄ App(s) on 127.0.0.1:PORT ⇄ DB/Cache/Queues
-
Put the proxy on the same host as the app for lowest latency.
-
Use Cloudflare or similar when you want global caching, WAF, and DDoS protection.
4) Install & First Reverse Proxy
Nginx (Ubuntu)
# Install
sudo apt update && sudo apt install -y nginx
sudo systemctl enable --now nginx
# Basic vhost for app on :3000 (WebSockets enabled)
sudo tee /etc/nginx/sites-available/app.conf > /dev/null <<'NGX'
server {
listen 80;
server_name example.com www.example.com;
# Cloudflare / proxy aware (optional)
real_ip_header CF-Connecting-IP;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
NGX
sudo ln -sf /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/app.conf
sudo nginx -t && sudo systemctl reload nginx
Caddy (Ubuntu)
# Official script (installs caddy service)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
# Caddyfile (auto HTTPS, HTTP/3)
sudo tee /etc/caddy/Caddyfile > /dev/null <<'CADDY'
example.com, www.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000 {
# WebSockets pass-through is automatic
# Trust local reverse proxies or Cloudflare if used
trusted_proxies private_ranges
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote}
}
@health path /healthz
respond @health 200
}
CADDY
sudo systemctl reload caddy
💡 Tip: With Caddy, just point DNS to the server and it will fetch & renew certificates automatically.
5) TLS & Certificates
-
Nginx: use Certbot (ACME) for Let’s Encrypt. For wildcard domains, use DNS‑01 plugins.
sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d example.com -d www.example.com --redirect # Auto-renewal is installed as a systemd timer; verify with: systemctl list-timers | grep certbot
-
Caddy: automatic HTTPS is core. Wildcards via DNS providers are supported in the Caddyfile with a DNS module.
-
OCSP stapling, HSTS, modern ciphers: both servers support hardened TLS; Caddy defaults are very strong; Nginx requires explicit TLS config blocks.
6) HTTP/2, HTTP/3, WebSockets, gRPC
-
HTTP/2: both on by default with TLS.
-
HTTP/3 (QUIC): Caddy enables it by default; Nginx supports it in recent mainline builds—ensure your package includes HTTP/3 and configure
listen 443 quic reuseport;
plus QUIC headers. -
WebSockets: both supported; preserve
Upgrade/Connection
headers in Nginx; Caddy handles automatically. -
gRPC / h2c: both can proxy gRPC; ensure HTTP/2 upstream and proper headers.
7) Performance & Caching
-
Nginx:
-
Tune
worker_processes auto; worker_connections 4096;
based on cores and expected concurrency. -
Built‑in
proxy_cache
to accelerate upstreams:proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m max_size=1g inactive=60m use_temp_path=off; map $request_method $no_cache { default 0; GET 0; HEAD 0; POST 1; } server { … location / { proxy_cache STATIC; proxy_cache_bypass $no_cache; add_header X-Cache $upstream_cache_status; proxy_pass http://127.0.0.1:3000; } }
-
limit_req
&limit_conn
for abuse control.
-
-
Caddy:
-
Excellent throughput and concurrency; minimal tuning needed.
-
For reverse‑proxy caching or rate‑limits, prefer edge CDN or Caddy plugins; otherwise rely on upstream app caches (Redis) and strong CDN policies.
-
8) Security Hardening Checklist
-
◻️ Enforce HTTPS only; redirect HTTP → HTTPS
-
◻️ Set HSTS, X‑Frame‑Options, X‑Content‑Type‑Options, Referrer‑Policy, Permissions‑Policy
-
◻️ Enable Brotli/gzip and reasonable time‑outs
-
◻️ Sanitize/forward X‑Forwarded‑ headers* and preserve real client IP
-
◻️ Apply rate limits (Nginx) or do at the CDN/WAF layer
-
◻️ Keep packages updated; restrict admin panels by IP; disable unused modules
Nginx header example:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()";
Caddy header example:
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
9) Cloudflare / CDN Integration
-
Terminate TLS at the proxy and enable orange‑cloud on DNS for WAF/CDN.
-
Preserve real client IP:
-
Nginx:
real_ip_header CF-Connecting-IP;
+set_real_ip_from
for all CF ranges. -
Caddy: use
trusted_proxies
and forwardX-Forwarded-For/Proto
.
-
-
Send cache‑friendly headers from upstream (or from the proxy) and respect
Cache-Control
.
10) Logging & Observability
-
Nginx:
log_format
(include request ID), ship via Fluent Bit/Vector/Promtail; metrics viastub_status
+ Prometheus exporter. -
Caddy: JSON logs out of the box; integrate with Loki/ELK/Datadog; metrics available via modules or logs → metrics pipelines.
Nginx JSON logs:
log_format json_combined escape=json '{"time":"$time_iso8601","remote":"$remote_addr","host":"$host","method":"$request_method","uri":"$request_uri","status":$status,"size":$bytes_sent,"req_time":$request_time,"upstream":"$upstream_addr"}';
access_log /var/log/nginx/access.json json_combined;
Caddy (log level/site logger):
{
log {
level INFO
}
}
example.com {
log {
output file /var/log/caddy/access.json
format json
}
reverse_proxy 127.0.0.1:3000
}
11) Migration Cheat‑Sheet (Nginx ⇄ Caddy)
Use Case | Nginx Directive | Caddyfile Equivalent |
---|---|---|
Redirect www → apex | return 301 https://example.com$request_uri; |
redir https://example.com{uri} permanent |
Proxy upstream | proxy_pass http://127.0.0.1:3000; |
reverse_proxy 127.0.0.1:3000 |
Add headers | add_header ... |
header { ... } |
Gzip/Brotli | gzip on; brotli on; |
encode gzip zstd |
Healthcheck | location =/healthz { return 200; } |
@health path /healthz + respond @health 200 |
Rate limit | limit_req_zone ... |
Plugin / CDN (recommend CDN) |
Proxy cache | proxy_cache ... |
Plugin / CDN (recommend CDN) |
12) Troubleshooting Tips
-
Certs won’t issue: Check DNS A/AAAA, port 80/443 open, and no extra listener binding to :80.
-
HTTP/3 not working: Confirm browser support, server build, and QUIC/UDP open (port 443/UDP).
-
Real IP missing: Validate CF ranges and headers; ensure no double proxying strips headers.
-
WebSockets disconnects: Keep‑alive,
proxy_http_version 1.1
, upgrade headers (Nginx); timeouts and buffers. -
CORS/auth bugs: Terminate headers at one layer; pass through required
Authorization
andCookie
headers.
✅ Conclusion / Next Steps
-
Pick Caddy for auto‑HTTPS + HTTP/3 and a fast, delightful config experience.
-
Pick Nginx when you need built‑in caching, rate‑limits, and ultra‑mature knobs for high‑traffic edges.
-
Either way, you can Deploy, Secure, Optimize, and Scale reliably on a tuned KVM NVMe VPS.
Need a reference implementation? See our end‑to‑end guide below.
🔗 Related Articles
-
From Zero to Production — The Complete VPS Setup Guide for Popular Stacks
https://www.domainindia.com/login/knowledgebase/772/-From-Zero-to-Production-The-Complete-VPS-Setup-Guide-for-Popular-Stacks.html -
Hardening Linux for App Gateways — SSH, UFW, Fail2ban, TLS, and log pipeline patterns
-
CI/CD Blue‑Green & Canary Deploys — Zero‑downtime rollouts for Node, Python, PHP, Rails