Once you are running more than about three self-hosted apps, remembering which one is on port 8096 and which is on 8181 stops being charming. A reverse proxy gives every service a real hostname and a real certificate. Traefik vs Nginx Proxy Manager is the choice most people land on, and the two work in genuinely different ways.
What the proxy is actually for
- One entry point on 80 and 443 instead of a dozen open ports.
- Automatic Let’s Encrypt certificates, renewed without you noticing.
- Hostnames like
jellyfin.home.example.comthat work identically on every device. - A single place to add security headers, basic auth, or an identity layer.
Nginx Proxy Manager: configuration in a browser
NPM is nginx plus a web UI and a small database. You add a proxy host in a form — domain, target IP, target port — tick “request a new SSL certificate”, and it works.
services:
npm:
image: jc21/nginx-proxy-manager:2.12.3
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81" # admin UI — never expose this publicly
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
The strength is obvious: someone who has never edited an nginx config can have HTTPS on five apps in twenty minutes. The weakness is equally obvious. Your routing lives in a SQLite database, not in a file you can read, diff or commit. Rebuilding from scratch means clicking through every host again unless you have that volume backed up, and you cannot review a change before it goes live.
Traefik: the container declares its own route
Traefik watches the Docker socket and builds its routing table from labels on your containers. There is no separate config to keep in step, because the route ships with the service it belongs to.
services:
traefik:
image: traefik:v3.3
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.websecure.address=:443
- --certificatesresolvers.le.acme.dnschallenge=true
- --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.le.acme.email=me@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
Adding a service is then four labels on that service, not a visit to a dashboard:
labels:
- traefik.enable=true
- traefik.http.routers.jellyfin.rule=Host(`jellyfin.example.com`)
- traefik.http.routers.jellyfin.entrypoints=websecure
- traefik.http.routers.jellyfin.tls.certresolver=le
- traefik.http.services.jellyfin.loadbalancer.server.port=8096
Mount the Docker socket read-only, and if you want to be careful about it, put a socket proxy in front so Traefik can only issue container reads rather than anything the daemon will accept.
Certificates: pick the right challenge
This is where people stall, and it is not really a proxy question at all.
HTTP-01
Let’s Encrypt connects to port 80 on your public IP to verify the domain. Simple, but it requires inbound port 80 to reach you, and it cannot issue wildcards. If your ISP uses CGNAT or blocks 80, it is a dead end.
DNS-01
The proxy writes a TXT record using your DNS provider’s API. Nothing inbound is needed, so it works on a purely internal network, and you can get one wildcard certificate for *.home.example.com covering every service forever. Both Traefik and NPM support it; with a Cloudflare token scoped to Zone:DNS:Edit on one zone, it takes five minutes. This is the option I use, and it also pairs neatly with the approach in my post on hosting on a Raspberry Pi with Docker and Cloudflare Tunnels.
One warning that catches everybody: test against the staging directory first. Let’s Encrypt rate limits are per registered domain per week, and a misconfigured resolver retrying in a loop will exhaust them before you have noticed.
Which one I would pick
If the stack is yours alone and it lives in git, Traefik. The labels sit next to the service they route, so there is no second source of truth to drift, and rebuilding the whole host is one docker compose up -d. The cost is a steeper first hour — Traefik’s routers, services and middlewares take a while to click, and its errors are quiet.
Choose Nginx Proxy Manager if other people need to add hosts, or if you would genuinely rather not learn another config dialect to run six containers on a Pi. Back up the data volume and it is a perfectly respectable answer. There is no wrong choice here, only an unbacked-up one.

Leave a Reply
You must be logged in to post a comment.