Web Servers (Apache, Nginx, Caddy)

Nginx vs Caddy for App Gateways 2025 Engineer's Guide

By the Domain India teamPublished 9 min read
Knowledge base article
Contents (10 sections)

An app gateway is the web server that sits in front of your application: it terminates HTTPS, speaks HTTP/2 and HTTP/3 to browsers, and passes requests to Node.js, Python, PHP-FPM or Go running on a local port. Nginx and Caddy are the two most common choices on a VPS. This guide compares them for that job, with working configurations for both, and is current for 2026.

Key takeaways

Choose Caddy if you want HTTPS certificates, renewals and HTTP/3 to work automatically with a five-line config. Choose Nginx if you need built-in response caching, rate and connection limits, or a proxy your team already knows well. Both handle WebSockets, gRPC and high traffic without trouble on a small VPS. Either way, your app listens on 127.0.0.1 and only the proxy faces the internet.

1. Quick answer

If you needNginxCaddy
Automatic HTTPS for many sitesWith Certbot or another ACME clientBuilt in, on by default
HTTP/3 (QUIC)Yes, with extra config (1.25 or later)On by default
Response cachingBuilt in (proxy_cache)Plugin or CDN
Rate and connection limitsBuilt in (limit_req, limit_conn)Plugin or CDN
Config styleBlocks and directives, verbose but preciseShort Caddyfile, or JSON through an API
Memory useVery lowLow; a little higher (Go runtime)
Reload without dropping connectionsnginx -s reloadcaddy reload or the admin API

A good rule: Caddy for small teams and many small sites where certificates are the main chore; Nginx where you need fine control at the edge or where caching saves real money.

2. How the gateway fits

text
Browser → (optional CDN) → Nginx or Caddy :443 → app on 127.0.0.1:3000 → database
  • Run the proxy on the same server as the app, and bind the app to 127.0.0.1 so nobody can reach it around the proxy.
  • Keep a process manager (systemd, PM2, Gunicorn or Uvicorn) in charge of the app; the proxy only routes traffic.
  • Open only ports 80 and 443 (plus UDP 443 for HTTP/3) and SSH in the firewall.

3. Nginx: install and first reverse proxy

Install from your distribution (sudo apt install nginx on Ubuntu, sudo dnf install nginx on AlmaLinux or Rocky) and enable it with sudo systemctl enable --now nginx. Save the site as /etc/nginx/conf.d/app.conf (both distributions read this folder):

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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;
    }
}

Test and reload, then add a Let's Encrypt certificate with Certbot, which rewrites the server block for HTTPS and installs a renewal timer:

bash
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d example.com -d www.example.com --redirect
sudo certbot renew --dry-run

Install Certbot from your distribution (python3-certbot-nginx on Ubuntu; from EPEL on AlmaLinux and Rocky) or with the method on the Certbot website.

4. Caddy: install and first reverse proxy

On Ubuntu, add Caddy's official repository and install:

bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
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

On AlmaLinux or Rocky: sudo dnf install 'dnf-command(copr)', sudo dnf copr enable @caddy/caddy, then sudo dnf install caddy.

The whole site in /etc/caddy/Caddyfile:

caddy
www.example.com {
    redir https://example.com{uri} permanent
}

example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3000
}

Run sudo systemctl reload caddy. Once the domain's A record points at the server and ports 80 and 443 are open, Caddy gets the certificate, redirects HTTP to HTTPS, renews automatically and serves HTTP/3. reverse_proxy passes WebSockets and sets X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host for you, so don't set them by hand.

5. HTTP/2, HTTP/3, WebSockets and gRPC

  • HTTP/2: both serve it over HTTPS. In Nginx 1.25.1 and later, write http2 on; inside the server block; the old listen 443 ssl http2 form is deprecated.
  • HTTP/3: Caddy enables it by default. In Nginx 1.25 or later built with QUIC support, add listen 443 quic reuseport; next to listen 443 ssl;, plus http3 on; and add_header Alt-Svc 'h3=":443"; ma=86400';. Both need UDP port 443 open in the firewall.
  • WebSockets: Caddy handles them automatically; Nginx needs the Upgrade and Connection headers shown in section 3, and a longer proxy_read_timeout for long-lived connections.
  • gRPC: Nginx uses grpc_pass grpc://127.0.0.1:50051;; Caddy uses reverse_proxy h2c://127.0.0.1:50051.

6. Caching and rate limits

This is where Nginx still leads without add-ons.

nginx
# in the http block (for example the top of conf.d/app.conf)
proxy_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app:10m max_size=1g inactive=60m use_temp_path=off;
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;

# inside location /
proxy_cache app;
proxy_cache_valid 200 301 10m;
proxy_cache_bypass $cookie_session $http_authorization;
proxy_no_cache $cookie_session $http_authorization;
limit_req zone=perip burst=20 nodelay;
add_header X-Cache $upstream_cache_status;

Never cache responses for logged-in users: the two $cookie_session $http_authorization lines skip the cache when a session cookie or token is present (use your app's cookie name). With Caddy, add caching or rate limiting with community plugins built in using xcaddy, or let a CDN do it.

7. Security headers and real client IPs

Security headers, Nginx:

nginx
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Caddy:

caddy
header {
    Strict-Transport-Security "max-age=31536000"
    X-Content-Type-Options "nosniff"
    X-Frame-Options "SAMEORIGIN"
    Referrer-Policy "strict-origin-when-cross-origin"
    -Server
}

Add includeSubDomains or preload to HSTS only when every subdomain serves HTTPS; preload is hard to undo.

Behind a CDN such as Cloudflare, every request arrives from the CDN's addresses. Trust its forwarded header only from its published ranges (Cloudflare lists them at cloudflare.com/ips; keep your list current). In Nginx, use real_ip_header CF-Connecting-IP; with one set_real_ip_from line per range. In Caddy, use the global options block:

caddy
{
    servers {
        trusted_proxies static 173.245.48.0/20 103.21.244.0/22
        client_ip_headers CF-Connecting-IP
    }
}

8. Logs and a migration cheat sheet

Caddy writes structured JSON with log { output file /var/log/caddy/access.log; format json } in a site block. Nginx writes JSON with a custom log_format ... escape=json and an access_log line. Both feed Loki, the ELK stack or any log shipper.

TaskNginxCaddyfile
Proxy to an appproxy_pass http://127.0.0.1:3000;reverse_proxy 127.0.0.1:3000
Redirect www to apexreturn 301 https://example.com$request_uri;redir https://example.com{uri} permanent
Compressiongzip on;encode zstd gzip
Add a headeradd_header Name "value" always;header Name "value"
Health checklocation = /healthz { return 200; }respond /healthz 200
Serve static filesroot /var/www/site;root * /var/www/site then file_server

9. Troubleshooting

  • Certificate won't issue: the A/AAAA record must point at this server, and ports 80 and 443 must be open. A stale AAAA record pointing elsewhere is a common cause.
  • 502 Bad Gateway: the app isn't running or listens on a different port. Check with ss -tlpn and the app's logs.
  • HTTP/3 not used: UDP 443 is blocked, or the Nginx build lacks QUIC support.
  • Wrong client IPs in app logs: the real-IP settings are missing, or you trust headers from addresses that aren't your proxy.
  • WebSockets drop after 60 seconds: raise proxy_read_timeout in Nginx, or send keep-alive pings from the app.

10. Running this on Domain India

VPS. Our VPS is self-managed with full root access, so you can run either gateway exactly as shown. Open ports only in your own firewall and keep the app on 127.0.0.1.

VPS Starter
₹552.65/mo + GST
  • 1 vCPU
  • 2 GB DDR4 RAM
  • 64 GB NVMe SSD Storage
  • 2 TB Monthly Bandwidth
See plan details

Shared hosting. You can't run your own gateway on shared hosting. Our shared servers run Apache, and on cPanel an nginx proxy sits in front of it; .htaccess rules work, but the proxy settings are managed by us.

App Platform. If you only want to run an app, the App Platform does the gateway work for you: every plan includes a custom domain and free automatic SSL, so there is no proxy to configure.

App Starter
₹100/mo + GST
  • 512 MB RAM per app
  • 1 vCPU
  • 5 GB NVMe SSD
  • PostgreSQL Database
See plan details

Prices on the cards are live and exclude 18% GST.

Is Caddy faster than Nginx?

For most apps the difference is too small to matter; the app and database decide your response times. Nginx uses slightly less memory and has built-in caching, which can make a real difference for cacheable pages. Caddy saves time on setup and certificates.

Does Caddy really handle HTTPS automatically?

Yes. When a site block names a domain that points at the server, and ports 80 and 443 are reachable, Caddy obtains a certificate, redirects HTTP to HTTPS and renews the certificate before it expires, with no extra tools.

Can Nginx do HTTP/3?

Yes, from version 1.25 with a build that includes QUIC support. Add a quic listener, http3 on, an Alt-Svc header, and open UDP port 443 in the firewall.

How do I rate-limit requests in Caddy?

Caddy has no rate limiting in its standard build. Add a community rate-limit plugin with xcaddy, or apply limits at a CDN. Nginx has limit_req and limit_conn built in.

Can I run Nginx or Caddy on Domain India shared hosting?

No. Shared hosting runs Apache, with an nginx proxy in front on cPanel, and the web server configuration is managed by us. Use a VPS for your own gateway, or the App Platform, which includes SSL and custom domains.

Should my app listen on 0.0.0.0?

No. Bind it to 127.0.0.1 so only the proxy on the same server can reach it, and let the proxy face the internet on ports 80 and 443.

Ready to build your gateway? Compare VPS plans, follow the MERN on a clean VPS playbook for a full stack behind Nginx, or deploy without a server on the App Platform.

Run your gateway on a VPS

KVM VPS with full root access and NVMe storage, ready for Nginx or Caddy in front of your apps.

See VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Nginx vs Caddy as a Reverse Proxy: 2026 Guide