Introduction
At some point every homelab hits the same wall. You've got Pi-hole on one IP, Nextcloud on another, Immich buried behind a port number nobody remembers, and you're tired of typing 192.168.1.47:8384 from memory. You want real names, real HTTPS, and one place to manage all of it.
Traefik is one of the two tools most homelab guides point you toward for that job — the other being Nginx Proxy Manager. They solve the same problem in almost opposite ways. NPM gives you a web form: type a domain, pick a target IP and port, click save. Traefik gives you almost none of that. It watches your other services for labels or config files and builds its routing table automatically, with zero clicking required once it's set up.
That sounds harder, and for the first ten minutes it is. But once it clicks, adding a new service means adding a few lines of config instead of opening a dashboard — and if you're already running Docker containers for half your homelab, Traefik can pick most of them up without you touching it again. This guide walks through installing Traefik in its own LXC container on Proxmox VE, configuring it from scratch, and routing your first real service through it.
What You Will Learn
- What Traefik actually does and how it's different from a traditional reverse proxy like Nginx Proxy Manager
- How to deploy Traefik in an LXC container on Proxmox VE using the community-scripts installer
- The three config files Traefik uses and what each one is for
- How to route your first service through Traefik and get a real Let's Encrypt certificate for it
- The errors people run into most (usually a bad label or a port that isn't actually reachable) and how to fix them
- A handful of settings worth changing before Traefik faces the public internet
What Is This Feature?
Traefik is a reverse proxy and load balancer, written in Go, that's built around one idea: it should discover what to route by watching other systems, not by you filling out a form. Point it at Docker, and it reads container labels to figure out what domain should go where. Point it at a folder of YAML files, and it reads routing rules from those instead. Either way, you're describing routes as data, not clicking through a UI.
A quick definition for anyone new to this: a reverse proxy sits in front of your other services and decides where to send each incoming request based on the hostname or path. Your router forwards ports 80 and 443 to it, and it fans that traffic back out to whichever internal service actually owns that domain. One public door, many rooms behind it.
Traefik's config splits into two halves. The static configuration — entrypoints, providers, the certificate resolver — is set once when Traefik starts and lives in /etc/traefik/traefik.yaml. The dynamic configuration — your actual routers, services, and middlewares — can change while Traefik is running and lives in files under /etc/traefik/conf.d/. Traefik watches that folder and picks up changes without a restart, which is the part people find genuinely useful once they're used to it.
You'll also see the word middleware used a lot in Traefik docs. A middleware is a small rule that modifies a request before it reaches its destination — redirecting HTTP to HTTPS, adding basic auth, stripping a URL prefix. You chain them onto a router the same way you'd chain filters onto a pipe.
Why Would You Use It?
A few honest reasons people pick Traefik over the form-based alternatives:
- Docker-native discovery. If your services already run in Docker containers with labels, Traefik finds and routes them automatically the moment they start. No dashboard, no manual entry.
- Automatic HTTPS by default. Once the ACME resolver is configured, every new router gets a Let's Encrypt certificate without extra clicks — Traefik requests and renews it in the background.
- Config as files, not clicks. If you like keeping your homelab in a Git repo so you can rebuild it from scratch, Traefik's YAML config fits that habit far better than a SQLite database sitting inside NPM.
- It scales with you. Weighted load balancing, canary routing, and per-router middleware chains are things you'll actually reach for if your homelab grows past "a few services on one box."
None of that makes NPM the wrong choice. If you want to add a domain in thirty seconds without editing a config file, NPM is genuinely easier, and I'd tell most first-time homelab users to start there. Traefik earns its complexity once you're running more than a handful of services, especially in Docker, and you're tired of clicking the same five buttons every time you spin up something new.
Prerequisites
Before you start, you'll want:
- A working Proxmox VE host — this guide was written against Proxmox VE 9.2, running Debian 13 "trixie" underneath, and the install script also works fine on Proxmox VE 8.x.
- Root shell access to the Proxmox host, either through the Shell button in the web console or over SSH.
- At least 4 GB of free space on whatever storage you're using for the container — Traefik itself is tiny, but the container's base OS needs room.
- A domain name you control, if you want real Let's Encrypt certificates instead of self-signed ones. You can follow along without one, but the HTTPS section won't apply to you.
- Router-level access, if you plan to expose Traefik to the internet later — you'll need to forward ports 80 and 443 to it eventually.
You don't need any Docker experience for the base install. If you do plan to use Traefik's Docker provider later to auto-discover containers, it helps to already have Docker running somewhere on your network, but that's a separate guide.
Step-by-Step Tutorial
1. Open a shell on your Proxmox host
Log in to the Proxmox web interface, click your node in the left-hand tree, and open Shell. You'll land in a root prompt on the Proxmox host itself — not inside any VM or container yet.
2. Run the community-scripts install command
Paste this in and hit enter:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/traefik.sh)"
This pulls a script maintained by the community-scripts project and walks you through an interactive menu. Pick Advanced if you want to set a static IP up front, or Default if you're fine grabbing an address from DHCP and changing it later. The default settings give the container 1 CPU core, 512 MB of RAM, and a 4 GB disk on a Debian 13 template — plenty for Traefik on its own.
The script builds the container, installs Traefik as a systemd service inside it, and drops the default config files into place. This takes about two minutes on a normal home connection.
3. Find the container's IP address
Once the script finishes, it prints the container's ID and IP address directly in the terminal. If you missed it, click the container in the Proxmox tree, open Summary, and read the IP off the panel there. Write it down — you'll need it for SSH and for testing routes.
4. Look at the default static configuration
SSH into the container (ssh root@<container-ip>) and open the main config file:
cat /etc/traefik/traefik.yaml
You'll see two entrypoints already defined, one for port 80 and one for 443, plus a provider pointed at /etc/traefik/conf.d/. This file is the skeleton. You generally set it once and leave it alone — the routes you actually care about go in the dynamic config folder, not here.
5. Turn on the built-in dashboard
Traefik ships with a dashboard that shows every router, service, and middleware it currently knows about, which is genuinely the fastest way to debug a routing problem. Edit the static config and add:
api:
dashboard: true
insecure: true
insecure: true serves the dashboard over plain HTTP on port 8080 with no authentication, which is fine on your private LAN for testing and a bad idea the moment this container is reachable from the internet. Restart the service to pick up the change:
systemctl restart traefik
Then open http://<container-ip>:8080/dashboard/ in a browser. You should see an empty-ish dashboard with the default entrypoints listed and no routers yet, since you haven't defined any.
6. Route your first service
Say you've got Uptime Kuma running on another machine at 192.168.1.50:3001 and you want it reachable at uptime.yourdomain.com. Create a new file in the dynamic config folder:
nano /etc/traefik/conf.d/uptime-kuma.yaml
And add this:
http:
routers:
uptime-kuma:
rule: "Host(`uptime.yourdomain.com`)"
service: uptime-kuma
entryPoints:
- web
services:
uptime-kuma:
loadBalancer:
servers:
- url: "http://192.168.1.50:3001"
Save the file. Traefik notices the change within a few seconds — no restart needed. Refresh the dashboard and you'll see a new router called uptime-kuma listed under HTTP Routers. Point your DNS (or your /etc/hosts file, for local testing) at the Traefik container's IP, and uptime.yourdomain.com should now load through the proxy.
7. Add HTTPS with a real certificate
To get Let's Encrypt working, add a certificate resolver to the static config:
certificatesResolvers:
letsencrypt:
acme:
email: you@yourdomain.com
storage: /etc/traefik/acme.json
httpChallenge:
entryPoint: web
Then update your router to use it and add the secure entrypoint:
http:
routers:
uptime-kuma:
rule: "Host(`uptime.yourdomain.com`)"
service: uptime-kuma
entryPoints:
- websecure
tls:
certResolver: letsencrypt
Restart Traefik. As long as port 80 is actually reachable from the internet — the HTTP challenge needs it — Traefik requests a certificate from Let's Encrypt on its own and stores it in acme.json. That file needs chmod 600 or Traefik will refuse to write to it, which trips people up constantly.
Commands Explained
| Command | What it does |
|---|---|
bash -c "$(curl -fsSL .../traefik.sh)" | Downloads and runs the community-scripts installer, which builds the LXC container and installs Traefik as a systemd service inside it. |
systemctl restart traefik | Restarts the Traefik service so it re-reads the static config file. Dynamic config in conf.d/ doesn't need this. |
systemctl status traefik | Shows whether the service is running and the last few log lines — the first thing to check when a route isn't working. |
journalctl -u traefik -f | Follows the live log output, which is where certificate errors and bad-config errors actually show up. |
chmod 600 /etc/traefik/acme.json | Locks down permissions on the certificate storage file. Traefik refuses to use it if the permissions are too open. |
Common Errors
"404 page not found" from Traefik itself. This means Traefik is running and reachable, but no router matched the hostname you requested. Almost always a typo in the Host() rule, or you're testing a domain that doesn't point at the container yet.
Bad Gateway or 502. Traefik found a matching router, but couldn't reach the backend service. Double-check the IP and port in your loadBalancer.servers block, and confirm the target service is actually listening there — try curling it directly from the Traefik container first.
Certificate never issues, no error shown. Usually port 80 isn't actually forwarded to the Traefik container from your router, so the Let's Encrypt HTTP challenge can't complete. Check journalctl -u traefik -f while requesting the cert — the ACME error is almost always logged there even when the dashboard looks fine.
Dynamic config changes don't seem to apply. Check the YAML indentation first — Traefik silently ignores a file it can't parse instead of erroring loudly, which is the single most common gotcha with the file provider.
Troubleshooting
Start with systemctl status traefik. If it's not active, journalctl -u traefik -n 50 almost always names the exact line in traefik.yaml that's malformed.
If the service is running but nothing routes, the dashboard is your fastest diagnostic tool. Open http://<container-ip>:8080/dashboard/ and check the HTTP Routers list. If your router isn't there at all, Traefik never loaded that config file — check the filename ends in .yaml and sits directly inside /etc/traefik/conf.d/, not a subfolder.
If the router shows up but is marked with a warning icon, hover over it — Traefik usually tells you exactly what's wrong, most often a service with no reachable backend.
For certificate problems specifically, delete acme.json and let Traefik recreate it (Let's Encrypt's rate limits are generous enough that this is safe to do a few times while testing), then watch the logs during the next restart.
Best Practices
- Turn off
insecure: trueon the dashboard before you expose Traefik to the internet, and put the dashboard behind its own authenticated router instead. - Use Let's Encrypt's staging server (
https://acme-staging-v02.api.letsencrypt.org/directory) while you're testing certificate config, so you don't burn against the real rate limits from repeated restarts. - Keep one YAML file per service in
conf.d/rather than one giant file. It makes it obvious what to delete when you retire something. - Back up
/etc/traefik/as part of your regular Proxmox backup schedule for the container. Losingacme.jsonisn't a disaster — Traefik just requests fresh certificates — but losing your dynamic config means rebuilding every route by hand. - If you're running more than a few services, consider a middleware for redirecting all HTTP traffic to HTTPS at the entrypoint level, instead of repeating a TLS block in every router.
Frequently Asked Questions
Is Traefik better than Nginx Proxy Manager?
Neither is objectively better — they fit different habits. NPM is faster to learn and easier for a handful of manually managed services. Traefik pays off once you're running many services in Docker and want routing to happen automatically as containers start.
Do I need Docker to use Traefik?
No. The file-based dynamic config used in this guide works with services running anywhere — other LXC containers, VMs, even bare metal. Docker just adds automatic discovery on top.
Can I run Traefik and Nginx Proxy Manager on the same Proxmox host?
Yes, but not on the same ports. Only one process can bind 80 and 443 at a time, so you'd need to pick which one actually faces your router and route the other's traffic through it, or just keep them serving different sets of services.
Does Traefik slow down my services?
Not in any way you'd notice on a homelab. It's a lightweight Go binary and the LXC container it runs in uses well under 100 MB of RAM at idle.
What happens if the Traefik container goes down?
Every service routed through it becomes unreachable by its domain name until it's back up, since Traefik is the only thing answering on ports 80 and 443. Your backend services keep running fine on their own IPs — you just lose the friendly hostnames until Traefik restarts.
Conclusion
Traefik takes longer to set up than Nginx Proxy Manager, and if you stopped reading after the first config file, that's a fair reaction. But once the static and dynamic config split clicks, adding a new service is a five-line YAML file instead of a dashboard session — and if you ever move more of your homelab into Docker, the automatic discovery alone is worth the learning curve.
Start with one service, get the certificate working end to end, and only then move the rest of your homelab over. Trying to migrate everything on day one is how people end up with three reverse proxies fighting over port 80.