Make sure Traefik works when you deploy
When you deploy to a Linux server (not local testing), Traefik is the reverse proxy that routes traffic by hostname to the API, frontend, and admin. Follow this checklist so routing and HTTPS work correctly.
Config verified for VPS: The self-host kit includes traefik/traefik.yml and traefik/dynamic/traefik-dynamic.yml set up for production: entrypoints 80/443/8080/8082, Docker provider (reads labels from api, frontend, admin, websocket-worker, chat-worker), file provider for middlewares. API has priority 1; /hubs/notification and /hubs/onlineusers go to websocket-worker; /hubs/chat and /hubs/chat-online go to chat-worker (priority 100). WebSocket upgrade is handled by Traefik automatically. On the server, run without docker-compose.local.yml so Traefik starts.
1. Use the right compose command (no local override)
On the server, do not use docker-compose.local.yml. That file is only for local testing and leaves Traefik stopped.
docker compose pull
docker compose up -d
This starts Traefik and all services. Traefik listens on 80 (HTTP) and 443 (HTTPS).
2. Set your real hostnames in .env
Traefik routes by hostname. Every TRAEFIK_*_HOST in .env must be set to the hostnames that will point to this server (no https://, no port).
Check before starting: from the folder that contains docker-compose.yml (e.g. the dockerPublish self-host kit) run:
./scripts/check-traefik-env.sh
This warns if TRAEFIK_API_HOST, TRAEFIK_FRONTEND_HOST, or TRAEFIK_ADMIN_HOST are missing or still contain placeholders like your-domain.
| Variable | Example | Must match |
|---|---|---|
TRAEFIK_API_HOST | api.yourdomain.com | DNS for API |
TRAEFIK_FRONTEND_HOST | app.yourdomain.com | DNS for main app |
TRAEFIK_ADMIN_HOST | admin.yourdomain.com | DNS for admin panel |
TRAEFIK_DASHBOARD_HOST | dashboard.yourdomain.com (optional) | If you expose Traefik dashboard |
TRAEFIK_DASHBOARD_IP | Your server's public IP | So http://YOUR_IP:8080 works for dashboard |
TRAEFIK_FRONTEND_WWW_HOST | www.yourdomain.com (optional) | For SEO: www is redirected 301 to main domain; set if you use www |
Also set the public URLs to match the same hostnames:
API_PUBLIC_URL=https://api.yourdomain.com
FRONTEND_PUBLIC_URL=https://app.yourdomain.com
ADMIN_PUBLIC_URL=https://admin.yourdomain.com
If you leave TRAEFIK_*_HOST as placeholders (e.g. api.your-domain.com or bellamybook.com), Traefik will only respond to those hostnames and your real domain will get 404 or wrong routing.
3. Configure Traefik dynamic file
File: traefik/dynamic/traefik-dynamic.yml (in the same folder as docker-compose.yml).
Traefik loads this file for middlewares (security headers, optional WebSocket-related headers) and for optional www→canonical redirects. The kit ships with placeholder values (your-domain.com, www.your-domain.com) in the redirect-www-to-canonical middleware and in the frontend-www-redirect / frontend-www-redirect-secure routers.
You need to align this file with your deployment:
| Situation | What to do |
|---|---|
You use www (e.g. www.yourdomain.com → app.yourdomain.com) | Replace every placeholder in traefik-dynamic.yml with your real domain. Set TRAEFIK_FRONTEND_WWW_HOST=www.yourdomain.com in .env, add www to CORS and Turnstile (see .env.example), and point DNS for www to this server. The redirect should send users to the same host you use as FRONTEND_PUBLIC_URL (your canonical frontend URL). |
You do not use www | Remove or comment out the redirect-www-to-canonical middleware block and the two frontend-www-redirect routers so Traefik does not register routes for www.your-domain.com. Otherwise you leave inactive stubs; cleaning them up avoids confusion and accidental copy-paste mistakes later. |
After editing, restart Traefik (e.g. docker compose up -d traefik) so the file provider reloads.
3b. Keep CSP config aligned (frontend/admin nginx)
Even when Traefik routes correctly, UI can still break if CSP in frontend/admin nginx blocks required domains.
Required files to review after security updates:
| File | Why it matters |
|---|---|
traefik/dynamic/traefik-dynamic.yml | Security headers and redirect routers loaded by Traefik file provider |
Src/frontend/nginx.conf | Frontend CSP (script-src, connect-src, img-src, media-src) |
Src/admin/nginx.conf | Admin CSP and API/WebSocket/storage allowlist |
Minimum domains typically needed in CSP:
- API + WS:
https://api.yourdomain.com,wss://api.yourdomain.com - Storage: your MinIO public URL or
https://r2.yourdomain.com - Cloudflare challenge/insights
- Optional analytics/livekit domains if enabled
If you use pre-built images only, nginx CSP changes require a new frontend/admin image tag from the publisher.
4. Point DNS to the server
For each hostname you use, create an A (or AAAA for IPv6) record pointing to this server's public IP.
Example: api.yourdomain.com → 203.0.113.10
Wait for DNS to propagate. Check with:
dig +short api.yourdomain.com
5. Open ports on the server
Traefik needs:
- 80 (HTTP)
- 443 (HTTPS)
Optional:
- 8080 – Traefik dashboard (only if you use
TRAEFIK_DASHBOARD_IPor a dashboard hostname)
On the server:
- Firewall: allow inbound 80, 443 (and 8080 if you use the dashboard).
- Cloud / security groups: allow the same ports to this host.
6. HTTPS (recommended for production)
By default, Traefik has HTTP (80) and HTTPS (443) entrypoints, but no TLS certificate provider is enabled. Configure one of the following.
Option A – Let's Encrypt (Traefik on the server)
- In
traefik/traefik.yml, uncomment thecertificatesResolversblock and set your email. - Ensure the
traefik_letsencryptvolume exists (it's in the compose file) and that the ACME storage file has correct permissions (e.g.chmod 600). - Attach the resolver to your HTTPS routers (e.g.
certificatesResolvers: letsencrypt) so Traefik requests certs for your hostnames.
Option B – Cloudflare Tunnel
- Run the tunnel in front of the server; Cloudflare terminates HTTPS.
- Point the tunnel to your server's HTTP (80) or HTTPS (443) as per Cloudflare docs. No need to enable Let's Encrypt in Traefik.
Option C – Another reverse proxy
- Terminate HTTPS on nginx, HAProxy, or Caddy and proxy to Traefik on 80 (or 443). Traefik itself doesn't need TLS.
7. Quick check that Traefik is working
-
Containers: Run
docker compose ps. Ensuretraefik,api,frontend, andadminare running. -
Traefik dashboard (optional): Open
http://YOUR_SERVER_IP:8080(setTRAEFIK_DASHBOARD_IPto that IP in.env). Check that HTTP routers exist for api, frontend, and admin with the correct hostnames. -
HTTP (port 80): From your machine:
curl -H "Host: app.yourdomain.com" http://YOUR_SERVER_IP/You should get the frontend (or a redirect), not a connection error or Traefik 404.
-
Real hostname: Once DNS points to the server:
curl -I https://app.yourdomain.com/(or
http://if HTTPS isn't set up yet). You should get 200 or 301/302, not 404.
Summary
| Step | Action |
|---|---|
| 1 | On server use docker compose up -d (no -f docker-compose.local.yml) |
| 2 | Set all TRAEFIK_*_HOST and *_PUBLIC_URL in .env to your domain hostnames; run ./scripts/check-traefik-env.sh |
| 3 | Edit traefik/dynamic/traefik-dynamic.yml: replace www/canonical placeholders with your domain, or remove the www redirect middleware and routers if you do not use www; restart Traefik |
| 4 | Point DNS (A/AAAA) for those hostnames (and www if used) to this server |
| 5 | Open ports 80 and 443 (and 8080 if using dashboard) |
| 6 | Configure HTTPS (Let's Encrypt, Cloudflare Tunnel, or another proxy) |
Next steps
- Self-Host with Pre-Built Images — Full installation
- Test the stack on your Mac — Try locally before deploying
- Environment Configuration — All variables and checklist