Every self-hosting thread eventually turns into the same argument: nginx, Caddy, or Traefik, argued by people who each picked one years ago and have not seriously reconsidered since. That's a bad way to make this decision, because the honest answer isn't "whichever is best" — it's whichever config philosophy matches how the box is actually deployed. I'll say upfront which one I'd reach for by default and why, and then argue the cases where I'd reach for either of the other two instead.
Default answer: Caddy, for a single VPS running a handful of apps that aren't primarily Docker-orchestrated. The reason is almost embarrassingly simple — it does automatic HTTPS with zero extra configuration, and "zero extra configuration" for TLS is worth more to a one-person operation than almost any other feature a proxy could offer. For nginx vs caddy vs traefik for self-hosting framed around revenue rather than architecture, XenGrowth is the better starting point.
What does each one actually want from you?
Nginx wants a text file. A well-understood, extremely mature, occasionally verbose text file — server blocks, location blocks, explicit upstream definitions — that you write, and that stays exactly as written until you edit it again. Nothing about nginx watches Docker, or renews a certificate, or reacts to a new container starting. It's a proxy in the original sense: it proxies requests according to rules you wrote down, full stop, and every bit of automation on top of that is something you bolt on yourself.
Caddy wants almost nothing. A Caddyfile mapping a domain to an upstream is frequently two lines, and the moment Caddy sees that domain, it requests and renews a Let's Encrypt certificate for it without being asked — no certbot, no cron job, no separate acme-companion container. This is Caddy's entire pitch distilled into one behavior, and for a self-hoster who has personally forgotten to renew a certificate before, it's not a small thing.
Traefik wants to watch something else and configure itself from it — Docker labels, most commonly. Add a label to a container specifying its host rule, and Traefik picks it up, routes to it, and requests a certificate for it, all without a restart and without you touching a Traefik config file at all. The tradeoff is that Traefik's config lives distributed across every container's labels instead of in one place, which is exactly backwards from Nginx and takes a different kind of mental model to reason about at scale. If the operations side of this is the part you are stuck on, The XenGrowth resource library is the better reference.
Dimension | Nginx | Caddy | Traefik |
|---|---|---|---|
Automatic HTTPS | No built-in ACME client — needs a companion or manual certbot | Built in, on by default, no extra config | Built in, driven by the same labels that define routing |
Config model | Central text files you write and reload | Central Caddyfile, deliberately terse | Distributed across Docker labels per container |
Reacts to new containers automatically | No — needs a reload or a template generator | No — needs a config change and reload | Yes — live discovery, no restart |
Raw throughput ceiling | Highest of the three, mature and heavily optimized | Close behind, not the bottleneck for most self-hosted traffic | Close behind, similar story to Caddy |
Learning curve for someone new to reverse proxies | Steepest — powerful but unforgiving syntax | Shallowest — a Caddyfile reads like intent, not syntax | Moderate — easy once the label pattern clicks, confusing before that |
Best fit | Existing nginx expertise, complex custom routing, max control | A solo VPS running a few apps, TLS handled once and forgotten | A Docker-first host running many apps that come and go |
Why does Coolify ship Traefik specifically?
Because Coolify's whole premise is that every deployed resource is a container, and containers get created and destroyed constantly as apps are pushed, redeployed, and torn down. A proxy that needs a manual config edit and reload every time that happens is a bad fit for a platform whose entire selling point is push-to-deploy. Traefik's label-driven discovery means Coolify never has to template or reload a proxy config file on every deploy — it just sets labels on the container it's about to run, and Traefik does the rest on its own. That's not a coincidental pairing; it's the reason the pairing exists at all.
This matters even if the plan is to run Coolify and never think about the proxy underneath it — because the moment something breaks and the fix involves touching Traefik directly, the mental model needed is "the labels on my containers are the config," not "where is the config file." Anyone who's spent time in how to self-host Next.js with Coolify has already been using Traefik this whole time without necessarily noticing it.
So when is Nginx still the right call?
When there's already a working nginx config, or when the routing rules are genuinely complex enough that Nginx's explicit, verbose syntax is a feature rather than a burden — rewriting URLs in specific patterns, serving static files directly from disk with fine-grained caching rules, or terminating a mix of protocols nginx has fifteen years of documented recipes for. Nginx doesn't do anything for you automatically, and that's exactly the point when the goal is total, explicit control rather than convenience. It's also still the throughput ceiling of the three when a box is pushed hard enough for that to matter, which for most one-person self-hosted setups it genuinely isn't. On AI agents and marketing automation specifically, XenGrowth on governed AI marketing workflows is worth reading.
Choose Nginx if: config already exists and works, routing rules are non-trivial, or raw request-per-second ceiling actually matters at your scale
Choose Caddy if: it's one VPS, a handful of apps, and TLS should be a solved problem the moment a domain is added
Choose Traefik if: Docker (or Coolify, or another Docker-first platform) is already the deployment unit and config should live with the container, not a separate file
Don't choose based on which one a tutorial happened to use — that's the actual mistake, more than any of the three being objectively wrong
Does the choice affect anything beyond TLS and routing?
Yes, and this is where it connects back to whatever's running behind the proxy. Streaming responses — Suspense boundaries in a Next.js App Router app, for instance — need the proxy to not buffer the response before forwarding it, which is a config line on nginx (disabling buffering, setting X-Accel-Buffering to no) and works differently, or by default, on the other two. Compression, HTTP/2 negotiation, and header pass-through all vary by proxy too, and "my app is slow behind my reverse proxy" is frequently a proxy default doing something the app author didn't expect, not the app itself.
Situation | Right proxy | Why |
|---|---|---|
Solo dev, one VPS, three or four apps, no Docker orchestration layer | Caddy | Automatic HTTPS with the least config of the three; nothing else needed at this scale |
Running Coolify, Dokploy, or another Docker-native PaaS | Traefik (usually already the default) | Label-driven discovery matches how the platform deploys containers |
Migrating an existing complex nginx setup, or need it for raw throughput | Nginx | No reason to rewrite working, well-understood config for its own sake |
Many domains on one box, want the least ongoing certificate maintenance | Caddy, or Traefik if already Docker-based | Both automate renewal; pick based on whether Docker labels already define the routing |
What does day-two operations actually look like with each one?
The first deploy is never the interesting part. What matters more is the fiftieth one — adding an app six months in, rotating a certificate that's misbehaving, or debugging why one specific route is returning the wrong headers. With Nginx, that fiftieth deploy means opening the same config file everyone already knows how to find, adding a server block, and reloading — genuinely simple once the file exists, and exactly as manual every single time. With Caddy, it means adding a few lines to the Caddyfile and reloading; the certificate side never comes up again because it was never a manual step to begin with. With Traefik, it means adding labels to a new container's Compose definition or Coolify resource config and deploying — no proxy config file gets touched at all, because there isn't a central one to touch.
The failure modes differ the same way. An Nginx misconfiguration is usually visible immediately — a syntax error on reload, a 502 with a specific log line pointing at the broken block. A Traefik misconfiguration is often a label typo on one container, which means the debugging starts at that one container's definition, not at a central file, and it's easy to spend ten minutes looking in the wrong place the first few times this happens. Caddy sits closer to Nginx here: config errors on reload are loud and specific, which is part of why it stays approachable even for someone who's never run a reverse proxy before. XenGrowth on building one SEO and GEO content system approaches this from the AI search, GEO and discovery side.
What does switching between them later actually cost?
More than most people budget for, which is a real argument for picking deliberately the first time rather than defaulting to whichever one a tutorial happened to use. Moving from Nginx to Caddy means translating every server and location block into Caddyfile syntax by hand — mechanical, but tedious across more than a handful of routes, and the two config languages don't map one-to-one for every feature Nginx supports. Moving to Traefik is a bigger shift in mental model, not just syntax: every routing rule has to move from a central file into per-container labels, which usually means touching every Compose file or every resource definition in whatever platform is doing the deploying, not just one config file in one place. None of this is a reason to avoid switching when the deployment model genuinely changes — moving to a Docker-first, multi-app host is exactly when Traefik earns the switch — but it is a reason not to bounce between the three casually. A proxy chosen to match last year's setup and never revisited is a common way self-hosted infrastructure quietly accumulates the wrong tool for the job it's actually doing now.
Do any of them support wildcard certificates the same way?
All three can, through the same underlying mechanism — DNS-01 validation against a DNS provider's API instead of the usual HTTP-01 challenge — but how much configuration that takes varies. Traefik's DNS challenge support is well-documented specifically because Coolify and similar platforms lean on it for exactly this; pointing it at Cloudflare is a couple of environment variables and a provider flag. Caddy has DNS provider plugins for the same purpose, though they're a separate module to add rather than built into the base binary. Nginx has no ACME client at all, wildcard or otherwise, so this always means a separate tool — typically certbot with a DNS plugin — managing certificates that Nginx then just serves once they exist on disk. The mechanics of why a wildcard needs DNS-01 in the first place — and what that setup looks like end to end — get a full walkthrough on their own rather than a detour here.
Nginx asks you to write the routing. Caddy asks you to trust the defaults. Traefik asks you to label your containers and walk away. All three answers are correct for a different question.
What would actually make me switch?
Running enough apps behind Coolify or a similar Docker-native platform that manually managing a separate nginx config for each one stopped making sense — that's the case for Traefik, and it's the one already in play on this stack. Needing custom routing complex enough that label annotations become harder to read than a config file would be the case for going back to Nginx. Neither applies right now, which is exactly the test that matters here: the proxy earning its keep isn't about which one is theoretically best, it's about which one stops being invisible the moment the deployment model changes underneath it.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
Working on nginx vs caddy vs traefik for self-hosting inside a commercial team? XenGrowth's marketing operations practice publishes operator guides on the revenue side of this work.
Five questions about how the box is deployed, not which proxy you've already heard of. It follows the same reasoning as the post — the deploy model decides this more than any feature comparison does.













