Skip to main content

Troubleshooting

Common issues and fixes for SaaS Starter setup and deployment.

Troubleshooting

Quick fixes for the most common SaaS Starter issues.

First step: Run pnpm doctor from the monorepo root. It checks Node version, pnpm, PostgreSQL, Redis, and environment variables.


Database connection failed

Symptom: ECONNREFUSED on port 5432 or role "saas_starter" does not exist.

Fix:

  1. Ensure PostgreSQL is running:
    # macOS
    brew services start postgresql@16
    
    # Linux
    sudo systemctl start postgresql
    
  2. Create the database and user:
    psql -U postgres -c "CREATE DATABASE saas_starter;"
    psql -U postgres -c "CREATE USER saas_starter WITH PASSWORD 'saas_starter_local';"
    psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE saas_starter TO saas_starter;"
    
  3. Verify DATABASE_URL in your .env matches the connection string.
  4. Test the connection:
    PGPASSWORD=saas_starter_local psql -U saas_starter -h localhost -d saas_starter -c "SELECT 1"
    

Redis connection failed

Symptom: Worker jobs not running, ECONNREFUSED on port 6379.

Fix:

  1. Ensure Redis is running:
    # macOS
    brew services start redis
    
    # Linux
    sudo systemctl start redis-server
    
  2. Test the connection:
    redis-cli ping
    # Expected: PONG
    
  3. Check REDIS_URL and REDIS_QUEUE_URL in your .env.

Build fails with Out of Memory

Symptom: FATAL ERROR: ... heap out of memory during pnpm install or Docker build step pnpm generate:active-modules.

Cause (Docker): On hosts under ~6GB RAM, BuildKit runs Node with a small heap. Invoking pnpm generate:active-modules triggers a full dependency sync and can OOM. SaaS Starter setup auto-enables a low-memory path that builds Web and API on the host volume instead.

Secrets in setup output: The configuration summary never prints API keys or passwords. If you see a key appended to a line like AI: groq (key set)gsk_..., upgrade to the latest setup scripts (a bash ${VAR:-} bug caused that leak in older versions). Pipe setup to a log file only when needed — one-time admin passwords are shown on the interactive terminal (stderr), not in stdout logs.

Fix:

  1. Re-run setup with low-memory profile (recommended on 2–4GB VPS):
    bash setup.sh --low-memory
    
    Or opt in to script-managed swap:
    NOVASAAS_CREATE_SWAP=1 bash setup.sh
    
  2. If Web built but API/worker failed, recover without full setup:
    bash scripts/recover-docker-api-low-mem.sh
    
  3. For PM2 / host builds, use the low-memory web path:
    pnpm run build:web:low-mem
    
  4. Manual swap (4GB recommended on 4GB VPS):
    sudo fallocate -l 4G /swapfile
    sudo chmod 600 /swapfile
    sudo mkswap /swapfile
    sudo swapon /swapfile
    

CSS/JS missing after PM2 restart

Symptom: App loads but looks broken, no styles or interactive elements.

Fix:

This is a known Next.js standalone issue — static assets aren't copied automatically.

# Use the PM2 web restart script — it copies static assets automatically
bash scripts/pm2-start-web.sh

# Or manually (if you need to restart without a full rebuild):
cp -rf apps/web/.next/static apps/web/.next/standalone/.next/static
cp -rf apps/web/public apps/web/.next/standalone/public
pm2 restart app-web

See PM2 Static Assets for the full explanation.


Migrations fail on first run

Symptom: relation "users" does not exist or similar schema errors.

Fix:

  1. Ensure database exists and is empty:
    psql -U postgres -c "DROP DATABASE IF EXISTS saas_starter;"
    psql -U postgres -c "CREATE DATABASE saas_starter;"
    
  2. Run migrations:
    pnpm migrate
    
  3. If using Docker:
    docker compose exec api pnpm migrate
    

2FA not working

Symptom: Can't scan QR code or TOTP codes are rejected.

Fix:

  1. Verify APP_URL matches your browser URL (including protocol and port).
  2. Ensure BETTER_AUTH_URL is set correctly in your .env.
  3. Clear browser cookies and try again.
  4. Check system clock — TOTP is time-sensitive.

Magic link email not received

Symptom: Email doesn't arrive, or link says "invalid token".

Fix:

  1. Check EMAIL_FROM is a valid email address.
  2. Verify email provider credentials (SMTP, Resend, SendGrid).
  3. Check spam/junk folder.
  4. For development, use mailto: link output in console by setting EMAIL_PROVIDER=log (if available) or check server logs for email content.
  5. Verify APP_URL matches the link in the email.

Webhook verification failed

Symptom: Stripe/Paddle/Lemon Squeezy webhooks rejected with "signature mismatch".

Fix:

  1. Ensure STRIPE_WEBHOOK_SECRET (or equivalent) is set correctly in .env.
  2. For local testing, use Stripe CLI:
    stripe listen --forward-to localhost:8000/webhooks/stripe
    
  3. Verify the webhook secret matches exactly (no extra whitespace or quotes).

Admin dashboard shows no data

Symptom: Charts empty, user list shows "No data yet".

Fix:

  1. Verify admin user exists:
    # In monorepo root
    pnpm exec tsx scripts/seed-initial-config.ts
    
  2. Or create admin via environment variables:
    ADMIN_EMAIL=your@email.com
    ADMIN_PASSWORD=yourpassword
    
  3. Restart the API server after setting admin variables.

Next.js App Router 404 on refresh

Symptom: Direct navigation to /dashboard or /admin/* returns 404.

Fix:

This usually means the Next.js standalone server is routing incorrectly.

  1. Verify output: 'standalone' is set in apps/web/next.config.ts.
  2. Ensure PM2 is running the correct entry point:
    pm2 logs app-web --lines 20
    
  3. Check that reverse proxy (Caddy, Nginx) is configured to pass all routes to Next.js.

See Production Deployment for proxy configuration.


Health check failing

Symptom: curl http://localhost:8000/health returns non-200.

Fix:

# Check API logs
pm2 logs app-api --lines 50

# Common causes:
# - Missing AUTH_SECRET or ENCRYPTION_KEY
# - DATABASE_URL not set or invalid
# - PostgreSQL/Redis not running

Getting more help

If pnpm doctor passes and you're still stuck:

  1. Run pnpm doctor:ops for infrastructure diagnostics.
  2. Check the environment matrix for correct variable names.
  3. Email support@example.com with:
    • Node and pnpm versions (node -v, pnpm -v)
    • OS and whether using Docker or native
    • Last 30 lines of the failing command
    • Output of curl http://localhost:8000/health
    • Output of pnpm doctor