Cause: web/.env.frontend was not downloaded or was saved in the wrong directory.
Fix: From /opt/nusaas, run mkdir -p web && curl -fsSL https://get.nusaas.com/.env.frontend -o web/.env.frontend, edit it, then run docker compose config --quiet.
Cause: The frontend origin does not match CORS_ALLOWED_ORIGIN_PATTERNS, or the regex was left unquoted and parsed as a comment.
Fix: Use a quoted exact frontend-origin regex such as '#^https://app\.yourdomain\.com$#', then recreate the backend services with docker compose up -d --force-recreate api worker cron ws so they load the changed environment.
Cause: Key mismatch, or Nginx WebSocket upgrade headers are missing.
Fix: Verify VITE_REVERB_APP_KEY matches backend REVERB_APP_KEY, and that the public frontend host/port/scheme are api.yourdomain.com, 443, and wss. Ensure Nginx includes the Upgrade and Connection headers.