Traefik vs Caddy vs Nginx Proxy Manager: Reverse Proxy Showdown 2026

Every self-hoster hits the same wall: you’ve got a dozen services running on different ports, and you want clean URLs with HTTPS. You need a reverse proxy. The question is which one.

Traefik, Caddy, and Nginx Proxy Manager are the three most popular choices in the self-hosting world. They all solve the same core problem — route app.yourdomain.com to the right container with automatic SSL — but they do it very differently.

This isn’t a “they’re all great!” comparison. Each one has real strengths and real trade-offs. Let’s figure out which one actually fits your setup.

The Quick Answer

If you want to skip the deep dive:

  • Nginx Proxy Manager → You’re new to self-hosting and want a web UI
  • Caddy → You want dead-simple config files with automatic HTTPS
  • Traefik → You run many containers and want auto-discovery via Docker labels

Now let’s get into why.

At a Glance

FeatureTraefikCaddyNginx Proxy Manager
Config methodDocker labels + YAML/TOMLCaddyfile or JSONWeb UI
Auto HTTPS✅ Let’s Encrypt / ZeroSSL✅ Let’s Encrypt / ZeroSSL✅ Let’s Encrypt (manual per host)
Docker auto-discovery✅ Native❌ Manual config❌ Manual via UI
Web dashboard✅ Read-only❌ None built-in✅ Full management UI
HTTP/3 (QUIC)✅ Experimental✅ Default❌ Not supported
Memory usage~50-80 MB~20-40 MB~100-150 MB (3 containers)
Config reloadHot reloadHot reloadRequires UI save
Learning curveSteepLowVery low
Written inGoGoNode.js + Nginx
LicenseMITApache 2.0MIT
Best forDynamic container environmentsSimple to mid-size setupsBeginners, GUI lovers

Nginx Proxy Manager: The Visual Approach

What It Is

Nginx Proxy Manager (NPM) wraps the battle-tested Nginx web server in a web-based management UI. You add proxy hosts through a form, click a button for SSL, and you’re done. No config files to edit.

Setup

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"    # Admin UI
    volumes:
      - npm_data:/data
      - npm_letsencrypt:/etc/letsencrypt
    healthcheck:
      test: ["CMD", "/usr/bin/check-health"]
      interval: 30s
      timeout: 10s

volumes:
  npm_data:
  npm_letsencrypt:

Default login: [email protected] / changeme. You’ll be prompted to change it immediately.

Adding a Service

  1. Go to Proxy Hosts → Add Proxy Host
  2. Enter domain name: jellyfin.yourdomain.com
  3. Forward hostname: jellyfin (container name) or IP
  4. Forward port: 8096
  5. Click SSL tab → Request Let’s Encrypt certificate → Force SSL
  6. Save

That’s it. No YAML, no labels, no config files.

Strengths

  • Lowest barrier to entry. If you can fill out a web form, you can set up a reverse proxy.
  • Visual certificate management. See all your SSL certs, expiry dates, and renewal status.
  • Access lists built in. Restrict services to specific IPs or add basic HTTP auth without touching config files.
  • Custom Nginx config. You can add raw Nginx directives per host for advanced use cases.

Weaknesses

  • No auto-discovery. Every new container needs a manual entry in the UI. At 20+ services, this gets tedious.
  • No HTTP/3. NPM is built on Nginx, and the underlying version doesn’t ship with QUIC support.
  • Database dependency. NPM stores config in SQLite/MariaDB, adding a failure point vs. flat config files.
  • Stale development periods. The project has had gaps in maintenance. Check the GitHub pulse before committing.
  • Three containers. The full stack runs Nginx, a Node.js management app, and a database — heavier than a single binary.

Caddy: The Config File Sweet Spot

What It Is

Caddy is a modern web server written in Go that treats HTTPS as the default, not an add-on. Its config language (the Caddyfile) is remarkably concise — a reverse proxy rule is two lines.

Setup

services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"  # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    healthcheck:
      test: ["CMD", "caddy", "validate", "--config", "/etc/caddy/Caddyfile"]
      interval: 30s
      timeout: 10s

volumes:
  caddy_data:
  caddy_config:

Adding a Service

Edit your Caddyfile:

j}g}n}eieltxletyrarcrfe.eleivyvovneoeue.rurdrysrs.soedeyeu_o_o_rpmpupdrarrrooiodomxnxoxay.ymyicanjogin.emineclt.xol{ectmyaocf:ml{i3on0{u:0d80:08906

Reload without downtime:

docker exec caddy caddy reload --config /etc/caddy/Caddyfile

HTTPS certificates are automatic. No extra config. No certbot. No cron jobs.

Strengths

  • Simplest config syntax. Two lines per service. Compare that to Nginx’s 15-line server blocks.
  • Automatic HTTPS by default. Every site gets a certificate. You have to explicitly opt out of HTTPS.
  • HTTP/3 out of the box. QUIC support is enabled by default — no experimental flags.
  • Single binary, low memory. ~20-40 MB RAM. No database, no extra containers.
  • Hot reload. Change the Caddyfile and reload without dropping connections.
  • JSON API. Full programmatic control via REST API for automation.

Weaknesses

  • No Docker auto-discovery. You must manually add each service to the Caddyfile. Community plugins like caddy-docker-proxy exist but add complexity.
  • No built-in dashboard. You’re managing everything through text files.
  • Smaller plugin ecosystem. Nginx and Traefik have larger communities and more middleware options.
  • Less familiar. If your team knows Nginx config syntax, Caddy’s format is one more thing to learn.

Traefik: The Docker-Native Powerhouse

What It Is

Traefik is a cloud-native reverse proxy that automatically discovers services from Docker, Kubernetes, and other orchestrators. You configure routing through Docker labels on your containers — Traefik watches the Docker socket and creates routes in real time.

Setup

services:
  traefik:
    image: traefik:v3
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api.dashboard=true"
      - "--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
      - traefik_letsencrypt:/letsencrypt
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
    healthcheck:
      test: ["CMD", "traefik", "healthcheck"]
      interval: 30s
      timeout: 10s

volumes:
  traefik_letsencrypt:

Adding a Service

Add labels to your container’s docker-compose.yml:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.yourdomain.com`)"
      - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"

That’s it. Restart the container, Traefik detects it, creates the route, and provisions an SSL certificate. No central config file to update.

Strengths

  • True auto-discovery. New container with the right labels? Traefik picks it up automatically. Remove the container? Route disappears. This is transformative at scale.
  • Dashboard. Real-time view of all routers, services, middlewares, and their health status.
  • Middleware ecosystem. Rate limiting, IP whitelisting, basic auth, header manipulation, circuit breakers, retry logic — all composable and stackable.
  • Multi-provider support. Docker, Kubernetes, Consul, file-based, ECS — Traefik integrates with everything.
  • Canary deployments and load balancing. Weighted routing, sticky sessions, health checks — production-grade features if you need them.

Weaknesses

  • Steep learning curve. Routers, services, middlewares, entrypoints, providers, resolvers — the concept model is deep. Expect to spend a few hours reading docs before your first successful deploy.
  • Verbose labels. Four labels per service minimum. At 30 containers, that’s a lot of label management spread across many compose files.
  • Docker socket access. Traefik needs read access to the Docker socket, which is a security consideration. Mitigate with a Docker socket proxy.
  • Higher memory. ~50-80 MB base, growing with the number of routes and middleware chains.
  • Debugging is harder. When something doesn’t route correctly, the debug output is verbose and the error messages aren’t always clear.

Head-to-Head: Real Scenarios

Scenario 1: “I just started self-hosting with 3-5 services”

Winner: Caddy. Write a 10-line Caddyfile and forget about it. NPM also works great here, but you’re learning a UI that you’ll outgrow. Caddy scales naturally as you add services.

Scenario 2: “I run 20+ containers and add new ones weekly”

Winner: Traefik. Auto-discovery eliminates the manual step of updating a central config every time you spin up a new container. The upfront investment in learning Traefik pays back quickly at this scale.

Scenario 3: “I want my non-technical family member to manage proxy hosts”

Winner: Nginx Proxy Manager. The web UI is genuinely intuitive. Point-and-click SSL certificates, visual host management, and no terminal required.

Scenario 4: “Performance and resource usage matter”

Winner: Caddy. Lowest memory footprint, HTTP/3 by default, single binary. Traefik is close behind. NPM’s multi-container architecture is the heaviest.

Scenario 5: “I’m running Kubernetes”

Winner: Traefik. It’s the default ingress controller in k3s for a reason. Native Kubernetes Ingress and IngressRoute support. Caddy has community K8s integrations, but Traefik is the established choice here.

Scenario 6: “I need advanced middleware (rate limiting, auth, headers)”

Winner: Traefik. The middleware system is composable and powerful. Caddy handles common cases well but Traefik’s middleware library is deeper. NPM supports custom Nginx directives, but you’re back to writing config files at that point.

Performance Comparison

For most self-hosters, all three handle traffic without breaking a sweat — you’re rarely pushing more than a few hundred concurrent connections at home.

That said, the differences matter more for:

  • Latency-sensitive services (game streaming, real-time apps): Caddy’s HTTP/3 and lightweight architecture give it a slight edge.
  • Many concurrent routes (50+): Traefik’s dynamic routing handles large service counts without config reload delays.
  • Resource-constrained hosts (Raspberry Pi, small VPS): Caddy’s 20-40 MB footprint leaves more room for your actual services.

Security Considerations

All three support automatic HTTPS, but their security postures differ:

AspectTraefikCaddyNPM
Docker socket accessRequired (use socket proxy)Not neededNot needed
Default HTTPSOpt-in via labelsOn by defaultPer-host toggle
Security headersMiddleware configCaddyfile directivesCustom Nginx config
Auth middlewareBuilt-in (BasicAuth, ForwardAuth)Built-in (basic_auth)Access lists + basic auth
Vulnerability surfaceGo binaryGo binaryNode.js + Nginx + SQLite

Key takeaway: Caddy’s “secure by default” philosophy means you’re less likely to accidentally expose an unencrypted service. Traefik’s Docker socket requirement is a known attack vector — always use a socket proxy in production.

Migration Tips

Already using one and thinking about switching?

From NPM to Caddy

Export your proxy host list from the NPM database, then create a Caddyfile entry for each host. The mapping is straightforward: domain → reverse_proxy container:port.

From NPM to Traefik

Add Traefik labels to each container’s compose file. The biggest change is shifting from centralized config to distributed labels. Do it one service at a time — run both in parallel during migration.

From Caddy to Traefik

Convert each Caddyfile block into Docker labels. You’ll gain auto-discovery but lose the simplicity of the Caddyfile. Worth it if you’re regularly spinning up new services.

From Traefik to Caddy

Strip labels from your compose files and create corresponding Caddyfile entries. The config will be dramatically shorter. Consider this if you found Traefik’s complexity isn’t justified by your scale.

Which One Should You Pick?

Stop overthinking it. Here’s the decision tree:

  1. Are you brand new to self-hosting and want a GUI? → Nginx Proxy Manager
  2. Do you prefer config files and want minimal complexity? → Caddy
  3. Do you run many containers with frequent changes? → Traefik
  4. Are you on Kubernetes? → Traefik
  5. Do you just want something that works with the least effort? → Caddy

All three are production-capable. All three handle HTTPS automatically. The “wrong” choice still works — you’ll just be slightly annoyed by the things your proxy doesn’t do well.

Pick one, deploy it, and move on to the services that actually matter. You can always switch later.

Further Reading