Skip to content

Traefik Dynamic Config

This directory is what Traefik’s file provider reads. The compose mount is relative to the project (./dynamic:/etc/traefik/dynamic), so on the traefik host the live path is the git checkout itself.

A route change therefore ships the normal way — commit → push → git pull on the host. The provider watches the directory and picks it up in seconds. No restart, and never docker restart, which only reloads static config from the compose command: flags. See Operations.

Older docs describe this directory as a review-only mirror of /mnt/lxcshare/docker-data/traefik/dynamic. That is stale — writing there now changes nothing, and nothing mounts that path any more.

traefik-manager does not edit this directory either, though it is meant to. Its mount is ./traefik/dynamic while Traefik’s is ./dynamic, so it lands on a sibling traefik/dynamic/ that Docker created empty and git does not track. The UI therefore shows no config at all. Correcting the mount is a one-word compose change plus a bws run … up -d recreate.

Two rules for this directory:

  • Never put backups in it. The provider parses every file present, so a services.yml.bak is a duplicate router definition, not an inert copy. Back up to /root/traefik-dynamic-backups/.
  • Match ownership. Existing files are 1000:1000; a root-owned file is still read but cannot be edited from traefik-manager.

Every file here is live — services.yml, middlewares.yml, middleware-chains.yml, tls-opts.yml, certificates.yaml, certificates-resolvers.yml, forgejo-ssh.yml, opengist-ssh.yml, proxmenux.yml (the node’s own web dashboard), docker-apps-cifs.yml (11 routers for VM 217) and ugreen-nas.yml (6 for the NAS). The two *-ssh.yml files are tcp routers, not http ones — raw git-SSH passthrough to VM 217, one port each.

Everything else on the fleet is routed by the traefik-proxmox-provider plugin, which reads labels out of each guest’s Proxmox description. It implements its own label parser, so only these do anything — anything else is read and discarded without warning. Taken from the vendored source in the running container (v0.7.6).

Router — traefik.http.routers.<name>.Service — traefik.http.services.<name>.loadbalancer.
ruleserver.url — full override, wins over everything else
entrypoints (or singular entrypoint)server.scheme — only https is special-cased
middlewaresserver.port
priorityserver.ip
servicepasshostheader
tls, tls.certresolverhealthcheck.path / .interval / .timeout
tls.domains, tls.optionssticky.cookie.name / .secure / .httponly
responseforwarding.flushinterval

server.url short-circuits the scheme/port/ip logic entirely, and nothing re-checks that the URL belongs to the guest whose notes carried it. That is how the UGREEN NAS (192.168.1.29, not a Proxmox guest at all) used to be routed from VM 217’s description rather than from a file here — one routing mechanism for the fleet instead of two. It is now in ugreen-nas.yml; the mechanism is still worth knowing, since it is what made that arrangement possible at all.

scheme=https changes the default port to 443, so pair it with an explicit server.port unless the backend really is on 443.

Why VM 217 and the NAS are routed from files

Section titled “Why VM 217 and the NAS are routed from files”

Not a plugin limitation — the description simply ran out of room. Proxmox caps that field at 8192 characters. VM 217’s held nine label blocks for its own services plus six more pointing at the UGREEN NAS, totalling 7808, and Comicarr’s seven-line block needed 397 of the 384 left. qm set rejects an over-length value whole (400 Parameter verification failed. description: value may only be 8192 characters long) and writes nothing, so there was no way to add a tenth service the old way.

Both groups moved here rather than trimming to buy a few hundred characters and meeting the same wall next time. docker-apps-cifs.yml carries the ten VM 217 routers, ugreen-nas.yml the six NAS ones. Splitting by backend keeps each file’s servers pointing at a single address, so a renumbered host is one file to edit.

The NAS entries in particular no longer depend on an unrelated guest. They only ever lived in VM 217’s description because server.url overrides the guest IP (see below) and there was nowhere better; now they survive that guest being renumbered or retired.

The description must not keep the labels. A label block and a file entry for the same host produce two routers — forgejo@file and forgejo@plugin-traefik-proxmox-provider — both matching Host(forgejo.toolsera.lan). VM 217’s description was replaced with a pointer to these files.

The traefik-proxmox-provider plugin reads labels out of each guest’s description. pve-dell is not a guest — it has no description field to write labels into — so a service running on the node itself can never be routed by the plugin, however it is named and whatever tags it carries.

proxmenux.yml is the first of these: ProxMenux Monitor on http://192.168.1.131:8008, a proxmenux-monitor.service unit installed under /usr/local/share/proxmenux. Plain HTTP, so it needs no serversTransport — just a router and a backend URL.

This is a different reason from the PegaProx case below. That one was a guest and could have carried labels; it needed a file because the plugin drops the one label that would have made its self-signed HTTPS work. Both end up here, for unrelated reasons.

pegaprox.yml used to live here. CT 268 was destroyed on 2026-08-20 and the file outlived it, leaving a router that resolved and answered 502 — including on the Dashy tile, which is why both were removed together on 2026-08-23.

It is documented rather than simply deleted because the reason it needed a file at all still applies to anything like it. PegaProx served HTTPS on :5000 with a self-signed certificate, and the plugin silently drops loadbalancer.serverstransport — the one label that would let a notes-based route skip verification. The result is a router that resolves, a backend reporting UP, and a 500 on every request. Defined in a file it could reference the insecure-tls transport already in services.yml, exactly as lxd (https://192.168.1.148:2956) still does.

The alternatives did not survive contact: PegaProx had no cleartext port (5001 and 5002 are WebSocket endpoints answering 426), Traefik’s container does not carry the Smallstep root so re-issuing the certificate would still fail without --serversTransport.rootCAs, and --serversTransport.insecureSkipVerify=true would disable verification for every backend. The last two are static config needing a recreate anyway.