Troubleshooting¶
Who this is for: hosters (sysadmins).
This page is for admins operating the stack. Start from the symptom and work down: the capture page's own error or status message, then the service state, then the logs. If a term here is unfamiliar, check the Glossary. Both supported ways to run the stack are covered: Docker Compose and Co-op Cloud.
The two commands you will reach for most, for the Docker Compose path:
This lists every service and whether it is healthy.
docker compose --env-file .env.production \
-f docker-compose.yml -f docker-compose.prod.yml ps
This shows the last 100 log lines for all services.
docker compose --env-file .env.production \
-f docker-compose.yml -f docker-compose.prod.yml logs --tail=100
On Co-op Cloud, use abra app <domain> logs for logs; the caption-ninja
recipe documents its status and restart commands.
Symptom, cause and fix¶
| Symptom | Likely cause | Fix |
|---|---|---|
Speech service returns 400 Bad Request |
The whisper-host middleware is missing from the caption-local router |
Restore the middleware that rewrites the backend Host header to 127.0.0.1:8765. Removing it makes the service reject public requests. |
Speech service returns 401 Unauthorized |
No service access token was sent | Send Authorization: Bearer <CAPTION_API_KEY>. If you do not have it, ask the service operator; it is not an OpenAI, Google or Gemini key. The browser forgets it on reload. |
| Relay logs "Relay startup failed" | The relay private JSON is missing, or is not readable by uid/gid 1000 |
Regenerate it, then apply chown 1000:1000 and chmod 600. On Co-op Cloud, re-run the recipe so it recreates the file. |
Overlay shows nothing; capture relay stuck reconnecting; publisher gets HTTP 403 |
The capture page's origin is missing from the relay's origins list |
Add the exact origin (scheme included, no trailing slash) and recreate the relay. The capture page and the overlays are usually different origins. |
| Let's Encrypt rate-limited | Too many certificate requests | Delete the letsencrypt volume and retry after an hour. |
| Build fails: no submodule content | Submodules were never initialised | git submodule update --init --recursive. |
Ports 80/443 already in use (production) |
Something else holds the port | Free the ports or change the mappings in docker-compose.yml. |
| Basic Auth prompt never appears | The auth overlay was not applied, or the htpasswd file is missing | Add -f docker-compose.auth.yml and generate the htpasswd file. |
| Page unavailable during startup | The model is still downloading or warming up | Wait and watch the logs for a real error before restarting. A first download can take several minutes. |
| Offline startup fails | The selected model was never cached | Cache it while online first: run once while online so the model volume is populated, then enable offline mode. |
| Microphone is unavailable | Browser permission missing, wrong device, or an insecure context | Allow the microphone and select the right device. Open the page over localhost or HTTPS; a plain-HTTP remote IP is not a secure microphone context. |
| Buffer keeps growing, or capture stopped | The host cannot keep up with the streams or model | Reduce active streams or translation work and competing CPU load. Do not enlarge the buffer to hide a machine that cannot keep up; retry retained audio once fixed. |
| Relay is disconnected | Connectivity issue, or the wrong source room | Local captioning still works. Check connectivity and the editor's source room; the relay queue is bounded, so watch its dropped count. External delivery is not guaranteed. |
| Server restarted on its own | A worker exceeded the 90-second watchdog deadline | Wait for readiness and retry pending browser audio. The server's retry cache does not survive a restart. |
CUDA is unavailable |
Driver or runtime not present | Run nvidia-smi, then test real inference. GPU acceleration intentionally fails when CUDA is requested but unavailable; run the CPU setup instead if needed. |
| Stream capacity reached | The session limit was hit | Stop and drain an unused capture page. Abandoned idle sessions expire after 120 seconds. Each producer needs a different stream ID. |
Check service health directly¶
When the stack looks unhealthy, query the speech service's own health endpoint. It reports the model, the actual device, readiness, workers, running and queued requests, and failures.
curl -H "Authorization: Bearer <CAPTION_API_KEY>" http://127.0.0.1:8765/health
Under Docker the port is bound to loopback, so run this on the host itself. On
Co-op Cloud, check the app's status through abra instead of calling the port
directly.
Fix a relay that rejects your origin¶
The relay refuses any WebSocket upgrade whose browser origin is not listed. The capture page and the overlays/editor are different origins, so list every one. After editing the private configuration, recreate the relay so it reloads the file:
docker compose --env-file .env.production \
-f docker-compose.yml -f docker-compose.prod.yml up -d --force-recreate captionninja-relay
On Co-op Cloud, restart the app through the recipe so it reloads the configuration. The relay settings are described in Relay and rooms and Configuration.
Recover from an unexpected restart¶
The runtime restarts a worker that exceeded its 90-second watchdog deadline, and
restart: unless-stopped brings services back; on Co-op Cloud the recipe manages
the restart policy. After a restart, wait for readiness and retry pending browser
audio; the in-memory retry cache is gone. If restarts are frequent, reduce the
model cost or the stream count rather than raising buffers.
Reporting a reproducible issue¶
Include your OS, release, install method, model, worker/thread/beam settings,
number of streams, chosen mode, the exact error, and whether one stream also
fails. Attach sanitised /health output and relevant logs. Share audio only if
you have permission to publish it, and never include credentials, private room
names or confidential transcripts.