Start with the checks for your deployment type, then follow the symptom below.
Run sim-setup doctor
On a production or local-development Compose install, or a source checkout, run the built-in checker first. It does not detect the Ollama stack. It checks configuration and common dependency failures. It reads env files, so it is not a Kubernetes tool — on Helm, skip to the checks below.
bunx sim-setup doctor
bunx sim-setup doctor --fix # repair what it safely can
bunx sim-setup doctor --json # machine-readable, for CIOn a standalone Compose install, run it from the directory holding your .env and Compose file. From a source checkout, run it there — it resolves the per-application env files itself and needs no root .env. It exits 1 when any check fails, so scripts can use the exit status. See Verify Your Install for what each group of checks covers.
502 Bad Gateway
# Check app is running — use the file that started your install
docker compose -f docker-compose.prod.yml ps simstudio
docker compose -f docker-compose.prod.yml logs simstudio
# Common causes: out of memory, database not readyDatabase connection failed
# The file that started your install — no bare `docker compose` works, the
# repo ships no default docker-compose.yml.
COMPOSE_FILE=docker-compose.prod.yml
# Check database is running
docker compose -f "$COMPOSE_FILE" ps db
# Test connection
docker compose -f "$COMPOSE_FILE" exec db psql -U postgres -c "SELECT 1"Verify DATABASE_URL format: postgresql://user:pass@host:5432/database
Migration errors
Migrations run in their own migrations service and image — the app image does not contain the migration tooling.
# View migration logs
docker compose -f docker-compose.prod.yml logs migrations
# Re-run them
docker compose -f docker-compose.prod.yml up --force-recreate migrationsOn Kubernetes, migrations are an init container on the app pod:
kubectl logs -n simstudio deploy/sim-app -c migrations --tail=200pgvector not found
Use the correct PostgreSQL image:
image: pgvector/pgvector:pg17 # NOT postgres:17Kubernetes app pods never become ready
Check the migrations init container first — a failed migration deliberately blocks the rollout:
kubectl logs -n simstudio deploy/sim-app -c migrations --tail=200
kubectl describe pod -n simstudio '<pod>'Common causes: DATABASE_URL unreachable, the database user lacking rights to create the vector extension, or an OOMKill from insufficient memory. See Upgrades for migration-failure recovery.
Kubernetes ImagePullBackOff
Either the tag does not exist in the registry (helm get values sim and check), or you are pulling from a private registry without global.imagePullSecrets. When mirroring into a private registry, set global.useRegistryForAllImages: true — otherwise third-party images still point at Docker Hub.
Kubernetes PostgreSQL pod pending
kubectl describe pvc -n simstudioCommon causes are no default StorageClass, no PV provisioner, or a StorageClass that does not support ReadWriteOnce. Set global.storageClass to pick a specific one.
Blank page after login
- Check browser console for errors
- Verify
NEXT_PUBLIC_APP_URLmatches your actual domain - After collecting the error, try a fresh browser session to distinguish a stale session from a deployment failure
- Check that all services are running:
docker compose -f docker-compose.prod.yml ps
WebSocket and realtime not working
- Verify reverse proxy routes
/socket.ioto the realtime service (default port 3002).NEXT_PUBLIC_SOCKET_URLis only needed if realtime is on a separate host. - Verify realtime service is running:
docker compose -f docker-compose.prod.yml ps realtime - Ensure the proxy passes WebSocket upgrades (see Networking)
WebSockets disconnect every 30 seconds
The load balancer's backend timeout is closing them. On GKE, attach a BackendConfig with timeoutSec: 3600 to the realtime Service; on AWS, raise the ALB idle_timeout. Clients may reconnect; repeated disconnects still interrupt collaboration. See Networking.
Agent output arrives all at once
Your reverse proxy is buffering the response stream. Set proxy_buffering off (Nginx) or flush_interval -1 (Caddy). See Networking.
Collaboration breaks with multiple replicas
If members on different replicas do not see edits or live status, check Redis configuration and connectivity in both services. Pub/sub and the Socket.IO adapter have no cross-pod local fallback:
kubectl exec -n simstudio deploy/sim-app -- sh -c 'test -n "$REDIS_URL" && echo "REDIS_URL is set"'
kubectl exec -n simstudio deploy/sim-realtime -- sh -c 'test -n "$REDIS_URL" && echo "REDIS_URL is set"'Both pods must have REDIS_URL. On Helm they share one Secret, so setting it under app.env covers both. See Redis.
App crashes at startup with a REDIS_TLS_SERVERNAME error
The app requires REDIS_TLS_SERVERNAME for an IPv4-literal rediss:// URL. That override is not read by realtime and does not cover IPv6 literals. Prefer a DNS hostname matching the certificate for the shared deployment; see Redis.
Certificate errors (CERT_HAS_EXPIRED)
Rule out expiry and clock drift first: the endpoint's certificate may genuinely have expired, or the host's clock may have drifted far enough to put a valid certificate outside its window. Check the certificate dates and the host date before changing any trust configuration — no CA bundle fixes either.
For a private certificate authority (CA), such as a corporate TLS-inspecting proxy or an internal service, mount the trusted CA certificates and configure NODE_EXTRA_CA_CERTS. For UNABLE_TO_GET_ISSUER_CERT, check whether the endpoint omits an intermediate certificate and repair its served chain. A trusted bundle can contain multiple certificates, but adding a root alone does not supply a missing intermediate.
The image ships CA certificates and runs as a non-root account. Mount your corporate CA bundle and set the runtime variable; do not disable verification. Verify the affected transport, because a client with explicit TLS ca settings may use its own trust configuration:
# docker-compose.prod.yml
services:
simstudio:
volumes:
- /etc/ssl/certs/corporate-ca.crt:/certs/corporate-ca.crt:ro
environment:
- NODE_EXTRA_CA_CERTS=/certs/corporate-ca.crt# Helm — mount a ConfigMap holding the CA
app:
env:
NODE_EXTRA_CA_CERTS: /certs/corporate-ca.crt
extraVolumes:
- name: corporate-ca
configMap:
name: corporate-ca
extraVolumeMounts:
- name: corporate-ca
mountPath: /certs
readOnly: trueNODE_TLS_REJECT_UNAUTHORIZED=0 disables certificate verification entirely and should never be used outside a throwaway test.
A workflow cannot reach a service on your network
Guarded requests to private and reserved addresses are blocked unless a supported self-hosted exception or allowlist applies. Loopback exceptions are scoped by URL provenance and port, so a workflow pointed at your Docker host, a LAN service, or a Kubernetes service name fails with a message naming the blocker — the private or loopback address it resolved to, a blocked port, or must use https:// to a public destination when the URL is plain HTTP — and pointing at the allowlist variables.
Name the destination:
EGRESS_ALLOWED_HOSTS=host.docker.internal,vllm.ai.svc.cluster.local
EGRESS_ALLOWED_IP_RANGES=10.4.2.17/32,10.4.9.0/24What naming a destination does and does not grant — plain HTTP, the blocked-port list, database hosts, cloud metadata, Sim Cloud — is covered once in Security.
Two things this does not cover:
- Inside a container
localhostis the container itself, so it will never reach a service on your host. Usehost.docker.internal(the Compose files map it) and name it above. - URLs harvested from content or from a third-party API response — an image URL, a file imported by URL, an MCP OAuth endpoint on a different origin than the MCP server itself — never reach a private network, allowlist or not. Nor does an HTTP block's
proxyUrl.
Ollama models not showing
Inside Docker, localhost = the container, not your host machine.
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434LM Studio requests route to Ollama
Sim identifies dynamically discovered LM Studio and vLLM models by their vllm/ prefix. If the endpoint is unavailable and you manually enter the raw LM Studio model identifier, Sim treats that unknown identifier as an Ollama model.
-
Set
VLLM_BASE_URL, andVLLM_API_KEYtoo if you enabled LM Studio's API authentication — without the key, discovery requests omit the bearer token and fail. No Compose file sets either for you —docker-compose.ollama.ymlonly setsOLLAMA_URL— so add them to the.envfile next to your Compose file —simstudiodeclaresenv_file: .envin every shipped file — then recreate the service and confirm it landed:Use the Compose file that started your installation —
docker-compose.prod.ymlfor the documented production setup,docker-compose.local.ymlfor a source-built stack, ordocker-compose.ollama.ymlif you started from the Ollama stack:COMPOSE_FILE=docker-compose.prod.yml # or docker-compose.local.yml, docker-compose.ollama.yml docker compose -f "$COMPOSE_FILE" up -d --force-recreate simstudio docker compose -f "$COMPOSE_FILE" exec simstudio printenv VLLM_BASE_URLAn empty result means the variable never reached the container, and model discovery cannot run.
-
In LM Studio, enable Serve on Local Network and API authentication so the container can connect safely.
-
With the shipped Compose host alias, use
http://host.docker.internal:1234rather thanlocalhost, including on Linux. If you use a custom container setup without that alias, configure it or use a reachable host IP. -
The server root and a URL ending in
/v1are both accepted. -
Reload the workspace and select the discovered
vllm/<model-id>option from the model picker.
File uploads fail with a CORS error
The bucket's CORS policy does not allow your Sim origin. Uploads go directly from the browser to object storage via presigned PUT, so server-side configuration being correct is not enough.
If small uploads succeed but cloud uploads over 50 MiB fail during completion, check the app logs for the provider's part-listing request. The server completes multipart uploads from provider-authoritative state; for S3, its identity needs s3:ListMultipartUploadParts. See Object Storage.
Knowledge base upload fails
Embeddings need a provider — set OPENAI_API_KEY, configure Azure OpenAI, set KB_EMBEDDING_MODEL=gemini-embedding-001 with a Gemini key, or set KB_EMBEDDING_MODEL=ollama/<model> with OLLAMA_URL to embed on your own Ollama. On a self-hosted deployment OPENROUTER_API_KEY also serves the OpenAI-family models on its own, as a fallback behind any OpenAI or Azure credentials you have. If one is configured, verify pgvector is installed on the database.
A document that fails with vector 0 has N unexpected dimensions means EMBEDDING_OUTPUT_DIMS does not match what the model actually emits. The message names both widths. If the width the model returned is one of 384, 768, 1024, 1536, or 3072, set the variable to it and recreate the knowledge base. If it is anything else, no column can store it — choose a model that emits one of those five instead, since setting an unstorable width silently falls back to 1536 and the next document fails the same way. Existing knowledge bases keep the width they were created with.
Scheduled workflows never run
Scheduled workflows need the scheduler and a deployed Schedule trigger.
Docker Compose — check the cron service is running and read its logs:
docker compose -f docker-compose.prod.yml logs --tail=50 cronA 401 there means the app and the scheduler disagree on CRON_SECRET.
Kubernetes — check the CronJobs are present and firing:
kubectl get cronjobs -n simstudio
kubectl get jobs -n simstudio --sort-by=.metadata.creationTimestamp | tailFor stale LAST SCHEDULE, inspect CronJob suspension, controller events, active Jobs, and scheduling deadlines. Forbid can skip a tick while an earlier Job is active. For a failed Job, inspect its logs; a 401 indicates authentication. Call the endpoint by hand to distinguish those cases:
kubectl exec -n simstudio deploy/sim-app -- sh -c \
'curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $CRON_SECRET" \
http://localhost:3000/api/schedules/execute'Wrap it in sh -c with single quotes so $CRON_SECRET expands inside the pod — otherwise your local shell substitutes an empty value and you get a misleading 401.
401 means the secret does not match. 202 means the endpoint accepted the run; it does not tell you whether a schedule was actually due, so confirm in the Logs view.
Gmail, Drive, and Outlook triggers never fire
These are polling triggers, driven by the per-minute /api/webhooks/poll/* jobs. Check the scheduler is running them — docker compose -f docker-compose.prod.yml logs cron, or kubectl get cronjobs -n simstudio for a recent LAST SCHEDULE.
Microsoft Teams chat triggers are the different case: they use a Microsoft Graph subscription capped at about three days, renewed by the twice-daily renew-subscriptions job. If Teams triggers work for a couple of days and then stop, that job is not running. See Background Jobs.
Credentials unreadable after a restore
Integrations show as connected but fail, or provider keys error on decrypt. ENCRYPTION_KEY does not match the value in use when the backup was taken. Restore the original key. A different key cannot decrypt that stored data.
Windows-specific issues
These apply to running Sim from source for development, not to the Docker or Kubernetes deployments, which are unaffected by the host OS.
Turbopack errors on Windows: run the source development environment in WSL2.
wsl --installLine ending issues:
# Configure git to use LF
git config --global core.autocrlf input