Nginx vs Caddy vs Traefik for Self-Hosting
Cloud

Nginx vs Caddy vs Traefik for Self-Hosting

Three reverse proxies, three completely different config philosophies. One wants a text file, one wants a single line, one wants to read your Docker labels. Here's which one actually fits a self-hosted VPS, and why Coolify picked the one it did.

Published November 22, 202511 min readUpdated Sep 6, 2026

Written by · Full-Stack Agentic AI Software Engineer — AI Agents, Automation & Revenue Systems for GTM/RevOps teams

In brief

Which reverse proxy — Nginx, Caddy, or Traefik — should actually front a self-hosted app, and why does it matter which one?

Caddy wins for a solo VPS where the whole point is not thinking about TLS again: automatic HTTPS with zero extra config, out of the box. Traefik wins the moment Docker is the unit of deployment, because it reads container labels directly and reconfigures itself on every deploy with no restart — which is exactly why Coolify ships it as the default. Nginx wins when raw throughput, mature tooling, or existing config already exists, at the cost of writing and renewing certificates yourself unless something else handles that. There's no universally correct answer, but there is a correct answer for a given setup, and picking based on habit instead of what the setup actually needs is the mistake.

  • Caddy issues and renews HTTPS certificates automatically with no separate certbot process — this is its entire reason to exist
  • Traefik discovers services from Docker labels and updates its own routing live, with no restart, which is why Coolify uses it by default
  • Nginx has no built-in ACME client; automatic HTTPS needs a companion like nginx-proxy plus acme-companion, or a manual certbot cron job
  • Config ergonomics and raw performance pull in opposite directions here — Nginx is still the throughput ceiling, Caddy and Traefik trade some of that for far less operator effort
  • The right choice tracks the deployment model, not personal preference: static single-app box favors Caddy, Docker-first multi-app box favors Traefik, everything-custom setup favors Nginx

Evidence notes

Coolify ships Traefik by default

Coolify's own documentation and changelog confirm Traefik is the default reverse proxy for automatic domain routing and Let's Encrypt certificate issuance across managed resources.

Next.js recommends a reverse proxy in front of next start

Next.js's self-hosting guide names a reverse proxy as the documented recommendation for handling malformed requests, slow-connection attacks, and payload limits ahead of the Node process.

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

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.

Which proxy actually fits your deploy model?

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.

1 / 5
How do apps get deployed on this box?

Apply this article

How to turn insights into execution

A practical sequence for teams turning concepts into production outcomes.

NginxCaddyTraefikReverse ProxySelf-HostingDockerHTTPScloud

Audit your current state

Map the bottlenecks and constraints connected to the article’s core problem.

Choose one bounded change

Test the most useful recommendation on one workflow before widening the scope.

Measure what changed

Keep the parts that improve the work, document what failed, and make the next decision from evidence.

Next step

Need help applying this in your stack?

I can translate these patterns into a concrete implementation plan for your team.

Discuss implementationBack to blog

Replies usually within 24 hours.

Next Steps

Continue reading

Don't Self-Host Until You Understand These 7 Things

This isn't a gate to keep you out. It's a readiness check — seven things worth being honest with yourself about before you're the one holding the pager, because a managed platform is still the right call for a lot of people right now.

Navigate

How I Self-Host PostgreSQL for My SaaS (and When I Wouldn't)

Running Postgres in a container is easy. Running it in a way that survives a redeploy, a full disk, and an eventual major-version upgrade is the actual job. Here's the setup, tuned against Postgres's own defaults, and the honest list of where managed wins outright.

Navigate

10 Mistakes That Break a Self-Hosted SaaS

None of these ten show up as a single dramatic outage. They show up as a disk that quietly fills, a rollback that turns out to be impossible, a backup nobody ever restored. Here's the mechanism behind each one, and the fix.

Navigate

How I Secure a Fresh VPS Before Deploying Anything

A brand-new VPS gets scanned within minutes of getting an IP address. Here's the exact order I run through before a single container touches the box — and which of these steps are real protection versus which ones are just theatre.

Navigate

My Free Monitoring Stack for Self-Hosted Apps

Vercel gives you monitoring whether you ask for it or not. A VPS gives you a blank terminal and the assumption you'll figure it out. Here's what to actually watch on a self-hosted box, with tools that cost nothing, and why watching from the box itself is the one setup that will lie to you.

Navigate

The Security Mistakes I See New Self-Hosters Make

These aren't rare. They're the same seven patterns, documented in breach reports, CVE databases, and botnet postmortems, showing up on new self-hosted boxes on a loop — because the defaults that make setup fast are the same defaults that make a box exploitable.

Navigate

Cloudflare Tunnel vs Reverse Proxy: Which One Should You Use?

One of these opens no inbound ports and works behind CGNAT. The other is simpler, portable, and doesn't ask you to trust a daemon or a vendor's uptime with every request. Neither one is the obviously correct default — the right answer depends on which failure you'd rather own.

Navigate

Zero-Downtime Deploys and Instant Rollbacks on a Cheap VPS

docker compose up -d looks like a deploy and behaves like an outage — there's a gap between the old container stopping and the new one answering requests, and on a small box that gap is exactly where a real user lands.

Navigate

What Happens When Your VPS Runs Out of RAM: The OOM Killer, Explained

There's no warning banner before the kernel kills something. One moment the box is fine, the next a process is dead mid-request — and the process that dies is often not the one that caused the spike. Here's the actual mechanism, and how to read the wreckage afterward.

Navigate

Why Next.js Feels Slow on a VPS — and the Six Things That Fix It

The app that felt instant on Vercel goes home to a cheap VPS and forgets how to load fast. The framework didn't change. Six specific things underneath it did, and none of them are next/image.

Navigate