Skip to main content
Work through every item before announcing your application publicly.

Security

1

Generate new JWT RSA key pair

Do not use the keys from local development. Generate a fresh pair specifically for production.
2

Change default admin credentials

Log in to the production admin account and change the password from Admin123! to something secure. Do not skip this — the default password is the same for every scaffolded project.
3

Verify ENVIRONMENT=Production

In production, this setting: disables Swagger (do not expose Swagger publicly), suppresses detailed error messages in API responses, causes missing Stripe configuration to throw on startup rather than warn.
4

Confirm Swagger is not accessible

Navigate to https://api.yourdomain.com/swagger — it should return 404, not the Swagger UI.
5

Verify HTTPS is configured

  • HTTP requests should redirect to HTTPS
  • SSL certificate is valid (not self-signed in production)
  • HSTS header is present: Strict-Transport-Security: max-age=31536000
6

Verify no secrets are in code or config files

Run this from the project root before deploying:
Any results indicate secrets are committed to git. Rotate those keys immediately.

Stripe

1

Switch to live Stripe keys

STRIPE_SECRET_KEY must start with sk_live_, not sk_test_. Check in your environment variables.
2

Webhook endpoint is registered in Stripe Dashboard

Dashboard → Developers → Webhooks. Verify an endpoint exists for your production API URL.
3

All five required events are selected

The endpoint must be subscribed to exactly these events:
  • checkout.session.completed
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_failed
  • invoice.payment_succeeded
4

Webhook signing secret is the live endpoint secret

STRIPE_WEBHOOK_SECRET must start with whsec_ and come from the live endpoint, not from the Stripe CLI. The CLI secret and the Dashboard endpoint secret are different values.
5

Complete a real test checkout

Use a real card (not a test card) to complete a Pro checkout. Verify the subscription appears in the Stripe Dashboard and the Billing page in your application shows the updated plan.

Email

1

Sending domain is verified in Resend

Resend Dashboard → Domains. Status must be Verified, not Pending.
2

EMAIL_FROM_ADDRESS uses your verified domain

Must be noreply@yourdomain.com or similar. Not onboarding@resend.dev.
3

Send a test email and check deliverability

Register a new account from the public-facing sign-up page. The verification email should arrive within 30 seconds. Check the spam folder — if it lands there, check your SPF/DKIM/DMARC records.

Database

1

Migrations applied to production database

The command should print No pending migrations. when complete.
2

Seed data applied

The admin user must exist. Run make seed or the equivalent command against the production database once.
3

Database backups are configured

If using a managed database (Railway, Render, RDS), enable automatic backups in the provider dashboard. If self-hosting PostgreSQL, configure pg_dump via cron.

OAuth

1

Production redirect URIs configured in Google Cloud Console

The authorized redirect URI must be https://api.yourdomain.com/api/v1/auth/google/callback. Not localhost.
2

Production callback URL configured in GitHub OAuth App

The authorization callback URL must be https://api.yourdomain.com/api/v1/auth/github/callback. Not localhost.

Frontend

1

VITE_API_URL points to production API URL

Must be https://api.yourdomain.com, not http://localhost:5000.
2

Frontend build uses production environment variables

If using Vite’s build command directly: VITE_API_URL=https://api.yourdomain.com npm run build. Verify the built JavaScript files do not contain localhost.

Notifications and Redis

1

Redis is running and accessible

The API must be able to connect to Redis. Verify: