Configuration
Every value the desk reads: the few that stay in the environment, a handful of switches, and the installation settings you normally manage in the browser.
How values are chosen
For each installation setting the desk uses, in this order:
- The environment variable of the same name, if it's set. An empty value counts as not set.
- The value stored in the database by the setup wizard or Settings → Installation. A stored value that is no longer valid is ignored.
- The default below.
A setting that comes from the environment shows as Set by the environment in the wizard and under Settings → Installation, and can only be changed there. This keeps infrastructure-as-code setups working: set everything in the environment and the browser only shows it.
With several instances, the others pick up a change made in the browser within 30 seconds.
[!NOTE] Secret settings are stored encrypted with
ENCRYPTION_KEYand never shown again. To change one, type the new value; leave the field empty to keep the stored one.
Environment only
| Variable | Default | What |
|---|---|---|
DATABASE_URL | (required) | Postgres 17 connection string, postgresql://user:password@host:5432/desk. |
DATABASE_POOL_SIZE | 20 | Database connections each app server keeps open at most. The job queue uses up to 10 more. Keep the total for all app servers under Postgres's max_connections (100 by default). See system requirements. |
DATABASE_DIRECT_URL | (empty) | Scale: a connection straight to Postgres, for migrations and the job queue, when DATABASE_URL goes through PgBouncer. Empty: they use DATABASE_URL. compose.scale.yaml sets it. See Standard and Scale. |
REDIS_URL | (empty) | Scale: Redis (or Valkey), redis://host:6379 or rediss:// for TLS, for who is viewing a case and messages between app servers. Empty: kept in memory, as Standard does with one app server. compose.scale.yaml sets it. |
JOBS_WORKER | true | Whether this process runs background jobs and schedules. Scale sets it to false on the app servers and true on the worker; with one app server leave it on. |
SESSION_SECRET | (required) | Signs the requesters' case-page cookies, the satisfaction survey links in emails, form start tokens, the setup wizard's cookie and the Message-IDs of outgoing emails. 32+ random characters in production. |
ENCRYPTION_KEY | (required) | Encrypts secrets at rest: form and webhook signing secrets, two-factor secrets, stored case links and the secret installation settings. 32+ random characters in production, different from SESSION_SECRET. Never change it on a running installation; see backups. |
PORT | 3000 | The port the desk listens on (plain http). |
Make each secret with openssl rand -base64 48.
[!WARNING] Never change
ENCRYPTION_KEYon a running installation; see backups.
Two more are read by compose.scale.yaml rather than the app, for Scale:
| Variable | Default | What |
|---|---|---|
APP_SERVERS | 2 | How many application servers run. |
PGBOUNCER_POOL_SIZE | 40 | How many Postgres connections PgBouncer shares among them. |
Switches
| Variable | Default | What |
|---|---|---|
NODE_ENV | development (the image sets production) | Production turns on the startup checks below and stricter rules, such as https-only webhook targets on public addresses. |
COOKIE_SECURE | true | Cookies are only sent over https. Must stay true in production; false is for local development over plain http. With true, the staff session cookie is __Host-desk_session: only this host, over https, can set it. |
SESSION_IDLE_HOURS | 12 | A staff session that sends no request for this many hours ends, and the person signs in again. Sessions end after 14 days in any case. 1 to 336. |
MIGRATE_ON_START | true | Read by the image's entrypoint: run prisma migrate deploy before the server starts. See upgrades. |
SETUP_TOKEN | (empty) | Opens the setup wizard at /setup?token=<SETUP_TOKEN> while the desk has no admin; 24 characters or more (openssl rand -hex 32). Empty: the desk prints a one-time link to its log. Does nothing once the first admin exists. See the setup wizard. |
APP_VERSION | (the image's) | Set when a release image is built; Settings → Installation shows it. Don't set it yourself. |
LICENSE_DEV_PUBLIC_KEY | (empty) | Development only: the public key development licence keys are signed with. Ignored in production. |
DEMO_MODE | false | A public demo, read-only for everyone: visitors sign in as one of the example company's people with one click, no password, and every change is refused, as are downloads, the API's writes, MCP, form and portal submissions and incoming email. No email is sent. node dist/demo-reset.js writes the demo again from scratch (run it nightly); it refuses to run without DEMO_MODE, because it empties the database. Never on a real desk. |
JOBS_INLINE | false | Test only: run background jobs inside the request instead of the queue. Not allowed in production. |
Boolean values accept true or 1; anything else is false.
[!CAUTION] Never set
DEMO_MODEon a real desk:node dist/demo-reset.jsempties the database.
Installation settings
The labels are the ones in the wizard and under Settings → Installation.
Name and address
| Variable | Label | Default | What |
|---|---|---|---|
APP_NAME | Product name | ITSM Engine | This installation's name: shown in the app, in emails to your team and in page titles. The ITSM Engine credit at the bottom of the app stays as it is. |
APP_URL | Public address | http://localhost:3000 | Where people reach the desk, without a path. Links in emails, snippets and OAuth use it. |
TRUSTED_PROXIES | Trusted proxies | empty | Proxies in front of the desk, as addresses or ranges, comma-separated (10.0.0.0/8, 2001:db8::/32). Only their forwarded client addresses are believed. |
OPERATOR_NAME | Operated by | empty | Who runs this installation: shown as "Operated by …" next to the ITSM Engine credit at the bottom of the app. |
OPERATOR_URL | Operator’s website | empty | Link for the name above. |
How the client address is read:
- With trusted proxies set, and only when the connection comes from a trusted proxy, the desk reads the client
address from
X-Forwarded-For: the first address from the right that isn't a trusted proxy. - Cloudflare appends to that header too.
CF-Connecting-IPis used only when a trusted proxy sends noX-Forwarded-For.
Email
| Variable | Label | Default | Secret | What |
|---|---|---|---|---|
MAIL_TRANSPORT | Send email with | smtp | smtp or resend. (memory is for tests, can only be set in the environment, and is refused in production.) | |
SMTP_URL | SMTP server | empty | yes | smtp://user:password@host:587, or smtps:// for port 465. |
RESEND_API_KEY | Resend API key | empty | yes | From resend.com, allowed to send from your domains. |
MAIL_DEFAULT_FROM | Default sender | ITSM Engine <[email protected]> | Used when a brand has no sender address of its own, and for emails to your team. |
Incoming email
Replies by email (email replies). Under Settings → Installation → Incoming email.
| Variable | Label | Default | Secret | What |
|---|---|---|---|---|
INBOUND_PROVIDER | Email service | none | none, cloudflare, resend, mailgun or postmark: the service that posts incoming email to the desk. With none, emails say that replies aren't read. | |
INBOUND_SECRET | Secret | empty | yes | What the service proves itself with: Resend's signing secret (whsec_…), Mailgun's webhook signing key, or one you choose for Cloudflare and Postmark. |
INBOUND_DOMAIN | Reply domain | empty | The domain of the reply addresses (t-<code>@reply.example.com), a subdomain of its own. Brands can set their own. |
File storage
Any S3-compatible storage.
[!WARNING] Without it, attachments and logos are kept in memory and lost on restart.
| Variable | Label | Default | Secret | What |
|---|---|---|---|---|
S3_ENDPOINT | Endpoint | empty | https://storage.example.com. | |
S3_REGION | Region | us-east-1 | ||
S3_BUCKET | Bucket | desk | Created if it doesn't exist. | |
S3_ACCESS_KEY | Access key | empty | ||
S3_SECRET_KEY | Secret key | empty | yes |
With the bundled storage of the compose setup, S3_ACCESS_KEY and S3_SECRET_KEY are the app's own key, limited to
its bucket; storage-init sets it up on each start, so changing S3_SECRET_KEY in .env and running
docker compose up -d changes it. The storage's admin password is STORAGE_ADMIN_PASSWORD, which only the storage
and storage-init see.
Bot protection
| Variable | Label | Default | Secret | What |
|---|---|---|---|---|
TURNSTILE_SITE_KEY | Site key | empty | Cloudflare Turnstile site key for the desk's domain. | |
TURNSTILE_SECRET_KEY | Secret key | empty | yes | The matching secret key. |
Forms and portals with the bot check turned on refuse submissions until both keys are set.
Policies
| Variable | Label | Default | What |
|---|---|---|---|
TOTP_REQUIRED_FOR_ADMINS | Admins must use two-factor authentication | true | Admins set up an authenticator app before they reach the admin pages. |
INBOUND_REQUIRE_AUTH | Hold email replies whose sender isn't verified | true | An email reply joins its case only when DMARC, SPF or DKIM vouches for the sender. Other replies, and those the check couldn't verify, wait in Email review for an agent. DMARC failures and forged SPF are always held. |
AUTO_CLOSE_DAYS | Close resolved cases after (days) | 7 | At least 1. The default; an organization can set its own under Settings → Organization. |
LICENSE_KEY | Licence key (in the setup wizard and under Settings → Installation, Licence) | (empty) | The free key from itsmengine.com. Checked offline; without one nothing is limited. See the licence key. |
LICENSE_CHECK_IN | Check in once a day (Settings → Installation, Licence) | false | Once a day, send the licence key's fingerprint and the version to the licence service, to hear if the key was replaced or withdrawn. Changes nothing in the desk. See the daily check-in. |
LICENSE_SERVICE_URL | (environment only) | https://api.itsmengine.com | Where the daily check-in goes. |
MAX_UNIT_DEPTH | Levels in the unit tree | 4 | 1 to 10, counting the organization. |
Updates
| Variable | Label | Default | What |
|---|---|---|---|
UPDATE_CHECK | Check for updates once a day (Settings → Updates) | true | The desk asks GitHub for the releases of ITSM Engine once a day; the sidebar and Settings → Updates show Update available and every release's notes. Nothing about the desk is sent. See upgrades. |
Language
| Variable | Label | Default | What |
|---|---|---|---|
LANGUAGES | Settings → Language | en | The languages the desk speaks to requesters in, from en, no, sv and da, like en,no. English is always on. With English alone, texts have no translation tabs. See Language. |
What production refuses
With NODE_ENV=production (the image), the desk does not start, and prints Invalid production configuration with
the reasons, when:
SESSION_SECRETorENCRYPTION_KEYis shorter than 32 characters, or is a development defaultENCRYPTION_KEYis the same asSESSION_SECRETCOOKIE_SECUREis falseJOBS_INLINEis setMAIL_TRANSPORTismemory
[!NOTE] A value that doesn't parse at all (for example a missing
DATABASE_URL, or anAPP_URLthat isn't a URL in the environment) stops the start withInvalid configurationin any mode.
What is only a warning
These don't stop the desk. They are listed under Still to do at the top of Settings → Installation, and as
warnings (the setting names) in GET /healthz, which stays healthy:
| Warning | Setting in /healthz |
|---|---|
| The public address isn’t set | APP_URL |
| The public address must use https (production only) | APP_URL |
| No email server: nothing can be sent | SMTP_URL |
| Resend needs an API key | RESEND_API_KEY |
| No file storage: attachments and logos are kept in memory and lost on restart | S3_ENDPOINT |
| No bot protection: forms with the bot check turned on refuse submissions | TURNSTILE_SITE_KEY |
| Cloudflare’s test keys let everything through (production only) | TURNSTILE_SITE_KEY |
| Incoming email needs the secret the service signs with | INBOUND_SECRET |
| Incoming email through Resend needs the Resend API key | INBOUND_PROVIDER |