Skip to main content

Troubleshooting

Common problems when running the desk, what causes them and what to check. Error codes are the ones the API returns in { "error": { "code": "…", "message": "…" } }; log lines are from the desk's container log.

The desk doesn't start​

The log showsCause
Invalid production configuration and a list of reasonsDevelopment secrets, a secret shorter than 32 characters, the same value for both secrets, or a test switch. See what production refuses.
Invalid configurationA value doesn't parse, often a missing DATABASE_URL.
It stops at a migrationThe database couldn't be migrated; see upgrades.

The Docker Compose setup​

With the recommended setup, run these in deploy/:

CommandShows
docker compose psWhat runs, and whether desk is healthy.
docker compose logs deskThe app's log.
docker compose logs caddyThe proxy's log.
SymptomLikely causeFix
set … in .env when startingA required value is missing from .env (DOMAIN, the five secrets). Compose names the missing value.Fill in the value it names.
No certificate, or the browser warnsCaddy needs DOMAIN's DNS to point to the server, and ports 80 and 443 to reach it from the internet.docker compose logs caddy shows the reason, such as a DNS mismatch, a closed port or a rate limit. Certificates are kept in the caddy-data volume, so restarts don't request new ones.
The build runs out of memoryBuilding needs about 2 GB.Add swap, or build on another machine and copy the image over (docker save and docker load), tagged itsmengine:local.
Ports 80 or 443 are already in useAnother web server is running.Stop it, or use it as the proxy instead of Caddy; see the installing notes on existing proxies.

Where to find the link:

  • With SETUP_TOKEN set, the link is https://<your address>/setup?token=<SETUP_TOKEN>; it works until the first admin exists. A SETUP_TOKEN shorter than 24 characters is ignored (the log says so).

  • Otherwise the link is in the log, in a line starting with No admin yet. It's the same on every restart until it's used:

    docker logs desk 2>&1 | grep "No admin yet"

When it doesn't work:

SymptomLikely causeFix
setup_link_invalid ("That setup token isn't right, or the link was used already")The token is wrong, or the link was opened already, maybe in another browser.Check the token. Restart the desk; it prints a new one. The browser that opened the first link can carry on.
setup_link_required ("Open the setup link from the log")This browser has no setup session. A setup session lasts 24 hours.Open the link again in it.
The address in the link is wrongThe public address isn't set yet.Open /setup?token=… on the address you reach the desk at.
The wizard keeps asking for the linkThe setup session cookie needs https. Over plain http (other than for local development with COOKIE_SECURE=false), the browser drops it.Serve the desk over https.

Without a browser, or when every admin is locked out, create an admin with create-admin instead (see installing).

Nobody can stay signed in​

Session cookies are only sent over https (COOKIE_SECURE). Serve the desk through a reverse proxy with TLS and set Public address to the https address.

People signed out after a while without using the desk: that's SESSION_IDLE_HOURS (12 by default).

No email arrives​

  • Settings → Installation, Still to do: "No email server: nothing can be sent" or "Resend needs an API key".
  • Send test email under Settings → Installation, Email, tries the values in the form and shows the transport's own error (for example a rejected login on the SMTP server, or Resend: … with Resend's reason).
  • In the log, every email sent has a line from Mail starting with sent "…". Emails are sent from the job queue, and a failed send is retried 5 times, waiting longer each time (about half an hour in all), then dropped.
  • Without a transport, sends fail with Email isn’t set up: choose Resend or SMTP.
  • Arrived in spam, or refused by the receiver: check that the sender domain (the brand's sender address, or Default sender) is verified with your email provider, with SPF and DKIM records.

Attachments and logos disappear after a restart​

There is no file storage: "No file storage: attachments and logos are kept in memory and lost on restart".

  1. Set up S3-compatible storage under Settings → Installation, File storage.
  2. Use Test upload, which writes, reads back and deletes a small file.

[!WARNING] Files uploaded before that are gone.

Browsers never talk to the storage directly: the desk serves every file itself.

Wrong client address behind a proxy​

Symptoms:

  • everyone gets rate_limited at once;
  • a server-mode form answers ip_not_allowed with the proxy's address in the message (IP … is not allowed for this form);
  • the audit log shows the same address for everyone.

The desk only believes forwarded addresses (X-Forwarded-For, or CF-Connecting-IP when there is none) from Trusted proxies (see configuration).

  1. Open Settings → Installation, Name and address. It shows Your address, as the desk sees it and Connected from (the direct connection), plus the forwarded headers it received.
  2. Add the address or range under Connected from to Trusted proxies until "your address" is your own.
  3. For a proxy chain, add every proxy.

[!CAUTION] Don't add 0.0.0.0/0: anyone could then claim any address.

Turnstile refuses submissions​

WhereMessageCause
Form or portalturnstile_missing, "Please complete the bot check"The page sent no token. The widget didn't load (a script blocker, or a headless form without the widget).
Form or portalturnstile_failed, "The bot check failed. Please try again."Cloudflare rejected the token. The log shows Turnstile rejected: <codes> with Cloudflare's reason.
LogTURNSTILE_SECRET_KEY is not set; refusingNo secret key: every check fails.
Settings → Installation"Cloudflare refused the site key for this address"The site key isn't valid for the domain the page is on. Add the domain to the widget in Cloudflare.
Check the keys"Cloudflare refused the check: is the secret key for the same site key?"The secret key belongs to another widget.

Common codes from Cloudflare: invalid-input-secret (wrong secret key), timeout-or-duplicate (the token was used already or is older than five minutes; reload the page).

/healthz warnings and 503s​

GET /healthz stays 200 while warnings lists settings that still need attention. The details are shown only on the server itself (see installing):

WarningFix
APP_URLSet the Public address, with https in production.
SMTP_URL, RESEND_API_KEYSet up email.
S3_ENDPOINTSet up file storage.
TURNSTILE_SITE_KEYSet real Turnstile keys (missing, or Cloudflare's test keys in production).
INBOUND_SECRET, INBOUND_PROVIDERFinish incoming email: the service's secret, or the Resend API key for Resend.

It also stays 200 when unknownMigrations lists migrations the database has but this image doesn't know: an older image runs on a newer database, for example after a rollback without restoring the backup. Start the image that matches the database (see rolling back).

It answers 503 when:

AnswerCauseFix
"database":"unreachable"The desk can't reach Postgres.Check DATABASE_URL, that Postgres runs, and the network between them.
"pendingMigrations":[…]The image is newer than the database.Run the migrations, or leave MIGRATE_ON_START on (see upgrades).

Assistants can't connect (OAuth)​

[!IMPORTANT] Hosted assistants (claude.ai, ChatGPT) connect from the internet, so the desk must be reachable at its public address, over https, from outside.

Then check:

  • Public address is exactly the address in the connector (https://support.example.com/mcp). The desk names itself with it in its OAuth metadata; a different address makes the approval end with invalid_target.
  • https://support.example.com/.well-known/oauth-authorization-server and https://support.example.com/.well-known/oauth-protected-resource answer with JSON from outside your network.
  • The proxy passes the Authorization header and doesn't buffer or cut off streamed responses on /mcp.

More in MCP server.

A website can't use a form​

The browser console shows a CORS error, or the form answers origin_not_allowed ("This website may not use this form").

The site's origin must be listed under Allowed websites on the form (Settings → Forms, the form, Security), exactly: scheme, host and port, no path or trailing slash (https://www.example.com).

  • example.com and www.example.com are different origins.
  • The desk's own hosted form is always allowed.

More on the form's settings in forms.

Other refusals and their codes are in form submissions.

Too many sign-in attempts​

Sign-in answers rate_limited ("Too many attempts, try again later") when a limit is reached. Wait for the window to pass.

WhatLimit
Sign-in20 attempts from one address, or 10 for one email address, within a 10-minute window.
Forgot password5 requests per address per 15 minutes, and 3 per email address per hour.
Two-factor codes10 tries per 10 minutes.

[!TIP] If it happens to many people at once, check the client address: without trusted proxies, everyone behind the proxy shares one address.

Live updates don't arrive​

Sidebar counts, New activity on a case and colleagues on a case should change within seconds. If they only change every few minutes, or when you move to another page, the live-update connection (/api/v1/stream) isn't getting through.

SymptomLikely causeFix
Updates arrive lateA proxy in front of the desk buffers or compresses it.Turn buffering off for /api/v1/stream (nginx: proxy_buffering off;), leave it uncompressed, and allow it to stay open longer than 30 seconds. The bundled Caddyfiles already do. See install.
The browser shows the request failing in its developer tools (Network, stream)A 401 means the session ended.Sign in again.

The desk keeps working without it; it falls back to asking every few minutes.

Scale: colleagues on another server don't show on a case​

People on different application servers only see each other on a case through Redis. Check that every application server has the same REDIS_URL and can reach it: the app logs Redis errors when it can't. With compose.scale.yaml this is set for you.