Deployment guide

Caddy vs Nginx: Which Web Server / Reverse Proxy for Your VPS?

Deploy on a VPS Cloud →

Caddy vs Nginx: Which Web Server / Reverse Proxy for Your VPS?

Comparison9 min read8 steps

On a VPS that serves HTTP traffic, the reverse proxy is not a configuration detail: it is the piece that handles TLS, distributes requests to your containers, and sets the security headers seen by every visitor. Caddy fully automates the issuance and renewal of Let's Encrypt certificates; Nginx offers the most proven fine-tuning on the market. This guide compares both in depth — advanced configuration, troubleshooting, HTTP/3 — and explains when a third choice, Traefik, is the right call.

Contents· Why Take Care of Your Reverse Proxy on a VPS1/9
  1. 01Why Take Care of Your Reverse Proxy on a VPS
  2. 02What a Good Front End Brings to Your VPS
  3. 03Prerequisites: A Frugal Front End
  4. 04Set Up Caddy (or Nginx) as a Reverse Proxy
  5. 05Advanced Configuration: Security, Compression and HTTP/3
  6. 06Caddy vs Nginx — Comparison Table
  7. 07Troubleshooting: The Most Common Errors
  8. 08Caddy, Nginx or Traefik: The Third Choice
  9. 09Combining Both Rather Than Choosing

Why Take Care of Your Reverse Proxy on a VPS

On a VPS, the front-end web server is the piece that orchestrates everything: it terminates TLS, distributes traffic to your containers (app, forge, media server, LLM), serves static files, and applies security headers. Well chosen, it radically simplifies enabling HTTPS on all your subdomains. Caddy obtains and renews Let's Encrypt certificates automatically, with no configuration, using a readable Caddyfile of a few lines. Nginx, the market reference, offers extremely fine control (cache, rewrite rules, load balancing, rate limiting) but requires manual certificate management or management via Certbot. The choice pits modern automation against proven, total control.

What a Good Front End Brings to Your VPS

  • Centralized TLS termination for all your subdomains
  • A single reverse proxy to multiple Docker containers
  • Fast static file serving and compression (gzip/brotli)
  • Security headers (HSTS, CSP, X-Content-Type-Options) applied in the same place
  • With Caddy: Let's Encrypt certificates obtained and renewed automatically
  • With Nginx: cache, rate limiting, and load balancing finely tuned
  • HTTP/3 and QUIC to reduce latency on degraded connections

Prerequisites: A Frugal Front End

A reverse proxy is very lightweight: both Caddy and Nginx run comfortably on 1 vCPU and 512 MB to 1 GB of RAM, even in front of several services. The resource to watch is rather the bandwidth and the number of simultaneous connections. You need a domain and its subdomains pointing to the VPS IP (A/AAAA records), ports 80 and 443 open on the firewall (port 80 is required for certificate validation), and Docker if you containerize the proxy. No GPU or large storage: here, it is the configuration that makes the difference, not raw power.

Set Up Caddy (or Nginx) as a Reverse Proxy

  1. Point the DNS

    Create the A records (and AAAA if IPv6) for each subdomain (app, git, media) pointing to the VPS IP. Let's Encrypt validation will fail as long as DNS resolution is not effective, so verify it first with dig app.yourdomain.com or an online tool.

  2. Open Ports 80 and 443

    Allow only 80 and 443 on the firewall (ufw allow 80/tcp && ufw allow 443/tcp). Port 80 remains essential for the Let's Encrypt HTTP challenge and to automatically redirect traffic to HTTPS.

  3. Write the Base Configuration

    With Caddy, one block is enough: app.yourdomain.com { reverse_proxy 127.0.0.1:3000 } and HTTPS is automatic. With Nginx, write one server block per subdomain with proxy_pass and the X-Forwarded-* headers, then obtain the certificate via Certbot: certbot --nginx -d app.yourdomain.com.

  4. Configure Multiple Subdomains

    With Caddy, list each block in the same Caddyfile: git.yourdomain.com { reverse_proxy 127.0.0.1:3001 }. With Nginx, create one file per subdomain in /etc/nginx/conf.d/ or /etc/nginx/sites-available/, then enable it with ln -s. Both approaches let you manage dozens of services without repetition.

  5. Enable Compression

    Caddy enables gzip by default; to add brotli: encode zstd br gzip in the site block. Under Nginx, add to nginx.conf: gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 256;. Compression reduces text response size by 60 to 80% in most cases.

  6. Launch and Reload Without Downtime

    Start the service (docker compose up -d or systemctl start caddy). After each change, validate the config (nginx -t or caddy validate) and then hot-reload (nginx -s reload / caddy reload) to never interrupt traffic.

  7. Verify Certificates

    With Caddy, run caddy validate --config /etc/caddy/Caddyfile to catch errors before reloading, and check logs (journalctl -u caddy -f) to confirm certificate issuance. With Nginx + Certbot, certbot certificates lists expiry dates and certbot renew --dry-run simulates renewal.

  8. Harden Security and Set Up Logs

    Enable HSTS, hide the server version (server_tokens off under Nginx, automatic under Caddy), enforce TLS 1.2+, and add basic rate limiting. Enable access and error logs, and watch for 502/504 codes that reveal an unreachable downstream container.

Once the proxy is operational and certificates have been issued, two complementary steps strengthen security and performance: advanced header and rate limiting configuration, and monitoring setup. Order matters: validate that basic routing works first (curl -I https://app.yourdomain.com) before stacking configuration layers.

Advanced Configuration: Security, Compression and HTTP/3

Once the basics are in place, three areas concretely improve your reverse proxy's posture.

Security headers. Apply at the proxy level the headers that each downstream service will forget: Strict-Transport-Security: max-age=31536000; includeSubDomains (HSTS), X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, and a Content-Security-Policy tailored to your application. Centralizing these headers at the proxy level ensures they cover all your subdomains in one move.

Rate limiting. Under Caddy, the rate_limit module (available via xcaddy build) lets you cap requests per IP. Under Nginx, the directive limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m; in http {} then limit_req zone=api burst=10 nodelay; in the relevant location is enough to protect an API against abuse without external dependencies.

HTTP/3 and QUIC. Caddy enables HTTP/3 by default as soon as UDP port 443 is open. Nginx has supported QUIC since the mainline branch (quic parameter in the listen block); check the installed version with nginx -v before enabling the directive. HTTP/3 reduces perceived latency on mobile connections and high packet-loss networks, without any change in behavior for clients that do not support it.

Caddy vs Nginx — Comparison Table

Scroll the table

CriterionCaddyNginx
HTTPS / certificatesAutomatic, zero configManual or via Certbot
Configuration syntaxCaddyfile, very conciseMore verbose, very expressive
Learning curveLowModerate to high
Fine-grained control (cache, rewrite, LB)Good, sometimes via pluginsVery complete and proven
HTTP/3 / QUICEnabled by defaultSupported (mainline branch)
Native rate limitingVia xcaddy moduleBuilt-in (`limit_req`)
Dynamic modulesCompilation via xcaddyDynamic modules (.so)
RAM footprint at rest~30–50 MB~20–40 MB (workers)
Log formatStructured JSON by defaultText, configurable
Ideal forQuick HTTPS setup, multi-subdomainAdvanced tuning, high traffic

The deployment steps cover the ideal configuration. In practice, several types of errors come up regularly: a certificate blocked by an intermediate network, a downstream service that is not yet responding, or a Docker container unreachable from the proxy because the two are on separate networks. The most useful dashboard in these situations is the proxy log (journalctl -u caddy -f or tail -f /var/log/nginx/error.log), combined with curl locally to isolate the problem.

Troubleshooting: The Most Common Errors

Even a well-configured proxy produces errors at startup or under load. Here are the five cases that come up most often.

Caddy — "no certificate" behind a load balancer. When Caddy sits behind a load balancer that already terminates TLS, it cannot receive the Let's Encrypt HTTP-01 challenge and fails to issue the certificate. Solution: use the DNS-01 challenge via your registrar's plugin (e.g. tls { dns cloudflare {env.CF_API_TOKEN} }), or delegate TLS management entirely to the load balancer and force Caddy to http:// internally.

Nginx — 502 Bad Gateway (upstream timeout). A persistent 502 after startup means the downstream service is not listening yet or has crashed. Check with curl -v http://127.0.0.1:<port> from the server. If the service starts slowly, increase proxy_read_timeout and proxy_connect_timeout. An intermittent 502 under load points to a lack of keepalive connections: add keepalive 32; in the upstream block.

Caddy with Docker — container unreachable. When Caddy and the target service run on separate Docker networks, reverse_proxy 127.0.0.1:3000 does not work: 127.0.0.1 is the loopback address of the Caddy container, not the host. Solution: connect both containers to the same named Docker bridge network and use the service name as the target address: reverse_proxy service-name:3000.

Nginx — "too many open files" under load. The error worker_connections are not enough or open() failed (24: Too many open files) appears when the VPS receives a traffic spike. Increase the system limit (ulimit -n 65535 or DefaultLimitNOFILE=65535 in the systemd unit) and align worker_connections 4096; in nginx.conf. The maximum simultaneous connections is worker_processes * worker_connections.

Caddy — silently blocked renewal. Caddy renews in the background, but if UDP port 443 is closed by the firewall, HTTP/3 fails and logs may mask the real cause. Monitor journalctl -u caddy -f around renewal dates (60 days after issuance) and test the challenge with caddy run --config /etc/caddy/Caddyfile --watch in the foreground to see errors in real time.

Caddy, Nginx or Traefik: The Third Choice

For infrastructure with many Docker microservices with automatic container discovery, Traefik emerges as a third option: it reads Docker labels (traefik.http.routers.myapp.rule=Host("app.yourdomain.com")) and configures routes on the fly without manual reloading. The downside is its more complex configuration and denser documentation. Caddy and Nginx remain the natural choices for a reasonably-sized VPS (1 to 20 services); Traefik takes over beyond that, especially in a Kubernetes or Docker Swarm context. For a full comparison of all three, see the article choosing your VPS reverse proxy: Caddy, Traefik or Nginx.

Combining Both Rather Than Choosing

If you are still hesitating, know that Caddy and Nginx are not mutually exclusive. A common pattern places Caddy as the very first front end to automatically manage TLS for all your subdomains, then leaves Nginx downstream for fine caching and rewrite rules of a specific service. This way you combine automatic certificates with tuning control, without picking a side for good.

An HTTPS Front End Ready in a Few Lines

The ServOrbit Cloud VPS with a Docker template lets you deploy Caddy or Nginx as a reverse proxy, with automatic SSL for all your subdomains and services.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab