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
locationmatching, 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.