Self-hosting Supabase hands you the whole stack, Postgres, GoTrue auth, the Kong API gateway, Storage, Realtime, and Studio, and every one of those is now your responsibility to secure. On Supabase's managed cloud, a chunk of this is handled for you. Self-hosted, supabase self-hosted security is entirely on whoever ran docker compose up.

That's not a warning to scare you off self-hosting, we run this stack too. It's a reason to treat the default Docker Compose setup as a starting point, not a finished deployment. This is the checklist we actually use before anything self-hosted touches real user data.

Quick answer

Self-hosted Supabase is only as secure as you make it: unique secrets in place of the .env.example defaults, Row Level Security enabled on every table, the service role key kept strictly server-side, Postgres and Studio never exposed directly to the internet, and no assumption that HIPAA or any compliance framework is handled for you. Details below.

Generate real secrets before launch

The single most common mistake in a self-hosted Supabase security best practices audit is finding placeholder values still in production: the .env.example JWT secret, a default Postgres password, or dashboard credentials nobody bothered to change. Every one of those is public, they're sitting in the open source repo for anyone to read.

Supabase ships a generate-keys.sh script that creates a genuinely random Postgres password and the supporting secrets, and a separate add-new-auth-keys.sh script for the newer asymmetric JWT signing keys. Run both before your first deploy, not after.

Worth knowing if you set this up more than a year or two ago: Supabase moved self-hosted auth toward an asymmetric key system, publishable and secret keys signed with ES256 instead of the older shared-secret HS256 JWTs used for the legacy anon and service role keys. The old keys still work for backward compatibility, but the new system means your API gateway verifies requests against a public key rather than every service needing a copy of the same shared secret. If you're setting up fresh, use the new key generation flow.

# Inside your self-hosted supabase repo checkout
cp .env.example .env

# Generates a random Postgres password and supporting secrets
./generate-keys.sh --update-env

# Generates the newer asymmetric JWT signing keys
sh utils/add-new-auth-keys.sh --update-env

The dashboard login (Studio, protected by HTTP basic auth in front of it) needs its own real password too, and Supabase enforces that it isn't purely numeric. A weak dashboard password behind a reverse proxy is still a weak dashboard password.

Lock down Row Level Security

Here's the part people misunderstand about the anon key: it being public isn't the vulnerability. Postgres tables with Row Level Security (RLS) turned off, reachable through that public key, are the vulnerability. RLS is what turns "anyone with the anon key can query this table" into "anyone with the anon key can query only the rows their policy allows."

  • Enable RLS on every table, no exceptions by default. A freshly created table in Postgres has RLS off, meaning the anon key can read and write it entirely until you turn RLS on and add at least one policy.
  • Write policies before you write app code that depends on the table. It's much easier to design access rules alongside a schema than to retrofit them once client code assumes open access.
  • Don't forget Storage buckets. Storage has its own RLS-style policies on the storage.objects table, a public bucket with no policy is exactly as exposed as a table with RLS off.
  • Test as the anon role, not just as yourself. It's easy to develop and test everything as the service role or a superuser and never notice a missing policy until a real anon-key request hits it in production.

Never expose the service role key

The service role key bypasses RLS entirely, by design, it's meant for trusted server-side code (edge functions, your backend, admin scripts) that needs full access regardless of policy. That's exactly why it can't leave the server. Ship it in a mobile app, a frontend bundle, or a client-exposed environment variable, and RLS stops mattering, whoever has that key already has the access RLS was supposed to gate.

A few concrete rules worth writing into a team's onboarding doc, not just this article:

  • Service role key lives in server environment variables only, never in anything that ships to a browser or app store.
  • Different environments (dev, staging, prod) get different service role keys, so a leaked dev key doesn't compromise production.
  • If a service role key or JWT secret leaks, rotate it immediately. Because both anon and service role keys are signed with the same JWT secret, rotating the secret invalidates every issued key and every active session at once, plan the maintenance window accordingly.

Restrict network access to Postgres and Studio

Docker Compose gives you internal networking between services essentially for free, use it. Postgres, GoTrue, and the internal Storage service don't need public ports at all, only the Kong API gateway (and, if you want dashboard access from outside the VPC, Studio) should be reachable from the internet.

Don't expose Postgres directly

Supabase's own docs carry an explicit warning here: exposing Postgres directly bypasses connection pooling (Supavisor) and puts your database straight on the network. If you need external database access, tunnel through SSH, restrict by IP allowlist at the firewall or security group level, or go through the pooler, never publish port 5432 to 0.0.0.0.

Put a real reverse proxy, Caddy or Nginx are the common choices, in front of both the API gateway and Studio for actual TLS termination in production rather than relying on the bundled defaults. And if Studio doesn't need to be reachable from outside your office or VPN, don't put it on the open internet at all, basic auth in front of an admin dashboard is a speed bump, not a wall.

Backups, encryption, and logging

Security isn't only about keeping people out, it's also about not losing the data or having no record of what happened when something goes wrong.

Area Risk if skipped What to actually do
Database backups A bad migration or deleted volume means total data loss Scheduled pg_dump exports or your cloud's managed snapshot feature, tested by actually restoring one
Encryption at rest A stolen or improperly decommissioned disk exposes raw data Encrypted EBS volumes (or your cloud provider's equivalent) under the Postgres data directory
Encryption in transit Credentials and query data readable on the wire TLS on every public-facing endpoint, API gateway and Studio included, no exceptions for "internal" traffic that isn't actually internal
Audit logging No way to reconstruct what happened after an incident Ship Postgres and GoTrue logs somewhere outside the container, container logs disappear with the container

Is self-hosted Supabase HIPAA compliant?

Not automatically, and this is worth being direct about since it's a genuinely common point of confusion. Supabase does have a HIPAA program, but it's tied to their managed cloud platform: you sign a Business Associate Agreement (BAA) with Supabase, pay for their HIPAA add-on, and their team runs continuous compliance checks against your project.

None of that applies when you self-host. There's no BAA to sign with Supabase, because Supabase isn't the one operating your infrastructure, you are. Self-hosted Supabase can be part of a HIPAA-compliant system, plenty of healthcare teams run it that way, but every safeguard has to come from your side: encryption at rest and in transit, strict RLS policies around PHI, access logging, a BAA with your cloud provider, and your own risk assessment process. Treat "we self-host Supabase" and "we're HIPAA compliant" as two separate claims that both need their own evidence.

Pre-launch security checklist

  • Ran generate-keys.sh and add-new-auth-keys.sh, no default secrets remain anywhere in the stack
  • Dashboard (Studio) has a strong, non-numeric password and sits behind a reverse proxy with real TLS
  • Row Level Security is enabled, with at least one policy, on every table reachable via the anon key
  • Storage buckets have explicit access policies, none left at open defaults
  • Service role key exists only in server-side environment variables, confirmed absent from any client bundle
  • Postgres port 5432 is not published to the public internet
  • Automated backups are running and a restore has actually been tested
  • Logs are shipped somewhere durable outside the containers themselves

If you're setting this up for the first time, our Supabase AWS developer guide and the GCP equivalent walk through the CloudFormation and deployment steps, this article is deliberately the security layer on top, not a repeat of the install. If you want the fastest path to a working deployment with the dashboard credentials and reverse proxy groundwork already handled, the Meetrix Supabase AMI and its GCP Marketplace listing start you from a more secure baseline than a bare docker-compose clone, though the checklist above still applies to what you build on top of it.

Frequently Asked Questions

Is self-hosted Supabase secure by default?

No. The Docker Compose defaults are meant to get you running, not to be production-safe. Placeholder JWT secrets, a default Postgres password, and no reverse proxy in front of Studio are all things you're expected to change yourself before going live.

Is it safe to expose the Supabase anon key in frontend code?

Yes, that's what it's designed for, but only if Row Level Security is enabled and enforced on every table it can reach. The anon key being public is fine. A table with RLS off and the anon key floating around is not, that's an open table to anyone.

What's the difference between the anon key and the service role key?

The anon key respects Row Level Security policies. The service role key bypasses RLS entirely and has full database access, it should never leave your server, never ship in a mobile app or frontend bundle, and never sit in client-side environment variables.

Is self-hosted Supabase HIPAA compliant?

Not automatically. Supabase's HIPAA program, with a signed BAA, applies to their managed cloud platform. Self-hosting means you're running your own infrastructure, so HIPAA compliance is entirely your responsibility: encryption, access controls, audit logging, and a BAA with whoever hosts your servers, not Supabase.

How do I secure a Supabase production deployment on Docker?

Generate unique secrets for every service instead of using the .env.example defaults, keep Postgres off the public internet, put a reverse proxy with real TLS in front of Studio and the API gateway, enable RLS on every table, and never expose the service role key client-side.

Should I rotate my Supabase JWT secret?

Periodically, yes, and immediately if you suspect it leaked. Rotating it invalidates every existing anon and service role key and every active user session, since they're all signed with that secret, so plan for a maintenance window and re-issue keys to every service that uses them.

Deploy a Pre-Configured Supabase Instance

Launch a self-hosted Supabase instance, our open source Firebase alternative, on AWS with a solid security baseline already in place.

Get Supabase on AWS Marketplace