Reverse proxy: Caddy vs nginx

Both put a domain in front of a backend service and terminate TLS. The differences that actually matter — certificate handling, config ergonomics, protocol support, and reload behaviour — and which one to reach for by default.

The short answer

For a new project — a homelab service, a small business app, a side project — start with Caddy. Automatic HTTPS alone removes a whole category of maintenance (certificate requests, renewal cron jobs, expiry monitoring), and its config format gets a working reverse proxy onto the page in a handful of lines. Reach for nginx instead when you're integrating with an existing nginx-centric stack, need a specific module from its large ecosystem, or are running configurations advanced enough that nginx's flexibility outweighs the setup cost. Neither is the wrong tool; they're optimized for different amounts of manual control.

What both actually do

A reverse proxy sits between the public internet and a backend application, accepting the incoming connection, terminating TLS, and forwarding the request to wherever the real service is listening — usually localhost or another host on a private network. The backend never needs to speak TLS itself, doesn't need to bind to port 80 or 443, and can be restarted or moved without the public-facing address changing. Both Caddy and nginx do this job correctly; where they diverge is in what you have to configure by hand to get there.

Automatic HTTPS vs. certificates you manage

This is the single biggest practical difference. Point Caddy at a domain with working DNS and reachable ports 80/443, and it requests a Let's Encrypt certificate on first request and renews it automatically for as long as the config exists — no cron job, no certbot, no renewal script to forget about. nginx has no certificate-issuing logic of its own; TLS certificates come from somewhere else (certbot, acme.sh, a manually-obtained cert) and nginx just points at the resulting files. That's not a flaw in nginx — it's a deliberate separation of concerns that a lot of production infrastructure actually wants — but for a small deployment it's one more moving part than Caddy requires.

Configuration: declarative vs. explicit

Caddy's Caddyfile reads like a description of the desired end state: a domain block, a reverse_proxy directive, done. nginx's server block syntax gives you granular control over every directive — listen sockets, SSL certificate paths, proxy headers, buffering behaviour — but you're responsible for assembling all of it, and a lot of "starter" nginx configs online copy-paste settings nobody in the room can fully explain. Neither approach is objectively better; Caddy trades explicit control for sane defaults, nginx trades convenience for precision. If you want to see the exact same reverse-proxy setup expressed both ways, the Reverse Proxy Config Generator builds both a Caddyfile and an nginx server block from one form, side by side.

WebSockets: the gap that surprises people

Caddy's reverse_proxy passes WebSocket upgrade requests through by default, with zero extra configuration. nginx does not — without explicit proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection $connection_upgrade; lines (plus the map block that defines $connection_upgrade), a WebSocket handshake behind nginx fails while ordinary HTTP requests to the same backend work perfectly. This is one of the more common "why doesn't my app work behind the proxy" reports, and it's a config gap, not a bug in the application.

HTTP/3 and protocol support

Caddy has shipped HTTP/3 (over QUIC) by default for years, enabled automatically alongside HTTP/2 wherever the client supports it. nginx's HTTP/3 support exists but historically required a separate build or module rather than being on by default in every distro package — check your specific nginx build if HTTP/3 matters for your use case. For most sites the practical difference is small (HTTP/2 already handles the common cases well), but it's a real point in Caddy's favour if you're specifically chasing the newest transport.

Reload behaviour and downtime

Both can reload configuration without dropping existing connections in the common case — nginx's nginx -s reload and Caddy's config-apply both use a graceful worker-replacement model. Where they differ is failure handling on a bad config: nginx's reload fails safe (the old workers keep running if the new config doesn't parse), and Caddy's API-driven reload similarly validates before swapping. Neither tool is meaningfully worse here for a typical deployment; both are mature enough that "the proxy" is rarely the thing failing.

When nginx is still the better choice

Nothing above makes nginx obsolete. Reach for it when: you're already running an nginx-based stack (ingress-nginx on Kubernetes, an existing fleet of nginx configs) and consistency matters more than any single feature; you need a specific nginx module (some caching, auth, or media-streaming modules exist only in the nginx ecosystem); you're doing advanced load balancing or caching configurations that a decade of nginx documentation and community examples has already solved; or your team's operational knowledge is built around nginx and there's no compelling reason to introduce a second proxy technology into that expertise.

ConcernCaddynginx
TLS certificatesAutomatic, built-in (Let's Encrypt)Manual — needs certbot/acme.sh or a pre-obtained cert
Config styleDeclarative, minimal boilerplateExplicit, full control over every directive
WebSocket supportAutomatic, no extra configNeeds explicit Upgrade/Connection headers
HTTP/3On by defaultOften needs a specific build/module
Ecosystem & modulesSmaller, growingLarge, mature, well-documented
Best fitNew projects, small-to-medium deploymentsExisting nginx stacks, advanced/custom configs
Build either config without hand-typing it: the Reverse Proxy Config Generator produces a working Caddyfile and the equivalent nginx server block from the same form — domain, upstream, TLS, WebSocket support, and security headers. The Docker Compose Generator is the natural next step if the backend you're proxying to also needs a compose file. Both run entirely in your browser, free, no sign-up. See also our post on why we moved most of our own infrastructure from nginx to Caddy.

Frequently asked questions

Can I run Caddy and nginx on the same server?

Yes, as long as they're not both trying to bind the same port — a common pattern is Caddy as the internet-facing proxy on 80/443, forwarding to an nginx instance handling something specific behind it. Just make sure only one of them owns the public ports.

Does switching from nginx to Caddy mean losing existing SSL certificates?

No — Caddy can use existing certificate files if you want to keep them, though the more common approach is to let Caddy request its own via Let's Encrypt once DNS points at it, which works alongside a still-valid old certificate during the transition.

Is Caddy production-ready, or just good for homelabs?

Production-ready — Caddy is used in production by companies well beyond hobbyist scale, and its automatic-HTTPS model is arguably more production-safe than a manually-scheduled certbot renewal that can silently fail. The "homelab-friendly" reputation comes from how little config it needs, not from any capability ceiling.

Which one has better performance?

For the overwhelming majority of workloads, the difference is not the bottleneck — both handle typical reverse-proxy loads well within what a small-to-medium deployment needs. Choose based on operational fit (certificate handling, config style, ecosystem) rather than a performance difference you're unlikely to notice.


Questions or corrections? info@mb-networks.ca