# Configuration ## Overview Muximux is configured via a single YAML file. By default, this is `data/config.yaml` (inside the data directory). The data directory defaults to `data/` relative to the binary's location and can be changed with `--data` or `MUXIMUX_DATA`. You can also override the config path directly: ```bash # Change the data directory (config will be at /opt/muximux/data/config.yaml) muximux --data /opt/muximux/data # Or override the config path explicitly muximux --config /path/to/config.yaml ``` ## Complete Configuration Reference ```yaml # ─── Server ───────────────────────────────────── server: listen: ":8080" # Listen address (default: ":8080") title: "Muximux" # Page title shown in browser tab language: en # Interface language (BCP 47 tag, e.g. "en", "sv") log_level: info # Log verbosity: debug, info, warn, error log_format: text # Log output format: text or json proxy_timeout: 30s # Global timeout for proxied requests base_path: "" # Subpath for reverse proxy (e.g. "/muximux") # Optional: TLS / HTTPS tls: domain: "" # Auto-HTTPS domain (requires email) email: "" # Let's Encrypt registration email cert: "" # Path to TLS certificate PEM key: "" # Path to TLS private key PEM # Replaced by `gateway_sites:` below. If you're upgrading from # 3.0.x and still have this set, Muximux converts it to # `gateway_sites:` on first boot and saves backups of both the # original `config.yaml` and your Caddyfile alongside them. gateway: "" # legacy # Optional: Override the bind address for the embedded Caddy that # serves gateway sites. Useful when (a) you don't want to grant # CAP_NET_BIND_SERVICE / run as root, or (b) an upstream proxy # terminates TLS and you want Caddy on a high port behind it. # When set, gateway sites are emitted with http://: # and auto-https is disabled. Empty = legacy behaviour (:80/:443). gateway_listen: "" # e.g. ":8443" # Optional: cookie scope for cross-subdomain session sharing. # Required when any entry in gateway_sites has require_auth=true, # otherwise the session cookie won't reach the gated subdomain. # Leading dot is optional (browsers normalise). session_cookie_domain: "" # e.g. ".example.com" # Optional: declarative gateway sites served by the embedded Caddy. # See the Gateway Examples and Gateway Auth Gate wiki pages for the # full reference. Each entry can optionally gate access behind the # Muximux session via require_auth + min_role + allowed_groups. gateway_sites: - domain: sonarr.example.com # required backend_url: http://192.168.1.5:8989 # required tls: auto # auto | none | custom tls_cert: "" # required when tls=custom tls_key: "" # required when tls=custom streaming: false # disable Caddy response buffering for live data strip_frame_blockers: true # strip X-Frame-Options for iframe embedding forwarded_headers: true # forward X-Forwarded-Proto/Host/Real-IP backend_skip_tls_verify: false # skip verifying a self-signed HTTPS backend (Proxmox); https backends only proxy_headers: {} # extra headers injected on upstream requests # Optional: also surface this site in the dashboard menu app_name: "" # link to an existing app by name # Gateway auth gate (3.1.0+) -- gate this subdomain behind Muximux login require_auth: false # 302 anonymous visitors to /login first min_role: "" # "user" / "power-user" / "admin"; empty = any signed-in user allowed_groups: [] # case-insensitive; admins bypass # Auto-populated by Docker discovery on import; do not hand-edit. # Presence of docker_key locks backend_url to the refresh poller. docker_key: "" docker_endpoint: "" docker_strategy: "" # ─── Authentication ───────────────────────────── auth: method: none # none, builtin, forward_auth, oidc setup_complete: false # Set automatically after onboarding wizard completes session_max_age: 24h # Session duration (default: 24h) secure_cookies: false # Require HTTPS for session cookies api_key_hash: "" # Optional bcrypt hash of API key for X-Api-Key auth # Builtin auth users users: - username: "" password_hash: "" # bcrypt hash (see Authentication page) role: admin # admin, power-user, or user email: "" # Optional display_name: "" # Optional groups: [] # Optional group memberships (for allowed_groups filtering) # Forward auth settings trusted_proxies: [] # CIDR ranges, e.g., ["192.168.0.0/16"] headers: user: Remote-User email: Remote-Email groups: Remote-Groups name: Remote-Name # Note: key is "name" not "display_name" logout_url: "" # Auth provider logout URL (e.g., https://auth.example.com/logout) forward_auth_admin_groups: [] # Groups that grant admin (default: admin, admins, administrators) # OIDC settings oidc: enabled: false issuer_url: "" client_id: "" client_secret: "" # Supports ${ENV_VAR} syntax redirect_url: "" # Must match provider callback URL scopes: [openid, profile, email] username_claim: preferred_username email_claim: email groups_claim: groups display_name_claim: name admin_groups: [] # Groups that grant admin role provider_logout: false # End the session at the provider on logout post_logout_redirect_url: "" # Default: /login?logged_out=1; register it at the provider logout_url: "" # End-session URL for providers without one in discovery auto_redirect: false # Login page goes straight to SSO (except after logout, ?local=1 or an error) disable_local_login: false # Refuse password sign-in while OIDC is on (API keys still work) # ─── Theme ───────────────────────────────────── theme: family: default # Theme family: default, nord, dracula, etc. variant: system # dark, light, system (follow OS preference) # ─── Navigation ───────────────────────────────── navigation: position: top # top, left, right, bottom, floating width: 220px # Sidebar width auto_hide: false auto_hide_delay: 0.5s show_on_hover: true show_labels: true show_logo: true show_home_button: true # Home button in the navigation (required for the splash screen) show_app_colors: true show_icon_background: false show_splash_on_startup: false show_shadow: true dynamic_tab_branding: false # Browser tab shows the active app's name and icon icon_scale: 1.0 # Icon zoom multiplier bar_style: grouped # grouped or flat (top/bottom bars only) floating_position: bottom-right # bottom-right, bottom-left, top-right, top-left hide_sidebar_footer: false # Hide the footer section in sidebars show_docker_badge: true # Docker whale on Docker-discovered apps in the nav and overview (admins only) max_open_tabs: 0 # Max app tabs kept alive in memory (0 = unlimited) # ─── Icons ────────────────────────────────────── icons: dashboard_icons: enabled: true mode: on_demand # on_demand, prefetch, offline cache_dir: icons/dashboard # Where fetched dashboard icons are cached cache_ttl: 7d # How long a cached icon is reused before refetch # ─── Health Monitoring ────────────────────────── health: enabled: true interval: 30s timeout: 5s # ─── Docker Discovery (optional, opt-in) ─────── # Connect to a Docker daemon to discover running # containers and keep their URLs current. See the # Docker Discovery wiki page for the full guide. discovery: docker: enabled: false # opt-in; requires socket access endpoint: unix:///var/run/docker.sock # or tcp://host:2376 network_strategy: container_ip # container_ip | container_dns | host_port | host_docker_internal network_filter: "" # scope scans to one docker network when set host_ip: "" # required by host_port strategy refresh_interval: 60s # poller cadence auto_import: off # off (default) | add | update | sync; auto-import muximux.*-labeled containers require_explicit_enable: false # auto-import only containers labelled muximux.app.enabled=true lifecycle_enabled: false # allow start/stop/restart of tracked containers (requires :rw socket mount) lifecycle_min_role: admin # min role for lifecycle controls lifecycle_allowed_groups: [] # additionally require group membership health_badge_placement: overview # overview | overview_and_nav | off tls: # only needed for tcp:// with mTLS enabled: false ca_cert: "" # CA bundle that signed the daemon cert client_cert: "" # client certificate (PEM) client_key: "" # client key (PEM, chmod 600) # ─── Keyboard Shortcuts ──────────────────────── keybindings: bindings: {} # Custom overrides only; defaults managed client-side # ─── Groups ───────────────────────────────────── groups: - name: Media icon: type: lucide name: play color: "#e5a00d" order: 1 expanded: true # Server-owned; written by Docker discovery, ignored in API payloads: # docker_managed: true # group created by discovery; muximux.group.* labels apply # docker_order: true # the group's order comes from a muximux.group.order label # ─── Apps ─────────────────────────────────────── apps: - name: Plex url: http://plex:32400 # health_url: http://plex:32400/web # Optional custom health endpoint icon: type: dashboard name: plex # variant: light # Icon variant (dashboard icons only) # file: healarr # Filename in data/icons/ (type: custom only) # url: https://example.com/icon.png # Remote image URL (type: url only) # color: "#ff0000" # Tint color (Lucide only) # background: "#1a1a2e" # Icon background override # invert: false # Invert icon colors (dark ↔ light) color: "#e5a00d" group: Media order: 1 enabled: true default: true pinned: false # Show as a button in the top/bottom bar, ahead of the group dropdowns (stays in its group) open_mode: iframe # iframe, new_tab, new_window, redirect, http_action proxy: false proxy_skip_tls_verify: true # Skip TLS certificate verification (default: true) proxy_headers: # Custom headers sent to the backend X-Custom-Header: value forwarded_headers: true # Send X-Forwarded-For/Host/Proto and X-Real-IP to the backend; false sends none (for backends that reject them) scale: 1.0 health_check: true # Opt-in: set true to enable health monitoring for this app shortcut: null # Assign a number key (1-9) for quick switching min_role: "" # Minimum role to see this app (user, power-user, admin) force_icon_background: false # Show icon background even when global setting is off auth_bypass: [] access: roles: [] users: [] # Docker tracking; written by discovery (Discover import, auto-import, Re-link). # Server-owned: ignored in API payloads. Editing url by hand detaches the app. # docker_key: "label:plex" # tracking key (label:, swarm:, compose:, name:, id:) # docker_endpoint: unix:///var/run/docker.sock # docker_strategy: container_ip # docker_managed_url: http://172.18.0.5:32400 # URL the poller last wrote # docker_managed_health_check: true # value of the muximux.app.health_check label # docker_auto: true # created by auto-import (only these are updated or removed by it) ``` ## Command Line Options ``` muximux [flags] --data PATH Base data directory (default: data/ beside binary, env: MUXIMUX_DATA) --config PATH Override config file path (default: /config.yaml, env: MUXIMUX_CONFIG) --listen ADDR Override listen address (env: MUXIMUX_LISTEN) --base-path PATH Subpath for reverse proxy (e.g. /muximux, env: MUXIMUX_BASE_PATH) --version Show version and exit ``` Precedence: CLI flag > environment variable > config file value > default. ## Environment Variable Expansion Use `${VARIABLE_NAME}` in any string value to reference environment variables. This is useful for keeping secrets out of config files: ```yaml auth: oidc: client_secret: ${OIDC_CLIENT_SECRET} api_key_hash: ${MUXIMUX_API_KEY_HASH} apps: - name: Plex url: ${PLEX_URL} ``` If the referenced environment variable is not set, the `${VAR}` reference is left as-is (the literal string is preserved). This avoids silently deleting values when an env var is missing. ## Environment Variable Reference This is the complete list of environment variables Muximux reads. Most of them override a `config.yaml` setting without needing `${VAR}` syntax in the file. A variable that is set but empty or only whitespace counts as unset, for every variable below. A Compose entry such as `MUXIMUX_LISTEN=${LISTEN}` therefore leaves the config file value in place when `LISTEN` is not defined, instead of forcing an empty value. ### Process and logging | Variable | Overrides | Values | Default | |----------|-----------|--------|---------| | `MUXIMUX_DATA` | Data directory (flag `--data`) | Path | `data/` beside the binary | | `MUXIMUX_CONFIG` | Config file path (flag `--config`) | Path | `/config.yaml` | | `MUXIMUX_LISTEN` | `server.listen` (flag `--listen`) | Address, e.g. `:9090` | From config file | | `MUXIMUX_BASE_PATH` | `server.base_path` (flag `--base-path`) | Path, e.g. `/muximux` | From config file (empty = root) | | `MUXIMUX_LOG_LEVEL` | `server.log_level` | `debug`, `info`, `warn`, `error` | From config file | | `MUXIMUX_LOG_FORMAT` | `server.log_format` | `text` or `json` | From config file | The Docker image starts Muximux with `--data /app/data`, and a flag wins over its variable, so `MUXIMUX_DATA` has no effect in the container unless you replace the command. ### Docker discovery | Variable | Overrides | Values | Default | |----------|-----------|--------|---------| | `MUXIMUX_DISCOVERY_AUTO_IMPORT` | `discovery.docker.auto_import` | `off`, `add`, `update`, `sync` | From config file | | `MUXIMUX_DISCOVERY_REQUIRE_EXPLICIT_ENABLE` | `discovery.docker.require_explicit_enable` | Boolean | From config file | An unknown `MUXIMUX_DISCOVERY_AUTO_IMPORT` value is treated as `off` with a warning. An invalid `MUXIMUX_DISCOVERY_REQUIRE_EXPLICIT_ENABLE` value is ignored with a warning and the config file value stays. An empty value counts as unset for both. See [Docker Discovery > Automatic Import](docker-discovery.md#automatic-import). ### Authentication and OIDC | Variable | Overrides | Values | |----------|-----------|--------| | `MUXIMUX_AUTH_METHOD` | `auth.method` | `none`, `builtin`, `forward_auth`, `oidc` | | `MUXIMUX_OIDC_ENABLED` | `auth.oidc.enabled` | Boolean | | `MUXIMUX_OIDC_ISSUER_URL` | `auth.oidc.issuer_url` | URL | | `MUXIMUX_OIDC_CLIENT_ID` | `auth.oidc.client_id` | String | | `MUXIMUX_OIDC_CLIENT_ID_FILE` | `auth.oidc.client_id` | Path of a file holding the value (Docker secrets) | | `MUXIMUX_OIDC_CLIENT_SECRET` | `auth.oidc.client_secret` | String | | `MUXIMUX_OIDC_CLIENT_SECRET_FILE` | `auth.oidc.client_secret` | Path of a file holding the value (Docker secrets) | | `MUXIMUX_OIDC_REDIRECT_URL` | `auth.oidc.redirect_url` | Absolute `http(s)` URL | | `MUXIMUX_OIDC_SCOPES` | `auth.oidc.scopes` | List, separated by commas and/or spaces | | `MUXIMUX_OIDC_USERNAME_CLAIM` | `auth.oidc.username_claim` | String | | `MUXIMUX_OIDC_EMAIL_CLAIM` | `auth.oidc.email_claim` | String | | `MUXIMUX_OIDC_GROUPS_CLAIM` | `auth.oidc.groups_claim` | String | | `MUXIMUX_OIDC_DISPLAY_NAME_CLAIM` | `auth.oidc.display_name_claim` | String | | `MUXIMUX_OIDC_ADMIN_GROUPS` | `auth.oidc.admin_groups` | List, separated by commas | | `MUXIMUX_OIDC_PROVIDER_LOGOUT` | `auth.oidc.provider_logout` | Boolean | | `MUXIMUX_OIDC_POST_LOGOUT_REDIRECT_URL` | `auth.oidc.post_logout_redirect_url` | Absolute `http(s)` URL | | `MUXIMUX_OIDC_LOGOUT_URL` | `auth.oidc.logout_url` | Absolute `http(s)` URL | | `MUXIMUX_OIDC_AUTO_REDIRECT` | `auth.oidc.auto_redirect` | Boolean | | `MUXIMUX_OIDC_DISABLE_LOCAL_LOGIN` | `auth.oidc.disable_local_login` | Boolean | Each defaults to the config file value. An empty or blank auth variable counts as unset, including an empty `X_FILE`, so `X=value` with `X_FILE=` uses the value. Setting both `X` and `X_FILE` is an error. Invalid auth configuration stops startup instead of leaving the instance open, for example an unknown `MUXIMUX_AUTH_METHOD`, or OIDC variables without `MUXIMUX_AUTH_METHOD` on an instance whose `config.yaml` is not already on OIDC. An unknown `MUXIMUX_OIDC_*` name is logged as a warning. The full rules, with examples, are in [Configuring OIDC with environment variables](authentication.md#configuring-oidc-with-environment-variables); Compose and Swarm examples with Docker secrets are in [Deployment](deployment.md#oidc-from-environment-variables-and-docker-secrets). ### Container image These are read by the Docker image's entrypoint, not by Muximux itself: | Variable | Purpose | Default | |----------|---------|---------| | `PUID`, `PGID` | User and group ID the process runs as, to match your bind-mounted `data/` directory | `1000` | | `DOCKER_SOCKET` | Path of the Docker socket whose group the entrypoint detects | `/var/run/docker.sock` | | `DOCKER_GID` | Force the group ID used for Docker socket access instead of detecting it | Detected | | `TZ` | Time zone | `UTC` | ### Booleans, precedence and the memory-only rule - **Booleans** accept `true`, `1`, `yes`, `on` and `false`, `0`, `no`, `off` (case-insensitive). - **Precedence:** command-line flag, then environment variable, then the `config.yaml` value (including a `${VAR}` reference there), then the built-in default. - **Memory only.** An override changes the running configuration only. Saving from Settings (or through the API) writes the value `config.yaml` already had, never the override, so removing the variable brings the file's value back on the next start. The one exception is a setup wizard completed under `MUXIMUX_AUTH_METHOD=builtin`, which saves `method: builtin` with the new admin. - **Locked in Settings.** A field whose value comes from a variable or flag is shown locked, labelled with the variable's name ("From MUXIMUX_LISTEN", "Set by MUXIMUX_OIDC_CLIENT_SECRET_FILE"). The admin `GET /api/config` response lists these fields in `env_overrides` (field to variable name, never the value); see the [API Reference](api.md#configuration). - **First start.** The overrides also apply when there is no `config.yaml` yet, so a container configured only through the environment starts with those values. - **Restart.** Variables are read at startup. Change one and restart Muximux. ## Validation Rules Muximux validates the configuration on startup and will refuse to start if the configuration is invalid. The following rules are enforced: - If `tls.domain` is set, `tls.email` is required (for Let's Encrypt registration). - `tls.cert` and `tls.key` must both be set or both empty. You cannot provide only one. - `tls.domain` and `tls.cert` are mutually exclusive. Use either auto-HTTPS or manual certificates, not both. - Legacy `gateway:` (Caddyfile path) configs from 3.0.x are auto-migrated to `gateway_sites:` on first boot under 3.1.0 (clean conversions only; lossy ones surface a hard error pointing at `muximux migrate-gateway`). - If any `gateway_sites[]` entry has `require_auth: true`, `server.session_cookie_domain` is required and every gated site must be a subdomain of it (real host-suffix check: `evilexample.com` is not a subdomain of `example.com`). - `gateway_sites[].min_role` must be one of `user`, `power-user`, `admin` when set (empty means "any authenticated user"). - `server.gateway_listen` must be a valid bind address (e.g., `:8443` or `127.0.0.1:8443`). ## Live Configuration Most settings can be changed through the Settings panel while Muximux is running. Changes are saved to `data/config.yaml` immediately and take effect without restarting. The following settings **require a restart** to take effect: - `server.listen` (listen address/port) - `server.base_path` (subpath prefix; must be a plain path such as `/muximux`: no leading `//`, backslashes, dot segments, whitespace, `?` or `#`) - `server.tls.*` (all TLS settings) - `server.gateway_listen` (overrides the bind port for the embedded Caddy) - `server.session_cookie_domain` (cookie scope; takes effect on next login) - `server.gateway_sites:` schema changes (the auth gate; backend swaps and TLS-mode flips reload Caddy in place) When Muximux saves the configuration it writes defaults for fields you omitted, so a saved `config.yaml` can contain more keys than you wrote by hand. Settings saves are a three-way merge: changes made on the server while Settings is open (a Docker URL refresh, auto-import, another tab) are kept, and fields you did not touch keep the server's value. A save that would create two apps or groups with the same name is refused (409) and Settings blocks Save until one is renamed. Settings closes only after the save succeeds; on failure it stays open with the server's message. Closing it with unsaved changes asks first, and **Discard** also reverts keybinding and theme previews. Every open browser gets a live update (the `config_updated` event) and refetches the config; the page reloads only when the language changed, and an open Settings dialog keeps its unsaved edits on top of the new state. A restored backup is read exactly like `config.yaml` at startup: `${VAR}` expansion, defaults and validation. Restore reloads users, auth, OIDC, discovery and proxy routes at once; listen address, TLS, base-path routing and session cookie settings still need a restart. Everything else -- navigation, themes, apps, groups, icons, keybindings, health monitoring, log level, log format -- is applied immediately. ## Config Export and Import You can export your configuration as a downloadable YAML file and import it on another instance: - **Export** strips sensitive data (password hashes, OIDC secrets, API key hashes) before download. - **Import** parses the uploaded YAML, validates it, and shows a preview so you can review before applying. This is available in the Settings panel under the General tab, or via the API (see [API Reference](api.md)). ## Config API The configuration can also be read and updated via the API: - `GET /api/config` -- Retrieve the current configuration (any authenticated user). - `PUT /api/config` -- Update the full configuration (admin role required). - `GET /api/appearance` -- Read-only snapshot of language, theme id, and theme colors, for embedded apps that want to sync their own appearance (see [Apps > Appearance API](apps.md#appearance-api)). See the [API Reference](api.md) for details.