Back to blog
Self-Hosting
Networking

Reverse Proxy Explained: Caddy vs. Nginx vs. Traefik for People Who Just Want HTTPS

Mohsen Karimi4 min read
Reverse Proxy Explained: Caddy vs. Nginx vs. Traefik for People Who Just Want HTTPS header image

If you run more than one self-hosted app, you need a reverse proxy. This post explains what that means in plain terms, then shows you the exact same setup in the three tools people actually use, so you can pick one and move on.

What a reverse proxy does

Your apps listen on plain HTTP ports on localhost: the wiki on 3000, the analytics tool on 8000, the git server on 4000. You do not want to expose those directly. A reverse proxy sits in front of all of them and does four jobs:

  1. Listens on 443 (and 80) as the single public entry point.
  2. Terminates TLS — handles the HTTPS certificate so your apps do not have to.
  3. Routes by hostname — wiki.example.com goes to localhost:3000, stats.example.com goes to localhost:8000.
  4. Adds cross-cutting behaviour — compression, rate limits, security headers, basic auth on an admin panel.

Without one, you are juggling ports, certificates per app, and no shared place for headers or auth.

The task we will configure

Route two subdomains to two local apps, with automatic HTTPS:

  • wiki.example.com → localhost:3000
  • stats.example.com → localhost:8000

Caddy

Caddy's headline feature is that HTTPS is automatic and on by default. It requests and renews Let's Encrypt certificates with zero configuration. The entire Caddyfile:

wiki.example.com {
    reverse_proxy localhost:3000
}

stats.example.com {
    reverse_proxy localhost:8000
}

That is the whole thing. Reload with caddy reload. Certificates, renewals, HTTP-to-HTTPS redirects, and modern TLS settings are handled.

Choose Caddy if: you want the least config, you are new to reverse proxies, or your setup is "point subdomains at local ports." The trade-off is that it is less familiar to traditional ops teams and has fewer Stack Overflow answers than Nginx.

Nginx

Nginx is the one every tutorial assumes. It is fast, battle-tested, and everywhere, but it does not manage certificates itself — you pair it with certbot.

server {
    listen 443 ssl;
    server_name wiki.example.com;

    ssl_certificate     /etc/letsencrypt/live/wiki.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/wiki.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name wiki.example.com;
    return 301 https://$host$request_uri;
}

Repeat for stats.example.com. Get the certs with:

certbot --nginx -d wiki.example.com -d stats.example.com

certbot installs a renewal timer. If that timer ever silently fails, your site goes dark when the cert expires — so monitor expiry.

Choose Nginx if: you already know it, you need its performance tuning knobs, or you are handing this to people who expect Nginx. The trade-off is more boilerplate per site and certificate management as a separate moving part.

Traefik

Traefik is built for containers. Instead of a config file per app, each container declares its own routing through labels, and Traefik picks it up live as containers start and stop.

services:
  traefik:
    image: traefik:v3
    command:
      - --providers.docker=true
      - --entrypoints.websecure.address=:443
      - [email protected]
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  wiki:
    image: requarks/wiki:2
    labels:
      - traefik.enable=true
      - traefik.http.routers.wiki.rule=Host(`wiki.example.com`)
      - traefik.http.routers.wiki.tls.certresolver=le
      - traefik.http.services.wiki.loadbalancer.server.port=3000

Add a new app and it routes itself the moment it comes up. Like Caddy, ACME certificates are built in.

Choose Traefik if: you run everything in Docker or a cluster, you add and remove services often, or you want routing to live next to each service. The trade-off is a steeper learning curve and more concepts (routers, services, middlewares, entrypoints, providers) before the first success.

Picking one

CaddyNginxTraefik
Automatic HTTPSBuilt inVia certbotBuilt in
Config styleOne tiny fileOne block per siteLabels on containers
Best forSimple host-to-port routingFamiliarity, fine controlDynamic Docker setups
Learning curveLowestMediumHighest
Certificate renewalAutomaticSeparate timer to watchAutomatic

For a first self-hosted server that is mostly "subdomain in, local port out," start with Caddy — you will have working HTTPS in ten minutes. Move to Traefik if your stack becomes container-heavy and you are tired of editing a proxy config every time you add a service. Reach for Nginx when you specifically need what Nginx gives you, or when it is what your team already runs.

Related posts

More on Self-Hosting, Networking.