Nosdesk is configured entirely through environment variables. In a Docker Compose install they live in the .env file beside your compose.yaml; running the backend directly, they come from the process environment.
Only a handful of variables are required. Everything else has a sensible default. Variables marked required must be set for a production boot.
Boolean values accept true/false, 1/0, yes/no, and on/off, case-insensitive.
Core secrets
These protect sessions and encrypt data at rest. Generate fresh values; never reuse the examples.
| Variable | Default | Description |
|---|
JWT_SECRET | required | Signing key for session tokens. Generate with openssl rand -base64 32. |
MFA_KEK_V1 | required | Key-encryption key for at-rest encryption of MFA secrets, channel credentials, and plugin secrets. 64 hex characters (32 bytes); generate with openssl rand -hex 32. |
MFA_KEK_V{n} | (unset) | Additional keyring generations for zero-downtime key rotation. Provision MFA_KEK_V2, point new writes at it, re-wrap, then retire V1. |
MFA_KEK_VERSION | highest loaded | Selects which keyring generation new writes use. Single-key installs can omit it. |
ALLOW_INSECURE_DEFAULT_SECRETS | false | Lets the stack boot with the example Postgres/Redis passwords. Test environments only. Never set in production. |
The single-key ENCRYPTION_KEY / MFA_ENCRYPTION_KEY variables are a legacy path superseded by the MFA_KEK_* keyring. New installs should use the keyring.
Server and networking
| Variable | Default | Description |
|---|
HOST | 127.0.0.1 | Bind address the backend listens on. Code default is loopback, but in the Docker image you set HOST=0.0.0.0 so the published port can reach the container. Host exposure stays restricted to loopback by the 127.0.0.1:8080:8080 port mapping in compose.yaml, not by this value. |
PORT | 8080 | Port the backend listens on. |
ENVIRONMENT | development | Deployment environment. Anything other than development/dev makes session cookies Secure (fail-closed). Set to production. |
FRONTEND_URL | http://localhost:3000 | Public URL of the app. Drives CORS and cookie scope. Set to your HTTPS origin. |
ADDITIONAL_CORS_ORIGINS | (unset) | Comma-separated extra origins allowed through CORS. |
TRUSTED_PROXIES | (unset) | Comma-separated CIDR ranges whose X-Forwarded-For Nosdesk will trust. Unset means the header is ignored (the safe default with no proxy). Set it when running behind a reverse proxy so rate limiting and audit logs see the real client IP — see the Reverse proxy + TLS guide. |
NOSDESK_TENANT_DOMAIN | (unset) | Hosted-mode only. Suffix that lets every <slug>.<domain> origin pass CORS. Leave unset for self-hosted. |
NOSDESK_DEPLOYMENT_MODE | self-hosted | Switches between single-tenant (self-hosted) and subdomain-routed multi-tenant (hosted). Leave at the default for self-hosting. |
Database
| Variable | Default | Description |
|---|
DATABASE_URL | required | PostgreSQL connection string. In Compose: postgres://nosdesk:<password>@postgres:5432/nosdesk. |
POSTGRES_DB | nosdesk | Database name. |
POSTGRES_USER | nosdesk | Database user. |
POSTGRES_PASSWORD | required | Database password. Must match the credentials in DATABASE_URL. |
DB_MAX_CONNECTIONS | 10 | Maximum pooled connections. |
DB_MIN_CONNECTIONS | 1 | Minimum idle connections kept warm. |
DB_CONNECTION_TIMEOUT | 30 | Seconds to wait for a connection before erroring. |
SSL mode is not a separate variable — set it in the DATABASE_URL query string (e.g. ?sslmode=require) when the database is on a separate host. Migrations run automatically on backend startup; there is no separate migrate step.
Redis and rate limiting
| Variable | Default | Description |
|---|
REDIS_URL | required (production) | Redis connection string. In Compose: redis://:<password>@redis:6379. Redis is a hard dependency in production — it backs distributed rate limiting, the collaborative-editing cache, and the readiness probe. A production instance with REDIS_URL unset refuses to boot; there is no in-memory fallback. Only development/dev mode falls back to redis://localhost:6379. |
REDIS_PASSWORD | required (Compose) | Redis password. Must match the credentials in REDIS_URL. |
RATE_LIMIT_PER_MINUTE | 60 | Max requests per IP per minute for unauthenticated callers. |
AUTH_RATE_LIMIT_PER_MINUTE | 600 | Max requests per minute for authenticated callers. |
Storage and uploads
| Variable | Default | Description |
|---|
STORAGE_TYPE | local | Storage backend: local or s3. An unrecognised value fails loudly at startup. |
STORAGE_PATH / UPLOAD_DIR | /app/uploads | Filesystem path for uploaded files (local backend). The Compose backend_uploads volume persists this. |
MAX_FILE_SIZE_MB | 50 | Maximum upload size in megabytes. |
For STORAGE_TYPE=s3, Nosdesk reads BUCKET_NAME, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ENDPOINT_URL_S3, and AWS_REGION (with STORAGE_S3_* equivalents for non-AWS S3-compatible stores). Any required value missing fails at startup. With the default local backend, uploaded files are captured by the application backup (and you can also snapshot the uploads volume directly) — see the Backup & restore guide.
Authentication and sessions
| Variable | Default | Description |
|---|
REQUIRE_ADMIN_MFA | true | Require administrators to set up MFA. Disable only for local development. |
REQUIRE_PASSWORD_COMPLEXITY | true | Enforce password complexity rules on local accounts. |
MFA_MAX_ATTEMPTS | (internal default) | Failed-attempt cap before an MFA lockout. |
MFA_WINDOW_SECONDS | (internal default) | TOTP acceptance window in seconds. |
Session lifetime is fixed and not operator-configurable: access tokens last 15 minutes and are transparently refreshed from a 7-day session, so sign-ins survive a working day without re-authenticating.
Passkeys (WebAuthn)
| Variable | Default | Description |
|---|
WEBAUTHN_RP_ID | host of FRONTEND_URL | Relying-party ID. Usually your bare domain (nosdesk.example.com). |
WEBAUTHN_RP_NAME | Nosdesk | Human-readable relying-party name shown in the passkey prompt. |
WEBAUTHN_RP_ORIGIN | FRONTEND_URL | Full origin (scheme + host) passkeys are bound to. Must be HTTPS in production. |
Bootstrap admin
Optional. Pre-seeds the first administrator for headless provisioning instead of using the browser first-run screen.
| Variable | Default | Description |
|---|
INITIAL_ADMIN_NAME | (unset) | Display name for the seeded admin. |
INITIAL_ADMIN_EMAIL | (unset) | Email for the seeded admin. |
INITIAL_ADMIN_PASSWORD_HASH | (unset) | Pre-hashed password for the seeded admin. |
BOOTSTRAP_TOKEN_TTL_SECONDS | 1800 (30 min) | Lifetime of the one-time token that authorises first-run setup. After it lapses, restart the server to mint a fresh setup link. |
Single sign-on
Microsoft Entra ID
| Variable | Default | Description |
|---|
MICROSOFT_CLIENT_ID | (unset) | Application (client) ID. |
MICROSOFT_TENANT_ID | (unset) | Directory (tenant) ID. |
MICROSOFT_CLIENT_SECRET | (unset) | Client secret. |
MICROSOFT_REDIRECT_URI | (unset) | OAuth callback, e.g. https://your-domain/auth/microsoft/callback. |
Generic OIDC
Works with Keycloak, Auth0, Okta, Google, Authentik, Authelia, and other compliant providers.
| Variable | Default | Description |
|---|
OIDC_CLIENT_ID | (unset) | OIDC client ID. |
OIDC_CLIENT_SECRET | (unset) | OIDC client secret. |
OIDC_ISSUER_URL | (unset) | Issuer URL for endpoint auto-discovery (recommended). |
OIDC_AUTH_URI / OIDC_TOKEN_URI / OIDC_USERINFO_URI | (unset) | Manual endpoints if the provider lacks discovery. |
OIDC_REDIRECT_URI | (unset) | OAuth callback, e.g. https://your-domain/auth/oidc/callback. |
OIDC_DISPLAY_NAME | OpenID | Label on the SSO login button. |
OIDC_SCOPES | openid profile email | Requested scopes. |
OIDC_USERNAME_CLAIM | preferred_username | Claim used as the username. |
OIDC_LOGOUT_URI | (unset) | Provider logout URL for single logout. |
Email (SMTP)
Outbound email powers password resets, invitations, and notifications.
| Variable | Default | Description |
|---|
SMTP_ENABLED | false | Master switch for outbound email. |
SMTP_HOST | (required when enabled) | SMTP relay hostname. |
SMTP_PORT | 587 | Relay port. 587 for STARTTLS, 465 for implicit TLS. |
SMTP_SECURITY | starttls | Transport security: starttls, tls (implicit, port 465), or plaintext (local test servers only). |
SMTP_USERNAME | (required when enabled) | Auth username. |
SMTP_PASSWORD | (required when enabled) | Auth password or app-specific password. |
SMTP_FROM_NAME | Nosdesk | Display name on sent mail. |
SMTP_FROM_EMAIL | SMTP_USERNAME | From address. |
Logging and observability
| Variable | Default | Description |
|---|
RUST_LOG | info,tantivy=warn,... | Log level and per-crate filters. Use debug for troubleshooting. |
LOG_FORMAT | text | Set to json for structured logs with request-correlation fields, suited to log aggregation. |
Data retention
Background jobs prune aged records on these schedules.
| Variable | Default | Description |
|---|
AUDIT_LOG_RETENTION_DAYS | 540 (18 months) | How long to keep audit_log partitions. |
SYNC_ACTIONS_RETENTION_DAYS | 90 | How long to keep the sync_actions change-event log. |
NOSDESK_USER_PURGE_GRACE_DAYS | (internal default) | Grace period before a soft-deleted user is hard-purged. |
WORKSPACE_HARD_DELETE_GRACE_DAYS | (internal default) | Grace period before an archived workspace is hard-deleted. |
Session geolocation
| Variable | Default | Description |
|---|
GEOIP_DB_PATH | (unset) | Path inside the container to a MaxMind-format .mmdb database. Adds a coarse “City, Country” to each active session. Fully offline; no IP leaves the server. Leave unset to disable. You supply the database (DB-IP Lite, MaxMind GeoLite2, or a commercial DB). |
Plugins and registry
| Variable | Default | Description |
|---|
NOSDESK_ROOT_PUBKEY | baked into official images | Ed25519 root public key the binary uses to verify official registry plugins. Compiled in at build time, so the official ghcr.io image already carries it; you do not set this at runtime. It only matters if you build your own image. |
NOSDESK_REGISTRY_URL | https://nosdesk.com/registry | Plugin registry base URL. Set to empty to disable registry sync (air-gapped). |
NOSDESK_ALLOWED_PLUGIN_TIERS | official,local | Comma-separated trust tiers permitted to run: official, verified, community, local. Verified and community run third-party code in-process, so they stay off until the sandbox ships. |
NOSDESK_ALLOW_WEB_SIDELOAD | false | Allow admins to upload a signed plugin zip through the browser. CLI install always works regardless. |
PLUGIN_SANDBOX_ORIGIN | (unset) | Separate origin that will serve the cross-origin iframe sandbox for community plugins. |
NOSDESK_PLUGINS_DIR | /app/plugins | Directory scanned for filesystem-provisioned signed plugin zips. |
Microsoft Graph sync
Used when importing users and devices from Microsoft Intune / Entra.
| Variable | Default | Description |
|---|
MSGRAPH_CONCURRENT_USER_PROCESSING | 5 | Concurrent user-processing workers during a sync. |
MSGRAPH_USER_BATCH_SIZE | 10 | Users fetched per batch. |
MSGRAPH_SKIP_DISABLED_ACCOUNTS | true | Skip disabled accounts during sync. |
MSGRAPH_SYNC_SECURITY_GROUPS / MSGRAPH_SYNC_M365_GROUPS / MSGRAPH_SYNC_DISTRIBUTION_LISTS / MSGRAPH_SYNC_DYNAMIC_GROUPS | varies | Toggles for which group types to sync. |
MSGRAPH_BACKGROUND_PHOTOS | (default) | Fetch user profile photos in the background. |
Advanced and security toggles
Most installs never touch these.
| Variable | Default | Description |
|---|
NOSDESK_STATE_DIR | (default) | Base directory for runtime state (search index, etc.). |
SEARCH_INDEX_PATH | under state dir | Path to the Tantivy full-text search index. |
CSP_REPORT_ONLY | false | Run the Content-Security-Policy in report-only mode (logs violations without blocking). Useful when tuning a custom proxy. |
NOSDESK_OUTBOUND_ALLOWED_HOSTS | (unset) | Allowlist for server-side outbound requests (SSRF guard) where applicable. |
NOSDESK_ALLOW_INSECURE_TLS | false | Skip TLS verification on outbound connections. Debugging only. |
NOSDESK_ALLOW_FRONTEND_DEBUG_LOGS | false | Permit verbose frontend debug logging in production builds. |
NOSDESK_WS_HEARTBEAT_MS / NOSDESK_WS_CLIENT_TIMEOUT_MS | (defaults) | WebSocket heartbeat interval and client timeout for collaborative editing. |