# Production checklist

> What to check before launching an app on SupaHost: RLS on every exposed table, key handling, Auth redirect URLs and settings, function auth, tested backups, alerts and capacity.

Source: https://supahost.app/docs/security/production-checklist · Updated 2026-11-11

Work through this list before you send real users to a SupaHost project, and again after big changes. Most items take a minute. The ones in the first two sections protect your data; the rest protect your uptime and your ability to recover.

## Data access

**1. RLS is enabled on every table in an exposed schema.** The Data API exposes `public`, `storage`, `graphql_public` and `pgmq_public`, and you can't change that list on SupaHost. Any table you create in `public` is reachable with the anon key unless RLS stops it. Find the gaps:

```sql
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity;
```

**2. Every RLS-enabled table has policies,** or you've decided on purpose that nobody but `service_role` should reach it. A table with RLS and no policies returns empty results, which is safe but often a bug:

```sql
select t.tablename
from pg_tables t
where t.schemaname = 'public'
  and t.rowsecurity
  and not exists (
    select 1 from pg_policies p
    where p.schemaname = t.schemaname and p.tablename = t.tablename
  );
```

An AI agent connected over MCP can run the same checks with `get_advisors`, which flags tables with RLS off, tables with no policies, and functions with a mutable `search_path`.

**3. Policies are narrow.** Read each `using (true)` and ask whether every visitor should really see those rows. Prefer `to authenticated using ((select auth.uid()) = user_id)` for user-owned data. See [Row Level Security](/docs/database/row-level-security).

**4. Views and functions don't leak around RLS.** Views run with their owner's rights unless created with `security_invoker = true`, and `security definer` functions bypass RLS. Pin `search_path` on functions:

```sql
alter view public.order_totals set (security_invoker = true);
alter function public.close_booking(uuid) set search_path = '';
```

See [security definer functions and RLS](/kb/security-definer-functions).

**5. Storage buckets have the right visibility and policies.** Public buckets serve files to anyone with the URL. Private buckets need policies on `storage.objects`. See [Storage access control](/docs/storage/access-control).

**6. Queue functions aren't open by accident.** If you call `pgmq_public` from clients, you've added RLS and grants deliberately. See [Queues with pgmq](/docs/cron-queues/queues).

## Keys and secrets

**7. The `service_role` key is only on servers.** Search your client bundles for it after a build:

```bash
npm run build
# Prints any built file that contains the key. No output means none do.
# Check the variable is set first, or grep would match every file.
test -n "$SUPABASE_SERVICE_ROLE_KEY" && grep -rlF "$SUPABASE_SERVICE_ROLE_KEY" .next/static dist build 2>/dev/null
```

**8. No keys or tokens are committed to git.** `.env` files are in `.gitignore`. Function secrets are on the Functions tab, not in source.

**9. MCP access tokens are scoped.** Use project-scoped, read-only tokens for anything touching production, one per person and tool. Revoke ones that aren't used. See [Access tokens](/docs/platform/access-tokens).

More in [Keeping keys safe](/docs/security/api-key-safety).

## Auth

**10. Site URL is your real app URL.** Left unset, it defaults to `http://localhost:3000`, and confirmation emails will point there. Set it on **Auth → Site URL**.

**11. Redirect URLs are exact.** List your production URLs. Keep wildcards narrow (`https://*-acme.vercel.app/**` rather than `https://*.vercel.app/**`, which matches anyone's Vercel deployment). See [Site URL and redirect URLs](/docs/auth/redirect-urls).

**12. Sign-in settings match your app.** Review **Auth → Sign-in & security**: leave email confirmation on (don't tick "Skip email confirmation" in production), set a minimum password length of at least 8, turn on "Disable new sign-ups" for invite-only apps, and leave anonymous sign-ins off unless you use them.

**13. OAuth apps use the right callback.** Each provider's console lists `https://<your project or custom domain>/auth/v1/callback`. If you added a custom domain, update it there too. See [Custom domains](/docs/platform/custom-domains).

**14. Auth emails read well.** Set the App name and check each template with "Preview saved version". See [Auth emails and templates](/docs/auth/email-templates).

## Edge Functions

**15. "Require JWT" is on** for every function that isn't a public webhook. Remember it accepts the anon key too, so functions acting for a user must check the caller with `supabase.auth.getUser(token)`. See [Authentication and JWT verification](/docs/functions/auth).

**16. Webhooks verify signatures.** Functions with "Require JWT" off, such as a Stripe webhook, must check the provider's signature. See [Example: Stripe webhooks](/docs/functions/stripe-webhooks).

**17. CORS allows only your origins** for browser-called functions. See [CORS for browser calls](/docs/functions/cors).

## Backups and recovery

**18. You've tested a restore.** Download a pg_dump and load it into a local Supabase, or restore a non-production project, and time it. See [Restoring a project](/docs/platform/restore).

**19. Storage files are exported on a schedule.** They aren't in the daily backups. Use **Package files** on the Backups tab. See [Backups](/docs/platform/backups).

**20. You know your recovery window.** 7 days of daily backups on Builder; 7-day PITR on Pro; 14-day on Business.

## Operations

**21. Alerts reach someone.** Downtime and usage alerts are on, and **Also send alerts to** includes a shared inbox. See [Monitoring, health checks and alerts](/docs/platform/monitoring).

**22. Updates are planned.** Turn on automatic upgrades with a maintenance window (03:00-05:00 UTC) that suits your traffic, or apply releases yourself.

**23. Connections are budgeted.** 400 connections are shared by every project on the instance, with no pooler. Keep ORM pools small and close connections in scripts. See [Connections and timeouts](/docs/database/connections-and-timeouts).

**24. Hot queries are indexed.** Check slow queries with `EXPLAIN ANALYZE` and add indexes for columns used in RLS policies. See [RLS performance best practices](/kb/rls-performance).

**25. Capacity fits.** Compare your expected busiest hour with the guides on [Plans and limits](/docs/platform/plans).

## Accept the platform's limits

Last, confirm SupaHost's current state is acceptable for this app: public beta, no SLA, no SOC 2, ISO 27001 or HIPAA, single VM per instance, and no key rotation. See [Security at SupaHost](/docs/security/overview).

## Related

- [Shared responsibility](/docs/security/shared-responsibility)
- [Securing your API](/docs/api/securing-your-api)
- [Row Level Security, simplified](/kb/rls-simplified)
