Blog / Comparisons

Caddy vs Traefik vs Nginx for a Self-Hoster — Which Reverse Proxy to Pick

By the NoctHost TeamAugust 24, 20266 min read

You have a box with three or four services on it and one public IP. Something needs to sit out front, terminate TLS, and route `nextcloud.example.com` to one container and `gitea.example.com` to another. That something is a reverse proxy, and the three names you'll keep hearing are Caddy, Traefik, and Nginx.

Here's the short version so you can stop reading if you want: use Caddy. For a single self-hosted box, automatic HTTPS with no ceremony is worth more than every other feature combined, and Caddy does it better than anyone. The rest of this post is about the cases where that advice is wrong.

The one thing that actually matters at this scale

Performance benchmarks for reverse proxies are a genre of internet content that has almost nothing to do with your life. They pit these tools against each other at hundreds of thousands of requests per second. Your VPS serves you, your family, and a scraper bot every few minutes. All three of these proxies will idle under that load forever. Nginx is genuinely faster at the extreme, and if you were fronting a CDN edge that would decide it. You are not fronting a CDN edge.

So throw out throughput. The things that will actually affect your weekends are: how much pain TLS certificates cause you, how readable the config is when you come back in six months, and how well the proxy notices when you add a new container. Rank the three on those and the picture changes completely.

Certificates: Caddy wins and it isn't close

Every service you expose needs an HTTPS certificate, which means Let's Encrypt, which means the ACME dance and renewal every 90 days.

Caddy does this with zero configuration. You write a hostname, Caddy sees it, fetches the cert on first request, and renews it silently for as long as the process lives. There is no certbot, no cron job, no renewal that quietly failed three weeks ago and you found out when your phone screamed. This is the whole reason Caddy exists and it nails it.

Traefik also automates ACME, but you configure it — a "certresolver" in static config, then referenced per-router. It works well once set up; the setup has more moving parts and the error messages when it doesn't work are cryptic.

Nginx does not do this itself. You bolt on certbot, which manages certs out of band and reloads Nginx on renewal. It's a well-worn path and it's fine, but it's a second tool with its own state, and the classic failure is certbot renewing the file while Nginx keeps serving the old cert until something reloads it.

The same job, three ways

Route two subdomains to two backend containers on ports 8080 and 3000, with HTTPS. Here is the entire Caddy config:

nextcloud.example.com {
  reverse_proxy nextcloud:8080
}

gitea.example.com {
  reverse_proxy gitea:3000
}

That's the whole file. Certs included. Nothing omitted.

Nginx, minus the certbot setup and assuming the certs already exist on disk:

server {
  listen 443 ssl;
  server_name nextcloud.example.com;
  ssl_certificate     /etc/letsencrypt/live/nextcloud.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/nextcloud.example.com/privkey.pem;

  location / {
    proxy_pass http://nextcloud:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Then a second server block for Gitea, plus a listen 80 block to redirect HTTP, plus the certbot invocation. It is not hard, but you can feel the difference in weight.

Traefik you mostly don't write as a file at all — you attach labels to the containers themselves. In a compose file the Nextcloud service grows:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.example.com`)"
  - "traefik.http.routers.nextcloud.tls.certresolver=le"

This looks verbose for one service, and it is. Its payoff shows up at service number ten.

When Traefik is the right answer

If your box is Docker-native — everything runs in compose, you add and remove services regularly, maybe you run Portainer — Traefik earns its complexity. It watches the Docker socket. You bring up a new container with the right labels and Traefik routes it instantly, gets it a cert, and needs no reload, no file edit, nothing. Tear the container down and the route vanishes. Nothing else on this list does that natively.

The tax is real: the static-vs-dynamic config split confuses everyone at first, the dashboard is another thing to secure, and debugging a route that won't come up means squinting at labels for typos. But if you think of your services as cattle that come and go, Traefik is built for exactly your life. For a fixed set of three services that rarely changes, it's overkill.

When Nginx is still the right answer

Reach for Nginx when you need something the other two don't do cleanly, or when you already know it. Concretely:

  • You need fine control: complex location matching, rate limiting, caching, request rewriting, serving static files directly and fast.
  • You're following documentation or a stack (a lot of self-hosted software ships an Nginx config you can paste).
  • You want the biggest pile of Stack Overflow answers on earth, because every question has been asked already.

Nginx is the boring, universal, infinitely documented choice, and boring is a virtue in infrastructure. The cost is the certbot bolt-on and config that's wordier than it needs to be for simple routing. If your setup is simple, that cost is pure overhead. If it's genuinely complex, Nginx's control is unmatched by the other two.

So, actually pick one

  • Self-hosting a handful of services and you want to spend your time on the services, not the proxy: Caddy. This is most people.
  • Everything lives in Docker and services churn: Traefik.
  • You need surgical control, you're pasting a vendor's config, or you already speak Nginx fluently: Nginx.

The mistake isn't picking the "wrong" one — all three will serve your traffic fine on a box you can rent for a couple of dollars a month, which is the range NoctHost sits in. The mistake is picking Traefik's complexity for a static setup, or fighting certbot cron jobs when Caddy would have handled the whole thing in four lines.

Spin one up in about a minute

Email signup, pay with crypto, hourly billing. Trying a box costs cents — destroy it when you are done.

Deploy a server

Frequently asked

Can I run Caddy or Traefik in front of containers Nginx already serves?
Yes, and people do — an outer proxy for TLS and routing, an inner Nginx inside a container serving a specific app's static files. That inner Nginx isn't the internet-facing reverse proxy, it's the app's own web server, and stacking them is normal.
Does Caddy's automatic HTTPS work behind another proxy or a CDN?
It can, but the ACME challenge needs a path to reach your server. Behind Cloudflare's proxy you'll typically use the DNS challenge instead of the default HTTP challenge, which needs an API token. Caddy supports it with a DNS plugin; it's the one setup that isn't zero-config.
Is Nginx really faster, and will I notice?
At very high concurrency, measurably yes. On a single self-hosted VPS serving human-scale traffic, no — all three spend most of their time idle. Choose on certificate handling and config ergonomics, not benchmarks.
What about renewals — do any of these ever break?
Caddy and Traefik renew automatically as long as the process runs, so the usual failure is the process being down for weeks, not the renewal logic. Nginx-plus-certbot's classic failure is certbot renewing the cert file but nothing reloading Nginx, so it keeps serving the expired one until a reload. A weekly reload in the renewal hook fixes it.

Keep reading