Traefik vs Caddy: Which Reverse Proxy Should You Use in 2026?

Traefik vs Caddy: Which Reverse Proxy Should You Use in 2026?

Traefik and Caddy both handle HTTPS well, but their workflows could not be more different. Pick the right reverse proxy for your Docker stack.

Traefik vs Caddy: Which Reverse Proxy Should You Use in 2026?

A reverse proxy is supposed to be boring. It should receive a request, send it to the right container, handle TLS, and quietly stay out of your way.

Then you put ten Docker apps behind one domain and discover that “boring” has two very different meanings. Caddy is boring because its config reads like English. Traefik is boring once you understand labels, routers, middlewares, entrypoints, and why one missing backtick can make an app disappear.

I would not call either one universally better. I would call Caddy the better default for a small, hand-managed stack, while Traefik earns its complexity when containers are created and destroyed all the time.

The short version

Choose Caddy when:

  • You run a handful of stable apps.
  • You want to read one config file and understand what it does.
  • You prefer explicit configuration over Docker label archaeology.
  • You want a proxy that is friendly to a first VPS.

Choose Traefik when:

  • Docker Compose is your main deployment interface.
  • You run many services, stacks, or multiple Docker hosts.
  • You want routes to appear from labels without editing a central proxy file.
  • You need advanced routing, authentication, and observability in the proxy itself.

My opinion: start with Caddy unless you can already explain a Traefik router, service, and middleware without opening documentation. Moving to Traefik later is easier than spending a weekend debugging it because you thought labels would be simpler.

What both proxies get right

Both projects solve the baseline job well:

  • Automatic HTTPS certificates through Let’s Encrypt or another ACME provider.
  • HTTP to HTTPS redirects.
  • Reverse proxying to containers and local services.
  • WebSocket support.
  • HTTP/2 and modern TLS defaults.
  • Docker deployments.

That means the decision is rarely about raw performance. A personal dashboard, Nextcloud instance, or small SaaS will not notice which proxy you picked. The real difference is how you describe routing and how much automation you want.

Caddy: the configuration I can still read six months later

Caddy’s main selling point is not that it obtains certificates automatically. Traefik does that too. Its real advantage is that a basic Caddyfile is almost embarrassingly legible.

cloud.example.com {
  reverse_proxy nextcloud:80
}

status.example.com {
  reverse_proxy uptime-kuma:3001
}

That is the whole idea. Requests for each hostname go to the named upstream. Caddy will obtain and renew certificates by default when DNS and ports are correct.

For a small Compose stack, I like keeping the routing rules in Git next to the rest of the infrastructure. When something breaks, I open one file. There is no hunt through labels scattered across seven compose.yml files.

A Caddy Compose setup

Here is a small but real-world starting point. It assumes the applications share an external Docker network named proxy.

services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy

volumes:
  caddy_data:
  caddy_config:

networks:
  proxy:
    external: true

And an app joins the same network:

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    restart: unless-stopped
    volumes:
      - ./data:/app/data
    networks:
      - proxy

networks:
  proxy:
    external: true

The Caddyfile stays explicit:

uptime.example.com {
  reverse_proxy uptime-kuma:3001
}

I prefer this over exposing every app port to the host. Only Caddy listens publicly; the app is reachable on the internal Docker network.

Caddy’s weak spots

Caddy becomes less pleasant when the proxy needs to discover a large number of ephemeral services. You can use Docker label plugins such as caddy-docker-proxy, but that adds a non-core component and moves you toward Traefik’s model anyway.

It also has fewer ready-made examples for every niche Docker integration. The official docs are good, but Traefik has become the default answer in many Compose projects, so copy-paste snippets are everywhere. That is useful until the snippet is three versions old.

Traefik: Docker-native routing with a real learning curve

Traefik watches Docker and builds routes from container labels. In a busy environment, that is excellent. Deploy a container with the right labels and it becomes available automatically. Remove the stack and the route goes away with it.

The cost is conceptual overhead. A router matches a request. A service points at a backend. A middleware changes the request or response. EntryPoints define listeners. Once that model clicks, Traefik is powerful. Before it clicks, its dashboard can feel like an airport departures board during a storm.

A minimal Traefik setup

This is a deliberately small Compose example. It exposes ports 80 and 443, watches Docker, and stores ACME certificate data in a mounted file.

services:
  traefik:
    image: traefik:v3.5
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - [email protected]
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy

networks:
  proxy:
    external: true

Then each app supplies its own route:

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    restart: unless-stopped
    volumes:
      - ./data:/app/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.uptime.rule=Host(`uptime.example.com`)
      - traefik.http.routers.uptime.entrypoints=websecure
      - traefik.http.routers.uptime.tls.certresolver=letsencrypt
      - traefik.http.services.uptime.loadbalancer.server.port=3001

networks:
  proxy:
    external: true

That local ownership is Traefik’s superpower. The route travels with the application. A teammate can deploy the stack on another host without separately editing a shared Caddyfile.

The Docker socket problem

Do not gloss over the Docker socket mount. Traefik needs it to discover containers, but access to /var/run/docker.sock is extremely privileged. Read-only mounts reduce some risks, but they do not transform the Docker API into a low-risk interface.

For a larger setup, put a Docker socket proxy in front of Traefik and allow only the API endpoints it needs. Our Docker socket proxy guide covers that pattern. It is one of the few security additions that is worth the extra container.

Day-to-day operations: where the choice becomes obvious

With Caddy, adding an app is usually two edits: add the app to its Compose file, then add a site block to the Caddyfile. Reload Caddy and move on.

With Traefik, adding an app normally means adding labels to the app’s Compose file. That is one less central edit, and it matters when there are many independent stacks. It is also easy to misspell a label and accidentally create a route that never matches.

Here is the practical comparison:

CaddyTraefik
Best forSmall, stable stacksMany dynamic Docker services
Routing configCentral CaddyfileLabels beside each app
Learning curveLowMedium to high
Docker discoveryOptional pluginBuilt in
Advanced routingStrong, readableExtremely flexible
DashboardNot the main workflowBuilt in, useful for debugging
Good first proxyYesOnly if you enjoy infrastructure details

I have watched people choose Traefik for a three-container homelab, configure eleven labels per service, then avoid updating the stack because they no longer trust themselves to touch it. That is not automation. That is operational debt with a nice dashboard.

Security is more about exposure than the proxy brand

Neither proxy makes an unsafe app safe. HTTPS is necessary, but it does not fix weak admin passwords, a public database port, or an unpatched container.

The baseline I use is simple:

  1. Expose only ports 80 and 443 on the public host.
  2. Keep application ports inside an internal Docker network.
  3. Put admin tools such as Portainer and Grafana behind a VPN or extra authentication.
  4. Use explicit health checks and monitor the public URL, not only the container state.
  5. Keep the proxy and its certificate storage in backups.

For private dashboards, a mesh VPN is usually cleaner than adding layers of public authentication. See our Tailscale self-hosting guide for the pattern. I would rather not expose an admin panel at all than spend an afternoon tuning a login page for it.

🚀NordVPN

Secure your server administration sessions when you need a trusted VPN outside your home network.

Get NordVPN →

Affiliate link — we may earn a commission at no extra cost to you.

When I would use Caddy

I would pick Caddy for a single VPS with a blog, Uptime Kuma, a file service, and maybe a couple of personal apps. The Caddyfile becomes a compact map of your public services, which is exactly what I want at 1 AM.

It is also ideal if you are learning reverse proxies for the first time. You see the hostname, the upstream, and any security rule in one obvious block. That clarity is not a beginner compromise. It is a maintenance feature.

If you are using Docker Compose profiles to keep optional services tidy, Caddy’s central config is still simple to audit. Just remove the route before bringing a profile down, or accept that a temporary 502 is the honest signal that the service is offline.

When I would use Traefik

I would choose Traefik for a server that behaves more like a platform than a fixed appliance. Think multiple app repositories, preview environments, a deployment tool like Coolify, or stacks owned by different people.

Traefik is also the sensible choice when you need routers based on paths, headers, or multiple hostnames, then want reusable middlewares for security headers, redirects, rate limits, and forward authentication. Caddy can do much of this, but Traefik makes these building blocks first-class.

If you already run a lot of Compose files, keep the labels formatted and documented. Never make the next person infer why traefik.http.routers.app.middlewares=security@file exists. That person is often you after a long weekend.

A migration plan that does not ruin your evening

Do not run two public proxies on ports 80 and 443 at the same time and improvise. Migrations are straightforward if you treat them as a small change window.

  1. Back up the existing proxy config and certificate data.
  2. List every hostname and backend port currently in use.
  3. Create the new proxy configuration while the old proxy still runs, but do not bind public ports yet.
  4. Stop the old proxy, start the new one, and test every hostname with curl -I.
  5. Check WebSocket-heavy apps, authentication flows, and uploads separately.
  6. Keep the old config for rollback until you have observed the new setup for a few days.

The common migration failure is forgetting that a service was reachable through a path prefix or needed a forwarded header. Write down the existing behavior first. Reverse proxies are invisible when they work, which makes undocumented special cases remarkably easy to forget.

The verdict

Caddy is my recommendation for most self-hosters. It is capable, secure by default, and much easier to reason about when your stack is small or medium-sized.

Traefik is the better tool when your infrastructure is dynamic enough to benefit from service discovery. It turns Docker labels into an application-level routing API, but it demands that you understand the moving parts.

Pick the proxy you will confidently update. The “best” reverse proxy is not the one with the longest feature list. It is the one you can repair before your friends start texting that the server is down.

Next step

If you are starting fresh, deploy Caddy with one non-critical service, confirm HTTPS works, then add apps one at a time. If your routes already belong to dozens of Compose stacks, give Traefik a test environment and learn its router-service-middleware model before moving production traffic.

Either way, put the proxy configuration in Git and test your backups. Certificates can be reissued. The time spent reconstructing twenty forgotten routes is what hurts.

Resources

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.