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.
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 need | Nginx | Caddy |
|---|---|---|
| Automatic HTTPS for many sites | With Certbot or another ACME client | Built in, on by default |
| HTTP/3 (QUIC) | Yes, with extra config (1.25 or later) | On by default |
| Response caching | Built in (proxy_cache) | Plugin or CDN |
| Rate and connection limits | Built in (limit_req, limit_conn) | Plugin or CDN |
| Config style | Blocks and directives, verbose but precise | Short Caddyfile, or JSON through an API |
| Memory use | Very low | Low; a little higher (Go runtime) |
| Reload without dropping connections | nginx -s reload | caddy 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
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.1so 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):
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:
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d example.com -d www.example.com --redirect
sudo certbot renew --dry-runInstall 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:
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 caddyOn 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:
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 oldlisten 443 ssl http2form 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 tolisten 443 ssl;, plushttp3 on;andadd_header Alt-Svc 'h3=":443"; ma=86400';. Both need UDP port 443 open in the firewall. - WebSockets: Caddy handles them automatically; Nginx needs the
UpgradeandConnectionheaders shown in section 3, and a longerproxy_read_timeoutfor long-lived connections. - gRPC: Nginx uses
grpc_pass grpc://127.0.0.1:50051;; Caddy usesreverse_proxy h2c://127.0.0.1:50051.
6. Caching and rate limits
This is where Nginx still leads without add-ons.
# 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:
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:
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:
{
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.
| Task | Nginx | Caddyfile |
|---|---|---|
| Proxy to an app | proxy_pass http://127.0.0.1:3000; | reverse_proxy 127.0.0.1:3000 |
| Redirect www to apex | return 301 https://example.com$request_uri; | redir https://example.com{uri} permanent |
| Compression | gzip on; | encode zstd gzip |
| Add a header | add_header Name "value" always; | header Name "value" |
| Health check | location = /healthz { return 200; } | respond /healthz 200 |
| Serve static files | root /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 -tlpnand 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_timeoutin 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.
- 1 vCPU
- 2 GB DDR4 RAM
- 64 GB NVMe SSD Storage
- 2 TB Monthly Bandwidth
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.
- 512 MB RAM per app
- 1 vCPU
- 5 GB NVMe SSD
- PostgreSQL Database
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.
KVM VPS with full root access and NVMe storage, ready for Nginx or Caddy in front of your apps.
See VPS plans