⚖️ Nginx vs Caddy for App Gateways — 2025 Engineer’s Guide Print

  • 0

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

  1. Quick Answer: When to Pick Which

  2. Feature Comparison Matrix

  3. Architecture Basics

  4. Install & First Reverse Proxy

  5. TLS & Certificates

  6. HTTP/2, HTTP/3, WebSockets, gRPC

  7. Performance & Caching

  8. Security Hardening Checklist

  9. Cloudflare / CDN Integration

  10. Logging & Observability

  11. Migration Cheat‑Sheet (Nginx ⇄ Caddy)

  12. Troubleshooting Tips

  13. ✅ Conclusion / Next Steps

  14. 🔗 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 forward X-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 via stub_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 and Cookie 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


Was this answer helpful?

« Back