Security
Traefik Manager is designed to run behind a reverse proxy on a trusted network. This page covers the security controls built in and how to harden your installation.
Looking to harden Traefik itself (header alias spoofing, encoded characters, forwardAuth limits, security advisories)? See Traefik Security Hardening.
Authentication
Password
The login password is hashed with bcrypt at cost 12 before storage in manager.yml. The plaintext password is never written to disk. Passwords must be at least 8 characters and at most 72 bytes, which is the bcrypt limit - accented and non-Latin characters take more than one byte each.
Login POSTs are rate-limited to 5 per minute per IP, successful or not. Wrong passwords from all addresses together are capped by LOGIN_FAILURE_LIMIT, and wrong 2FA codes by OTP_FAILURE_LIMIT.
After the password step, 5 wrong 2FA codes or 10 minutes send you back to the password step.
While ADMIN_PASSWORD is set, login compares that plaintext value instead of the stored hash, and 2FA and the in-UI password change have no effect. flask reset-password with --prompt, --stdin or --password exits with an error and writes nothing; with no password option it still writes a temporary password, which login then ignores.
Session management
Sessions use signed client-side cookies (Flask SecureCookieSession). The signing key is generated once and persisted to /app/config/.secret_key - it does not change on restart, so sessions survive container restarts.
| Setting | Value |
|---|---|
| Max session lifetime | 7 days (when "Remember me" is checked) |
| Inactivity timeout | 120 minutes, configurable via INACTIVITY_TIMEOUT_MINUTES; 24x that value for "Remember me" sessions - 48 hours at the default |
| Cookie flags | HttpOnly, SameSite=Lax (always on) |
Secure flag | Off by default - set COOKIE_SECURE=true env var |
Set COOKIE_SECURE=true whenever TM is accessed over HTTPS. Without it, browsers may send cookies over HTTP, which is a risk if your reverse proxy is not enforcing HTTPS-only access. It also adds a Strict-Transport-Security response header.
Logging out clears the session. Changing or resetting the password, turning off two-factor, or changing ADMIN_PASSWORD and restarting signs out every other session. Settings - Authentication - Password & 2FA can also sign out every other session on demand. API keys are not affected.
Authentication modes
Traefik Manager has two independent web-UI auth mechanisms - built-in password and OIDC / SSO - plus API keys for programmatic access. Access to the UI is required whenever either password or OIDC is enabled:
| Password | OIDC | Result |
|---|---|---|
| Enabled | Off | Password login (optionally with 2FA). |
| Enabled | Enabled | Login page offers both. |
| Disabled | Enabled | OIDC is the sole login - the password form is hidden and users are sent to your identity provider. |
| Disabled | Off | No authentication - the UI is publicly accessible. A red warning is shown in the app and Settings, and logged at startup. Avoid this outside a fully trusted, isolated network, or acknowledge it if a reverse proxy already requires a login. |
Disabling built-in authentication only turns off the password form - it does not disable OIDC. API keys keep working in every mode, so the mobile app and automation are unaffected when OIDC is your only interactive login.
Authentication handled by a reverse proxy
If something in front of Traefik Manager already requires a login - Authelia, Authentik, GateKeeper, or any forward-auth middleware - then running with both mechanisms off is a deliberate choice, and the red warning is noise. Under Settings → Authentication, the warning offers Acknowledge and hide. It is stored in manager.yml as auth_external_ack, so it applies to every browser and survives restarts, and the startup log drops from a SECURITY: warning to an informational line.
This changes what you are told, never what is enforced. Traefik Manager still authenticates nobody, so anything that reaches it directly - another container on the same Docker network, a LAN client hitting the port, a route that bypasses your middleware - has full administrative access. The acknowledgement cannot be set while a password or OIDC is active, and Settings keeps showing a neutral note stating that authentication is delegated, with an Undo link.
Recovery / lockout safety: disabling built-in authentication preserves your password hash in
manager.yml. If your OIDC provider becomes unreachable and you are locked out, setauth_enabled: trueinmanager.ymland restart the container - the password form returns and you can log in with your existing password. You can also generate a fresh password withflask reset-password(see the Reset Password guide).
OIDC / SSO login
TM supports OpenID Connect, either alongside the built-in password or as the sole login method (disable built-in authentication to make OIDC mandatory). When enabled, a "Sign in with [provider]" button appears on the login page.
Supported providers include Google, Keycloak, Authentik, Entra ID, Zitadel, and any OIDC-compliant identity provider. Access can be restricted to specific email addresses or groups.
See the OIDC setup guide for full configuration details.
| Setting | Detail |
|---|---|
| Client secret storage | Fernet-encrypted at rest (same key as TOTP secret) |
| CSRF protection | state parameter validated on callback |
Rate limit on /auth/oidc/login | 10 / min per IP |
| Token exchange | Server-side only - no tokens exposed to the browser |
Two-factor authentication (TOTP)
TM supports TOTP-based 2FA compatible with any standard authenticator app (Google Authenticator, Authy, etc.).
The TOTP secret is encrypted at rest using Fernet symmetric encryption. The encryption key is independent of the session signing key: it is taken from the OTP_ENCRYPTION_KEY environment variable, or generated once and persisted to /app/config/.otp_key. The same key encrypts every other stored secret - OIDC client secret, Traefik API password, CrowdSec keys, webhook password, git backup token, and each notification channel's tokens and password. A secret you write to manager.yml in plain text is read as written and encrypted in place on the next start, with a Security notification when that happens.
2FA can be reset via the reset password page if you lose access to your authenticator.
API keys
API keys are used by the mobile app and scripts to access the API without a browser session. A key grants the same access as a signed-in session - there are no per-key scopes - so a valid key can create, edit and delete routes, middlewares and services. Treat keys like passwords and revoke any you no longer use.
- Up to 10 keys can exist simultaneously, each with a device name for identification
- Each key is hashed with SHA-256 - the plaintext is shown once at creation and never stored
- Keys are revoked by their preview (first 8 and last 4 characters), not by device name. Revoking one device does not affect others
- Generation is rate-limited to 5 per hour per IP
Keys are passed via the X-Api-Key request header:
X-Api-Key: your-key-hereCSRF protection
All state-changing requests (POST, PUT, PATCH, DELETE) require a CSRF token when using session authentication. The token is embedded in every HTML page and is generated once per session.
API key requests are exempt from CSRF checks only when a valid key is provided. A request with a missing or invalid key still requires a CSRF token.
External auth providers
When an external provider such as Authentik, Authelia, or Keycloak already protects TM through Traefik's forwardAuth middleware, you can turn the built-in auth off and acknowledge the no-authentication warning so it stops being reported as a misconfiguration.
Mobile app compatibility
forwardAuth intercepts all requests including mobile app API calls. To use the mobile app alongside an external auth provider, split the Traefik route so /api/* bypasses forwardAuth and relies on Traefik Manager's built-in API key auth. See the mobile app docs for the full example.
Rate limiting
| Endpoint | Limit |
|---|---|
| Login, OTP verification | 5 / min per IP |
| Wrong passwords, all addresses | 30 / min, 200 / hour (LOGIN_FAILURE_LIMIT) |
| Wrong 2FA codes, all addresses | 10 / min, 30 / hour (OTP_FAILURE_LIMIT) |
| OIDC login initiation | 10 / min per IP |
| Password change | 10 / min per IP |
| Backup restore | 10 / min per IP |
| Setup connection tests (CrowdSec, git) | 10 / min per IP |
| API key generation | 5 / hour per IP |
| GeoIP database update | 6 / hour per IP |
| All other endpoints | Unlimited |
Counters live in memory, per gunicorn worker, so they reset on restart.
Browser autofill
Every field in the app except the sign-in form is marked so browsers and password managers do not autofill it. Credential fields inside the app (API user and password, git backup token, CrowdSec key, the basic-auth and digest-auth generators) are marked new-password, which stops Chrome offering saved logins and prompting to save what you type there.
The sign-in form is deliberately untouched, so your password manager still works where it should. The password change and TOTP fields in Settings keep their proper current-password, new-password, and one-time-code semantics.
Outbound requests (SSRF protection)
Several features make TM issue outbound requests on your behalf - the Traefik connection tests, the CrowdSec setup test, the git connection tests, the webhook test, the URL ping tool, and the OIDC provider test. To prevent these from being used to reach cloud metadata endpoints, these fetchers reject:
- Link-local addresses (
169.254.0.0/16, including the169.254.169.254cloud metadata IP) - Multicast, reserved, and unspecified addresses
Private and loopback targets are still allowed, because reaching internal services (e.g. http://traefik:8080) is the normal, legitimate use for a self-hosted reverse-proxy manager. Redirects are not followed on the ping tool.
The git connection tests run git ls-remote under the same address rules and do not follow redirects, on the Host and on agents. The setup page's checks answer only a signed-in admin.
A saved Traefik API password is only sent to the saved API URL. Testing a different URL, or saving one on a different host, port or path, needs the password typed again.
IP geolocation
IP geolocation (off by default) resolves client IPs to countries entirely on the server against a local database - IP addresses are never sent to any third-party geolocation API. The only outbound request downloads the database file itself from download.db-ip.com, and only when the feature is enabled: at startup and once a day after that, TM re-downloads it if the local file is older than 35 days. Point GEOIP_DB_PATH at your own .mmdb to control which database is used, and keep its mtime fresh (or leave the feature disabled) to stay fully offline.
Git backup safety
When you configure git backup:
- The repository URL must use
https://,http://,ssh://, orgit://. Other transports (ext::,file://,fd::) are rejected, and git is invoked withprotocol.ext.allow=never, so a crafted URL cannot execute local commands. - The access token is passed to git through
GIT_ASKPASSrather than being embedded in the remote URL. It is not written to.git/config, does not appear in process arguments, and is redacted from any error message shown in the UI.
Recommended setup
Run behind a reverse proxy with HTTPS
Never expose Traefik Manager directly on port 5000 to the internet. Use a reverse proxy (Traefik itself works well) with a valid TLS certificate.
- Use HTTPS - configure a cert resolver in Traefik and enable the self-route in TM Settings
- Set
COOKIE_SECURE=truein your docker-compose environment - Enable 2FA via Settings → Authentication → Password & 2FA
- Use per-device API keys - generate a separate key for each device/script, revoke individually if compromised
- Mount config files read-only where possible - TM needs write access to
CONFIG_DIR,/app/config, andBACKUP_DIR(default/app/backups)
Static config editor
The Static Config editor lets you edit traefik.yml directly from the UI and restart Traefik with one click. This has security implications beyond the dynamic config:
- Read-write mount -
traefik.ymlmust be mounted without:ro, giving TM write access to Traefik's entire static configuration including entrypoints, providers, and TLS settings - Restart access -
RESTART_METHODselects how Traefik is restarted, and each option has a different trust boundary:
| Method | Access granted |
|---|---|
proxy (default, recommended) | TM connects to a socket proxy sidecar limited to container restart operations only |
poison-pill | TM writes a signal file to a shared volume - no Docker API access at all |
socket | TM has direct access to the Docker socket - broadest access |
The socket proxy and poison-pill methods limit the blast radius if TM is compromised. Direct socket access allows TM to interact with any container on the host.
If you do not use the Static Config editor, do not mount traefik.yml read-write and do not set RESTART_METHOD.
Removing certificates
The Certs tab can delete entries from acme.json, which is where Traefik keeps every issued certificate and its private key. It is off unless both of these are true:
- TM can write
acme.json: mounted without:ro, or on a native install, owned by the user TM runs as - a
RESTART_METHODis set, because Traefik only readsacme.jsonat startup
TM never reads or logs the key material, and only ever removes whole entries; the ACME account is left untouched. A timestamped copy of the file is written to BACKUP_DIR first, with mode 600, which means your backup directory then holds a copy of every private key - treat it with the same care as the store itself. The file is rewritten in place rather than replaced, so a bind-mounted acme.json stays attached to Traefik, and its mode is kept at 600 because Traefik refuses to load a store with any group or other permission.
Leave acme.json mounted :ro and none of this is reachable.
File permissions
TM writes to these locations (default paths - they follow SETTINGS_PATH):
| Path | Purpose |
|---|---|
/app/config/manager.yml | Settings, hashed password, API key hashes, encrypted secrets |
/app/config/.secret_key | Session signing key (generated once, written 0600) |
/app/config/.otp_key | Fernet key for the TOTP secret and every other stored secret |
/app/config/agents.yml | Agent registrations. The agent API key, CrowdSec API key, CrowdSec machine password and git token are encrypted; other fields, including the Traefik API password, are stored as written |
/app/config/dashboard.yml | Dashboard groups and card overrides, kept per server |
/app/config/notifications.yml | Notification history, its .lock and .next_id companions, and the digest queue |
/app/config/templates.yml | Custom middleware templates |
/app/config/cache/ | Dashboard group cache |
/app/config/geoip/ | The GeoIP database and a .geoip.lock beside it, when IP geolocation is on |
CONFIG_DIR / CONFIG_PATHS | Dynamic Traefik config files |
/app/config/ is the most sensitive directory: it holds the password hash and both encryption keys. Own it as the container user and keep it out of world-readable host directories - .secret_key is created 0600, but .otp_key is written with the process umask.
If you provide your own session key with the SECRET_KEY environment variable, it must be at least 32 characters - TM refuses to start with a shorter key.