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 (underscore header spoofing, encoded characters, forwardAuth limits, CVE 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.
Login POSTs are rate-limited to 5 per minute per IP, successful or not.
While ADMIN_PASSWORD is set, login compares that plaintext value instead of the stored hash, and 2FA, the in-UI password change and flask reset-password all have no effect.
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.
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. Only the encrypted values are stored in manager.yml.
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.
- 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 |
| 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 HTTP requests on your behalf - the Traefik connection tests, the CrowdSec and git setup 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.
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 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.
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 |
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.