How to secure your Supabase database: RLS, keys, and best practices
A practical guide to securing your Supabase database with Row-Level Security, API key management, and security best practices every team should follow.

Why Supabase Security Matters
Supabase gives you a powerful Postgres database with auto-generated APIs — but that power comes with responsibility. If your RLS policies aren't set up correctly, your entire database could be exposed to the public internet.
IBM reports that nearly 49% of data breaches are caused by human error. In Supabase, the most common mistake is leaving tables without RLS policies or exposing the service_role key in client code.
Source: [IBM — Database Security Guide](https://www.ibm.com/think/topics/database-security)
Row-Level Security (RLS): The Basics
RLS is PostgreSQL's built-in mechanism for controlling which rows a user can access. In Supabase, RLS is enabled by default on tables created with the dashboard, but you still need to write the policies.
Enable RLS on Every Table
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;If RLS is not enabled on a table in the public schema, anyone with your anon key can read and write all data in that table.
Write Policies
A policy defines who can do what. For example, let users read only their own rows:
CREATE POLICY "Users can read own data"
ON posts
FOR SELECT
USING (auth.uid() = user_id);And let users insert only their own rows:
CREATE POLICY "Users can insert own posts"
ON posts
FOR INSERT
WITH CHECK (auth.uid() = user_id);Source: [Supabase Docs — Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security)
- 1
Client sends the anon key
Every request is anonymous until Supabase resolves the session.
- 2
RLS policies evaluate
SQL rules decide which rows this user may touch.
- 3
Filtered rows return
The client only ever sees what a policy allows.
- 4
service_role bypasses everything
Server-side only. This key ignores RLS and returns all rows.
| anon | service_role | |
|---|---|---|
| Safe in client code | ||
| RLS policies enforced | ||
| Can read every row | ||
| Risk if leaked | Low | Critical |
RLS Best Practices
- 1Enable RLS from day one. Don't leave it for later. Every new table should have RLS enabled before any data is inserted.
- 1Keep policies simple. Start with basic checks (e.g.,
auth.uid() = user_id) and refine as needed. Complex policies are harder to debug and can hurt performance.
- 1Add indexes on RLS columns. If your policy filters on a column that isn't a primary key, add an index. Otherwise, every query does a full table scan.
- 1Test your policies. Use the Supabase SQL editor with different auth contexts to verify that users can only see what they should.
- 1Never expose service_role keys in client code. The
service_rolekey bypasses RLS entirely. It should only be used in server-side code (Edge Functions, backend services).
Source: [MakerKit — Supabase RLS Best Practices](https://makerkit.dev/blog/tutorials/supabase-rls-best-practices), [Supabase — RLS Performance and Best Practices](https://supabase.com/docs/guides/troubleshooting/rls-performance-and-best-practices-Z5Jjwv)
API Key Management
Supabase provides two keys:
anonkey: Safe to use in client-side code. Subject to RLS policies.service_rolekey: Bypasses RLS. NEVER expose this in client code. Use only in server-side environments.
If your service_role key leaks, an attacker can read, modify, or delete every row in your database — RLS won't protect you.
Tips:
Store the service_role key as an environment variable on your server, never in your frontend bundle. Rotate keys immediately if you suspect a leak (Supabase Dashboard > Settings > API).
Additional Security Measures
- 1Enable point-in-time recovery (PITR) on Pro plans for short-window rollback protection.
- 2Use database backups. RLS protects against unauthorized access, but it doesn't protect against bad migrations, accidental deletions, or ransomware. That's what backups are for.
- 3Set up CORS restrictions to limit which domains can access your API.
- 4Enable MFA on your Supabase dashboard account.
- 5Audit your policies regularly. As your schema evolves, old policies may grant unintended access.
Why Backups Are Still Essential
Even with perfect RLS policies, your database is vulnerable to:
- Ransomware attacks (rose 32% in 2025)
- Accidental
DELETEorDROPstatements - Bad migrations that corrupt data
- Hardware failures and cloud outages
Supakeep automates daily Supabase backups to your own Google Drive. Even if your database is perfectly secured, you still need a backup you control.
Read our backup best practices guide and learn how to restore from a SQL backup.
Related reading: Supabase backup best practices for small teams and the off-site restorable backup your SOC 2 auditor asks for.
Frequently asked questions
Yes, RLS is enabled by default on tables created through the Supabase dashboard. However, you still need to write policies — an RLS-enabled table with no policies blocks all access until you add them.
Yes. If your RLS policy filters on a column without an index, Postgres does a full table scan for every query. Add indexes on columns used in RLS policies that aren't primary keys.
Anyone with your service_role key can bypass RLS and access all data in your database. Treat it like a root password — never put it in client-side code.
Yes, Supabase encrypts data at rest on Pro plans and above. Free tier uses standard Postgres storage without additional encryption at rest.
Absolutely. RLS controls access — it doesn't protect against data loss. You still need backups for ransomware recovery, accidental deletions, and disaster recovery. See our [backup guide](https://supakeep.io/blog/supabase-backup-best-practices).