Troubleshooting
Local setup
Docker services fail to start
Check that Docker Desktop is running, then:
npm run docker:downnpm run docker:upIf ports 5432 or 6379 are already in use, stop whatever is holding them or change the ports in docker-compose.yml.
API fails to start — “missing required environment variable”
JWT_SECRET and ACTIVITY_HMAC_SECRET are required. Generate them:
echo "JWT_SECRET=$(openssl rand -base64 64)" >> apps/api/.envecho "ACTIVITY_HMAC_SECRET=$(openssl rand -base64 64)" >> apps/api/.envThen verify DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, REDIS_HOST, and REDIS_PORT are all set.
Migrations fail
Make sure the database container is healthy before running migrations:
npm run docker:up# wait a few seconds for Postgres to initialisenpm run db:migrateWeb app can’t reach the API
In local development the Vite proxy handles /api routing automatically — leave VITE_API_URL empty.
In a hosted split deployment, set both:
VITE_API_URL=https://api.yourdomain.com/api/v1VITE_WS_URL=wss://api.yourdomain.com/wsRebuild the web app after changing Vite env vars — they are baked in at build time.
SPA routes return 404 on reload
Your web host must rewrite all non-asset paths to index.html. A Vercel config is included at apps/web/vercel.json. For other hosts, configure equivalent rewrite rules.
Authentication
“Invalid or expired token” on every request
Your JWT_SECRET may have changed since the token was issued. Log out, log back in, and the new token will be signed with the current secret.
Emails not arriving (verification, reminders, password reset)
Check your SMTP configuration:
SMTP_HOST=SMTP_PORT=587SMTP_SECURE=falseSMTP_USER=SMTP_PASS=SMTP_FROM=Run with NODE_ENV=test locally — this activates a JSON email transport so you can see email payloads in the API logs without needing real SMTP.
2FA locked out
Use a recovery code from the set generated when 2FA was enabled. If you’ve lost both the authenticator and recovery codes, an admin can reset the account via:
POST /api/v1/admin/users/:userId/unlockProduction
Cookie / CORS errors in the browser
If the web app and API are on different origins:
- Set
FRONTEND_URLto the exact web origin (including protocol, no trailing slash) - Set
CORS_ORIGINSto the same value - Enable TLS on both origins —
Securecookies require HTTPS
WebSocket connection fails
Ensure your reverse proxy forwards WebSocket upgrade requests for /ws. In Nginx:
location /ws { proxy_pass http://api:3001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";}Rate limiting not working across multiple API instances
The default rate limiter uses in-process memory. For multi-instance deployments, add a Redis-backed rate limiter or use an edge/WAF rate limiter in front of your API.
Still stuck?
- Open an issue: github.com/HandoverKey/HandoverKey/issues
- Email: info@handoverkey.app