Troubleshooting
Start with the service logs; most problems name themselves there.
docker compose ps # what's up, what's healthydocker compose logs --tail 100 backend worker # API and test runsdocker compose logs --tail 100 playwright-mcp # the Chromium browser serviceA run that ends with status error didn’t really execute: something in the setup stopped it
(the browser service, the AI provider, a quota). Its message starts with what went wrong and ends
with Details: and the raw error. A failed run is a real test failure, explained in plain
English.
Install and start-up
Section titled “Install and start-up”The app isn’t on port 80
Section titled “The app isn’t on port 80”That’s expected: nginx is published on http://localhost:8080, because port 80 is often taken
(on Windows, commonly by IIS). If 8080 is taken too, publish nginx on another port in a
docker-compose.override.yml:
services: nginx: ports: !override - "8081:80"The frontend uses the relative path /api, so nothing else changes. Set APP_BASE_URL to the
new address so links in emails and alerts are right. Other host ports: 127.0.0.1:3000 (frontend),
127.0.0.1:8000 (backend), 127.0.0.1:5433 (Postgres, change with POSTGRES_HOST_PORT),
127.0.0.1:6379 (Redis), 127.0.0.1:8931 (Chromium).
Compose rejects the file
Section titled “Compose rejects the file”Messages about env_file (for example that an entry must be a string) or about !reset and
!override tags mean Compose is too old. The compose file marks .env as optional, which needs
Compose v2.24 or newer (!override needs v2.24.4). Check with docker compose version and
update Docker Desktop or the Compose plugin.
The first browser runs fail right after starting
Section titled “The first browser runs fail right after starting”The browser services install @playwright/mcp and download their browser on every start, which
takes a few minutes the first time. Workers don’t wait for them, so until they’re listening,
browser runs end in error with “Could not reach or drive the browser automation server”. Wait
until docker compose ps shows the browser service as (healthy) instead of
(health: starting), then re-run:
docker compose psdocker compose logs -f playwright-mcp # or playwright-mcp-firefox, playwright-mcp-webkitFirefox and WebKit take longer than Chromium. A service that stays (unhealthy) failed to
install; its log says why (often no internet access from the container).
The backend won’t start or stays unhealthy
Section titled “The backend won’t start or stays unhealthy”The workers and the scheduler wait for a healthy backend, so nothing runs until this is fixed.
Check docker compose logs backend:
Refusing to start with PLAINWRIGHT_ENV=production: SECRET_KEY is …: production mode found an empty, publicly known or too-shortSECRET_KEY. If the install is new, set a random key of at least 32 characters. If it already holds data encrypted with the current key, rotate the key instead: the rotation command runs in a one-off container, so the backend doesn’t need to start first.- A settings error such as “Input should be a valid boolean” or “…valid integer”: a number
or boolean in
.envis set to an empty value. Remove the line or give it a value. Can't load plugin: sqlalchemy.dialects:postgres:DATABASE_URLstarts withpostgres://. Usepostgresql://.- A migration error: the migration transaction rolled back, so the database is unchanged. Fix
the cause in the message, then
docker compose up -d backend. See Upgrades, migrations and backups. module 'bcrypt' has no attribute '__about__':bcryptwas upgraded past the pinned4.0.1.passlib 1.7.4doesn’t work with bcrypt 4.1 or newer; keep the pin inbackend/requirements.txtand rebuild.
nginx answers 502 Bad Gateway
Section titled “nginx answers 502 Bad Gateway”The backend isn’t answering yet (it migrates the database on start) or has crashed. Check
docker compose ps and the backend log. The bundled nginx re-resolves service addresses every
10 seconds, so a rebuilt backend is picked up without restarting nginx.
A changed .env has no effect
Section titled “A changed .env has no effect”docker compose restart restarts containers with their old environment. Run
docker compose up -d, which recreates the containers whose configuration changed. On hosted
platforms, redeploy every service that reads the variable; Configuration
lists which ones.
A warning about SECRET_KEY
Section titled “A warning about SECRET_KEY”In development mode, the backend logs a CRITICAL line starting SECURITY: SECRET_KEY is…,
and project owners and admins see the same warning as a banner in the app. The key is empty, a
publicly known default, or shorter than 32 characters, so anyone who knows it can forge
sign-ins and decrypt stored credentials from a database copy.
Don’t just replace the value: anything already encrypted would become unreadable. Follow
Rotate SECRET_KEY, which
re-encrypts stored credentials under the new key, then set PLAINWRIGHT_ENV=production so a weak
key can’t come back unnoticed.
AI providers
Section titled “AI providers”Runs fail with 401 Invalid API Key
Section titled “Runs fail with 401 Invalid API Key”When Test connection passes but runs fail with “The AI provider rejected the API key”, the
backend and the worker have different SECRET_KEY values. The backend encrypted the key with
its value; the worker can’t decrypt it and sends the ciphertext as the API key. Give every
service the same SECRET_KEY and recreate them (docker compose up -d). On Docker Compose they
share .env, so look for a SECRET_KEY set differently on one service, or a worker started from
an older environment. Right after a key rotation,
it means one service still has the old key.
If SECRET_KEY was changed by hand instead of rotated, stored keys were encrypted with a key that’s
gone. Re-enter each provider key in AI providers (and the vault secrets, saved logins and
integration credentials in each project).
If Test connection fails too, the key itself is wrong, expired, or for a different provider. Re-enter it in AI providers.
If neither the project nor its owner has a provider and ANTHROPIC_API_KEY is empty, runs fail
with an authentication error as well. Add a provider, or set the server-wide key.
“The selected AI model does not support tool/function calling”
Section titled ““The selected AI model does not support tool/function calling””Plainwright drives everything through tool calls, so every model must support them. Test
connection checks this with a no-op tool. Pick a tool-capable model, such as the current
Claude, GPT and Gemini models, or llama3.1, qwen2.5 or mistral-nemo on Ollama.
“The model produced an invalid tool call”
Section titled ““The model produced an invalid tool call””Some open models occasionally emit a malformed tool call (Llama and Qwen on Groq, for example). It’s intermittent: re-run, turn on retries, or use a more capable model.
“The AI provider rate-limited the request”
Section titled ““The AI provider rate-limited the request””The provider answered 429. Wait and re-run, raise your plan’s limits, or run fewer tests at once:
every worker makes its own AI calls. See Scaling workers.
“Could not parse AI result”
Section titled ““Could not parse AI result””The model’s final answer wasn’t the JSON verdict Plainwright asks for. Reasoning models (Qwen3,
DeepSeek-R1 and similar) emit <think> blocks, which Plainwright strips first, but a model that
spends its whole reply budget thinking leaves nothing to parse. Re-run, or choose a model that
follows instructions more reliably. Very small local models are the usual cause.
Ollama models don’t show up or can’t be reached
Section titled “Ollama models don’t show up or can’t be reached”Ollama runs on the host, not in Docker. The backend and worker reach it at
http://host.docker.internal:11434:
- Linux: Ollama listens on
127.0.0.1by default, which containers can’t reach. Start it withOLLAMA_HOST=0.0.0.0(and allow port 11434 from the Docker bridge in your firewall). - No models listed: the list comes live from the Ollama server. Pull one, from the app or
with
ollama pull llama3.1. - Runs error on a model: the model must support tool calling.
Browser runs
Section titled “Browser runs”“Could not reach or drive the browser automation server”
Section titled ““Could not reach or drive the browser automation server””The worker couldn’t use a browser service:
- It’s still starting (see above), or it
crashed: check
docker compose logs playwright-mcp(or-firefox,-webkitfor those browsers). - Its command was edited and
--allowed-hosts '*'is no longer the last flag. The option takes every value after it, so flags placed after it are swallowed. - On a hosted platform,
PLAYWRIGHT_MCP_URLdoesn’t point at the browser service’s private address. Only the host and port matter; the client opens/sseon them.
The browser crashes or pages time out
Section titled “The browser crashes or pages time out”Browsers need memory: about 0.5 GB per concurrent browser run. Give the browser service more
memory and CPU, keep shm_size at 2 GB or more, or run fewer workers. See
Scaling workers.
“Max tool calls reached”
Section titled ““Max tool calls reached””The agent used all 30 of its steps without finishing. Split the test into smaller ones or make the steps more specific. See Writing good tests.
Runs stay “pending”
Section titled “Runs stay “pending””No worker is taking jobs. Check that workers are running (docker compose ps worker; they only
start once the backend is healthy) and can reach Redis (docker compose logs worker). If triggering a run answers “The job queue is unavailable —
try again shortly” (HTTP 503), the backend can’t reach Redis.
Runs stay “running”, then end in error
Section titled “Runs stay “running”, then end in error”“Run did not complete — its worker stopped or it exceeded the time limit (orphaned job reaped)”
means the worker died or was stopped mid-run (a deploy, a crash, running out of memory), or the
run took longer than 300 seconds per trial. The scheduler does this clean-up, so if it isn’t
running, such runs stay running forever. Make sure exactly one scheduler runs.
A test can’t find an uploaded file
Section titled “A test can’t find an uploaded file”Project files are stored in ./test-files/<project_id>/ and must be visible to the browser
service at /test-files. The compose file mounts the folder into the backend, the workers and all
three browser services; keep those mounts if you customise it. Uploads are limited to 25 MB per
file. Platforms without a shared disk (Render, Railway, Fly.io) don’t support them.
Stale or ambiguous elements in the steps
Section titled “Stale or ambiguous elements in the steps”“Element [ref=…] not found” and “Ambiguous element found” in a run’s steps aren’t failures by themselves. Plainwright recovers automatically by taking a fresh snapshot of the page, or by asking the agent for a more specific target. They only matter if the run then fails.
The live run view doesn’t update
Section titled “The live run view doesn’t update”Live steps stream over a long-lived response, and a proxy that buffers responses holds them back.
In your own reverse proxy, turn buffering off for /api/ (proxy_buffering off; in nginx,
flush_interval -1 in Caddy) and allow long reads. See Deploy to a VPS.
Screenshots are missing or broken
Section titled “Screenshots are missing or broken”- With
STORAGE_BACKEND=s3, look for “…not configured (S3_BUCKET empty) — using local storage” in the backend and worker logs; then artifacts went to the local disk instead. Both services need theS3_*settings. - With a private bucket, images redirect to presigned bucket URLs, so people’s browsers must be able to reach the bucket’s endpoint (a MinIO address that only resolves inside Docker won’t work).
- Behind your own proxy,
/screenshots/must go to the backend as well as/api/.
Requests to URLs under test
Section titled “Requests to URLs under test”The same guard covers API tests, eval targets, MCP servers, A2A agents, webhooks and the URLs the AI agent opens in the browser. In a browser run, a refused URL shows up in the steps as a tool error. See Security model for the full list.
“Requests to internal service ‘…’ are not allowed”
Section titled ““Requests to internal service ‘…’ are not allowed””The URL names one of the platform’s own services (postgres, redis, backend, worker,
scheduler, frontend, nginx, playwright-mcp*) or one of their container names. Point the
test at the service you actually want to test.
“Requests to a loopback address are not allowed”
Section titled ““Requests to a loopback address are not allowed””localhost, 127.0.0.1 and ::1 are refused. They’d point at the worker’s or the browser
container’s own address anyway, not at your machine. To test an app running on the Docker host,
use http://host.docker.internal:<port>.
“Requests to the Docker host on the platform’s own port … are not allowed”
Section titled ““Requests to the Docker host on the platform’s own port … are not allowed””host.docker.internal is allowed, except on the ports Plainwright itself publishes: 3000,
5432, 5433, 6379, 8000, 8080 and 8931. If the app you want to test runs on one of
them (a dev server on 3000, say), move it to another port.
“Requests to the platform’s internal services are not allowed”
Section titled ““Requests to the platform’s internal services are not allowed””The host name resolves to the address of one of the platform’s services, so it’s refused like the service name itself.
“Requests to cloud metadata endpoints are not allowed”
Section titled ““Requests to cloud metadata endpoints are not allowed””The URL is a metadata or link-local address, or a host name that resolves to one.
“Only http(s) URLs are allowed”
Section titled ““Only http(s) URLs are allowed””The URL has no scheme or uses another one. Write it in full, including https://.
Testing an internal service on purpose
Section titled “Testing an internal service on purpose”On a trusted single-user install, ALLOW_INTERNAL_TARGETS=true turns all of the checks above off
except the scheme check. Never set it on an install other people use.
Sign-in, email and billing
Section titled “Sign-in, email and billing”- No Google or GitHub button, a redirect mismatch, an expired sign-in link, or no reset or confirmation emails: see the table in Sign-in and email.
- An invited person doesn’t see the project: their address isn’t confirmed yet. See the FAQ.
- “Too many attempts — wait a few minutes and try again.” for everyone at once: behind the bundled nginx the backend sees one client address for all users, so the per-address sign-in limit can trip install-wide. Wait 15 minutes, and rate-limit at your edge proxy instead; see Rate limits.
- Everyone was signed out:
SECRET_KEYchanged (for example after a rotation). One person was signed out on every device: a password change or reset, a sign-out of all sessions, or removal from their last project revoked their tokens. Sign in again. - Runs end with “Monthly run limit reached” or “Monthly AI token limit reached”:
BILLING_ENABLEDistrueon the workers. Self-hosted installs should leave itfalse. See Billing.
Still stuck?
Section titled “Still stuck?”Search the issue tracker, or open an issue with:
- what you did, what you expected, and what happened;
docker compose psand the relevant part ofdocker compose logs;- the version (
git rev-parse --short HEAD) and how you deploy (Compose, Render, …).
Security problems go through private reporting, not public issues.