Client Area

Nginx vs Caddy for App Gateways 2025 Engineer's Guide

8 min readPublished 4 Mar 2026Updated 21 Apr 20262,021 views

In this article

  • 1Short Summary
  • 2Table of Contents
  • 31) Quick Answer: When to Pick Which
  • 42) Feature Comparison Matrix
  • 53) Architecture Basics

A practical, productionready comparison of Nginx and Caddy for use as an application gateway / reverse proxy: TLS automation, HTTP/3, performance, caching, observability, and secure defaults--plus copypaste 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 battletested performance, builtin reverseproxy caching, and finegrained 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 CheatSheet (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
Zerotouch 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
Reverseproxy caching builtin proxy_cache is mature Use a plugin or CDN (not core)
Finegrained 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 Humanfriendly Caddyfile
Enterprise ubiquity & docs Longstanding standard Growing fast, great docs
Dynamic/onthefly config nginx -s reload (no drop) Admin API; live reloads

Rule of thumb:

  • Prefer Caddy if you value autoHTTPS + HTTP/3 and concise configs with great defaults.

  • Prefer Nginx if you need builtin caching, kernellevel tuning, and broad distro packaging.


2) Feature Comparison Matrix

Capability Nginx Caddy
Automatic HTTPS Via Certbot / ACME clients Builtin ACME, OCSP stapling, automatic renewals
HTTP/2
HTTP/3 (QUIC) in recent mainline builds; check your distro enabled by default in most builds
Reverseproxy 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
RealIP/CDN headers realip module trusted_proxies / header passthrough
Observability Access/error logs, exporters JSON logs, structured fields, metrics module

Note: Caching and advanced ratelimits are where Nginx still has the edge without plugins.


3) Architecture Basics

A typical appgateway (reverse proxy) sits in front of one or more upstream services (Node.js/PM2, Django/Uvicorn, PHPFPM, 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 DNS01 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.

    • Builtin 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 reverseproxy caching or ratelimits, 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, XFrameOptions, XContentTypeOptions, ReferrerPolicy, PermissionsPolicy

  • Enable Brotli/gzip and reasonable timeouts

  • Sanitize/forward XForwarded 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 orangecloud 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 cachefriendly 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 CheatSheet (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: Keepalive, 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 autoHTTPS + HTTP/3 and a fast, delightful config experience.

  • Pick Nginx when you need builtin caching, ratelimits, and ultramature knobs for hightraffic edges.

  • Either way, you can Deploy, Secure, Optimize, and Scale reliably on a tuned KVM NVMe VPS.

Need a reference implementation See our endtoend guide below.


Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket