Reverse Proxy Explained: Caddy vs. Nginx vs. Traefik for People Who Just Want HTTPS
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:
- Listens on 443 (and 80) as the single public entry point.
- Terminates TLS — handles the HTTPS certificate so your apps do not have to.
- Routes by hostname —
wiki.example.comgoes tolocalhost:3000,stats.example.comgoes tolocalhost:8000. - 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:3000stats.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
| Caddy | Nginx | Traefik | |
|---|---|---|---|
| Automatic HTTPS | Built in | Via certbot | Built in |
| Config style | One tiny file | One block per site | Labels on containers |
| Best for | Simple host-to-port routing | Familiarity, fine control | Dynamic Docker setups |
| Learning curve | Lowest | Medium | Highest |
| Certificate renewal | Automatic | Separate timer to watch | Automatic |
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.
PostgreSQL vs. SQLite for Self-Hosted Apps: Which Should You Actually Use?
SQLite is not a toy and Postgres is not always overkill. A practical look at when each one is the right call for a self-hosted app, with concrete scenarios.
Backing Up a Self-Hosted App the Right Way: The 3-2-1 Rule in Practice
Most self-hosted backups are a cron job nobody has ever tested. Here is how to apply the 3-2-1 rule with real commands, and how to run a restore drill so you know it works.
A Docker Compose Starter Stack for Your First Self-Hosted Setup
The handful of pieces every self-hosted setup needs before the apps: a reverse proxy, automatic HTTPS, backups, and updates. A calm walkthrough you can finish in an afternoon.