If you're running more than three self-hosted services, you've hit the port-hell wall. Jellyfin on 8096, Immich on 2283, Forgejo on 3000 β remembering which port maps to which service stops being cute fast. Traefik v3 solves this cleanly: one LXC container handles all ingress, terminates TLS, and routes by hostname automatically. No nginx blocks to hand-edit every time you add a service.
This is a full walkthrough: creating the LXC, configuring Traefik v3 with the Docker and file providers, wiring up Let's Encrypt with a Cloudflare DNS-01 challenge, and adding a middleware stack worth keeping. By the end, every service gets a real HTTPS subdomain with auto-renewing certs β no self-signed anything.
Why Traefik Over Nginx Proxy Manager?
NPM is fine for beginners. Traefik is better for engineers. The key difference: Traefik detects new Docker containers and reconfigures routing automatically via labels β no UI clicks, no manual config edits. You define the route in your docker-compose.yml and it's live. The file provider handles non-Docker services (bare LXCs, VMs, anything with an IP). Traefik v3, released in 2024, dropped a lot of legacy baggage and tightened the middleware API.
The trade-off is real: Traefik's config syntax has a learning curve. Plan 30-60 minutes the first time.
Hardware This Setup Runs On
I run this on a Beelink EQ12 mini PC as the Proxmox host β 16GB RAM, N100, pulls 6W idle. Plenty of headroom for a full home lab stack. Storage is a Samsung 870 EVO 1TB SATA SSD for the OS drive and a WD Red Plus 4TB NAS drive for bulk storage. Networking runs through a TP-Link TL-SG108E 8-port managed switch with static DHCP leases for every node.
If you're expanding to something bigger, the Minisforum MS-01 with dual 10GbE is worth the investment, and you'll want a CyberPower CP1500PFCLCD UPS to keep everything online during brief outages.
Step 1: Create the Traefik LXC
Use the community script β it's maintained, tested, and saves 20 minutes:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/traefik.sh)"
Accept defaults: Debian 12, 512MB RAM, 1 vCPU, 4GB disk. Assign it a static IP β I use 192.168.0.10 and reserve it at the router level. After creation, note the container ID. Set the hostname:
pct exec <CTID> -- hostnamectl set-hostname traefik
Step 2: Static Configuration (traefik.yaml)
The static config lives at /etc/traefik/traefik.yaml inside the LXC. This is Traefik's startup config β it does not hot-reload. SSH into the LXC:
pct enter <CTID>
nano /etc/traefik/traefik.yaml
Here's a production-ready static config:
global: checkNewVersion: false sendAnonymousUsage: falseapi: dashboard: true insecure: false
log: level: INFO filePath: /var/log/traefik/traefik.log
accessLog: filePath: /var/log/traefik/access.log
entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443" http: tls: certResolver: letsencrypt
certificatesResolvers: letsencrypt: acme: email: your@email.com storage: /etc/traefik/acme.json dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "8.8.8.8:53"
providers: file: directory: /etc/traefik/conf.d watch: true docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false network: traefik_net
Key decisions: HTTP auto-redirects to HTTPS at the entrypoint level, exposedByDefault: false means you opt-in containers instead of accidentally exposing everything, and the DNS-01 challenge means you don't need port 80 open externally β critical for home labs behind CGNAT.
Step 3: Cloudflare API Token for DNS-01
In Cloudflare: Profile β API Tokens β Create Token β "Edit zone DNS" template. Scope it to your specific zone. Copy the token.
Set it as an environment variable in Traefik's systemd unit:
systemctl edit traefik
Add:
[Service]
Environment="CF_DNS_API_TOKEN=your_cloudflare_token_here"
Create and lock down the ACME storage file:
touch /etc/traefik/acme.json
chmod 600 /etc/traefik/acme.json
Restart Traefik:
systemctl restart traefik
journalctl -u traefik -f
First run, test against the Let's Encrypt staging CA by adding caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" to the acme: block. Do this before going live or you'll hit rate limits.
Step 4: File Provider β Routing Non-Docker Services
The file provider watches /etc/traefik/conf.d/ and hot-reloads on change. Create one YAML per service or group them. Here's Proxmox itself on 192.168.0.100:8006:
# /etc/traefik/conf.d/proxmox.yaml http: routers: proxmox: rule: "Host(proxmox.yourdomain.com)" service: proxmox tls: certResolver: letsencrypt middlewares: - secure-headersservices: proxmox: loadBalancer: servers: - url: "https://192.168.0.100:8006" passHostHeader: true serversTransport: ignorecert
serversTransports: ignorecert: insecureSkipVerify: true
middlewares: secure-headers: headers: stsSeconds: 31536000 stsIncludeSubdomains: true contentTypeNosniff: true frameDeny: true browserXssFilter: true
insecureSkipVerify is necessary for Proxmox's self-signed cert β Traefik terminates TLS externally, so this is safe.
Step 5: Docker Provider β Automatic Container Routing
For Docker-based services, use labels in your docker-compose.yml. Here's Immich:
services: immich-server: image: ghcr.io/immich-app/immich-server:release networks: - traefik_net labels: - "traefik.enable=true" - "traefik.http.routers.immich.rule=Host(photos.yourdomain.com)" - "traefik.http.routers.immich.entrypoints=websecure" - "traefik.http.routers.immich.tls.certresolver=letsencrypt" - "traefik.http.services.immich.loadbalancer.server.port=2283"
networks: traefik_net: external: true
Create the shared network first:
docker network create traefik_net
Traefik sees the label, creates the router and service, and requests a cert β all without a restart. This is why engineers prefer Traefik over GUI-based alternatives.
Step 6: Securing the Dashboard
Don't run the dashboard with insecure: true. Add HTTP Basic Auth:
htpasswd -nb admin yourpassword
Paste the output into a file provider config:
# /etc/traefik/conf.d/dashboard.yaml http: routers: dashboard: rule: "Host(traefik.yourdomain.com) && (PathPrefix(/api) || PathPrefix(/dashboard))" service: api@internal tls: certResolver: letsencrypt middlewares: - dashboard-auth
middlewares: dashboard-auth: basicAuth: users: - "admin:$apr1$..." # output from htpasswd
Firewall and Port Forwarding
Open ports 80 and 443 on your router pointing to the Traefik LXC's static IP. That's the only external exposure. Everything else stays internal. If you've configured OPNsense as your Proxmox firewall, create a NAT rule there instead of relying on your ISP router β it gives you per-rule logging and easier VLAN segmentation.
If you're on CGNAT or prefer not to forward any ports, Traefik pairs cleanly with Cloudflare Tunnels: Traefik handles internal routing and TLS, the tunnel handles external access without port 443 open.
Common Gotchas
Certificate not issuing. 99% of the time it's acme.json permissions or a Cloudflare token scope mismatch. Check journalctl -u traefik -f immediately after restart β the error message is explicit.
502 Bad Gateway. Traefik reached the router but the backend is down or the port is wrong. Verify with curl http://192.168.0.x:PORT from inside the Traefik LXC.
Router not matching. Rules are case-sensitive. Host() requires a lowercase hostname β Host('Photos.domain.com') won't match a request for photos.domain.com.
Docker socket security. Mounting /var/run/docker.sock directly gives Traefik root-equivalent Docker daemon access. In a shared or semi-trusted environment, front it with docker-socket-proxy and limit to read-only API calls.
How This Fits Into a Broader Stack
Traefik is the ingress layer β it doesn't replace a container manager. If you're running Coolify v4 on Proxmox LXC, configure Coolify to use Traefik as its proxy instead of the built-in Caddy. The two cooperate well: Coolify handles deployment lifecycle, Traefik owns routing and cert management.
For a dedicated always-on Traefik node separate from your main Proxmox host, a Raspberry Pi 5 8GB draws under 5W idle. Pair it with a USB SSD enclosure and a 120GB SSD for better write endurance than SD card.
Verdict
Traefik v3 is the right reverse proxy for a home lab growing past toy status. The learning curve is real but bounded β once static config and the first file provider route are working, each new service takes five minutes to add. The automatic Docker label routing is genuinely better than any GUI-based alternative, and the Let's Encrypt + Cloudflare DNS-01 flow delivers real certs even behind CGNAT.
The community-scripts helper gets you from zero to running in under 10 minutes. This guide covers the 90% of config that the script leaves to you.
---
Disclosure: This post contains affiliate links. If you purchase through these links, I may earn a small commission at no extra cost to you.