Running Supabase on your own infrastructure gives you complete control over your data—but that control comes with responsibility. Unlike Supabase Cloud, where the team handles security patches, firewall rules, and intrusion detection, self-hosted deployments put you in charge of keeping attackers out.
This guide covers the essential security hardening steps for production self-hosted Supabase deployments, from basic firewall configuration to advanced attack prevention with CrowdSec.
Why Security Hardening Matters for Self-Hosted Supabase
When you expose a Supabase instance to the internet, you're running multiple attack surfaces: a PostgreSQL database, Kong API gateway, GoTrue authentication server, and the Studio dashboard. Each component needs proper configuration to prevent unauthorized access.
The self-hosting community has raised security as a top concern. In GitHub Discussions, users have noted that while Supabase provides powerful features out of the box, the default Docker Compose configuration prioritizes ease of setup over production security. The .env.example file includes placeholder secrets that should never be used in production.
Without hardening, common attack vectors include:
- Brute-force attacks against authentication endpoints
- SQL injection through improperly configured RLS policies
- Credential theft from unencrypted connections
- Unauthorized database access via exposed ports
- API abuse from missing rate limits
Step 1: Replace All Default Secrets
The most critical first step is replacing every default secret in your environment configuration. The official Supabase self-hosting docs are explicit: never start your deployment using example placeholder passwords.
Generate new values for these critical secrets:
# Generate a secure JWT secret (at least 32 characters) openssl rand -base64 32 # Generate new API keys using the JWT secret # ANON_KEY - limited permissions for frontend use # SERVICE_ROLE_KEY - full database access, server-side only
Key secrets to configure:
| Secret | Purpose | Risk if Exposed |
|---|---|---|
JWT_SECRET | Signs and verifies all JWTs | Complete auth bypass |
ANON_KEY | Client-side API access | Limited, but enables enumeration |
SERVICE_ROLE_KEY | Full database access | Complete data breach |
POSTGRES_PASSWORD | Database root access | Full database control |
DASHBOARD_PASSWORD | Studio access | Configuration tampering |
For production deployments, use a secrets manager rather than plain .env files. Options include HashiCorp Vault, AWS Secrets Manager, or Docker secrets. This prevents accidental exposure through version control or server access.
Step 2: Configure Firewall Rules
Your server's firewall is the first line of defense. By default, you should deny all incoming traffic and only allow what's necessary.
For UFW (Ubuntu/Debian):
# Enable firewall sudo ufw enable # Allow SSH (don't lock yourself out) sudo ufw allow 22 # Allow HTTP and HTTPS sudo ufw allow 80 sudo ufw allow 443 # Deny direct database access from internet sudo ufw deny 5432 sudo ufw deny 6543 # Supavisor pooler
Critical: Never expose PostgreSQL port 5432 directly to the internet. All database access should go through the Kong API gateway (which handles authentication) or a VPN tunnel for direct connections.
If you need direct database access for tools like pgAdmin or migrations, consider:
- SSH tunneling: Connect through an encrypted SSH tunnel
- VPN: Use Tailscale or WireGuard for private network access
- IP allowlisting: Restrict to known static IPs only
The Docker Compose production guide covers network isolation in more detail.
Step 3: Enable HTTPS with SSL Certificates
Running without HTTPS is unacceptable for production. Authentication tokens, API keys, and user data travel in plain text without encryption.
For self-hosted Supabase, you have several SSL options:
Option A: Reverse proxy with Let's Encrypt (recommended)
Using Caddy as a reverse proxy handles automatic certificate renewal:
# Caddyfile example
your-domain.com {
reverse_proxy localhost:8000
}
studio.your-domain.com {
reverse_proxy localhost:3000
}
Option B: Nginx with certbot
sudo certbot --nginx -d your-domain.com -d studio.your-domain.com
Option C: Use Supascale's built-in SSL
Supascale handles custom domains with free SSL certificates automatically, eliminating the manual certificate management burden. See the custom domains setup guide for configuration details.
Whichever method you choose, verify your SSL configuration at ssllabs.com and aim for an A+ rating.
Step 4: Enforce Row-Level Security
Row-Level Security (RLS) is your primary defense against unauthorized data access. Even with authentication working correctly, missing or misconfigured RLS policies can expose all your data.
Enable RLS on every table:
-- Enable RLS
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
-- Create policies based on authenticated user
CREATE POLICY "Users can view own data" ON your_table
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own data" ON your_table
FOR INSERT WITH CHECK (auth.uid() = user_id);
Common RLS mistakes to avoid:
- Forgetting to enable RLS - Tables created via SQL editor don't have RLS enabled by default
- Overly permissive policies - Using
trueas the policy condition exposes everything - Missing policies for all operations - Having SELECT policies but forgetting INSERT/UPDATE/DELETE
- Not testing as different users - Always verify policies work correctly
The Supabase dashboard includes an RLS assistant, but for self-hosted instances, manually audit every table:
-- Find tables without RLS enabled
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT IN (
SELECT tablename
FROM pg_policies
WHERE schemaname = 'public'
);
Step 5: Harden the Data API
Supabase's Data API (powered by PostgREST) exposes your database through REST endpoints. For production, limit what's accessible.
Use a private schema for sensitive tables:
-- Create private schema CREATE SCHEMA private; -- Move sensitive tables ALTER TABLE sensitive_data SET SCHEMA private;
Tables in the private schema aren't exposed via the Data API but remain accessible from Edge Functions and server-side code.
Disable the Data API entirely if unused:
If you only access Supabase through server-side code or Edge Functions, consider disabling the public Data API to eliminate this attack surface entirely.
Step 6: Configure Authentication Hardening
GoTrue handles authentication, and its default settings need tightening for production.
Password policies:
# Minimum password length GOTRUE_PASSWORD_MIN_LENGTH=12 # Require strong passwords GOTRUE_PASSWORD_REQUIRED_CHARACTERS=abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789
Rate limiting:
Configure rate limits to prevent brute-force attacks:
# Limit login attempts GOTRUE_RATE_LIMIT_HEADER=X-Forwarded-For GOTRUE_RATE_LIMIT_EMAIL_SENT=3600
Enable MFA:
Multi-factor authentication adds critical protection for sensitive accounts. Enable it in your application code:
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp'
})
For OAuth configuration, the auth providers documentation covers setup for Google, GitHub, Discord, and other providers.
Step 7: Set Up Attack Prevention with CrowdSec
CrowdSec is a modern alternative to Fail2Ban that provides collaborative threat intelligence. The community has developed a Supabase-specific collection that parses PostgreSQL and Kong logs.
Installation:
# Install CrowdSec curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec # Install Supabase collection sudo cscli collections install crowdsecurity/supabase
Configure log sources:
# /etc/crowdsec/acquis.yaml filenames: - /var/log/supabase/kong/*.log labels: type: kong --- filenames: - /var/log/supabase/postgres/*.log labels: type: postgresql
CrowdSec can block attackers at multiple levels:
- CDN level: Cloudflare integration
- Web server level: Nginx/Caddy bouncer
- Packet level: iptables/nftables
The collaborative aspect means your instance benefits from threat intelligence gathered across all CrowdSec users—when one deployment detects an attacker, that IP can be blocked across the network.
Step 8: Implement Monitoring and Alerting
Security hardening isn't a one-time task. Continuous monitoring catches issues before they become breaches.
Essential metrics to monitor:
- Failed authentication attempts - Spikes indicate brute-force attacks
- API error rates - Unusual patterns suggest probing
- Database connections - Unexpected connections may indicate compromise
- Resource usage - Crypto miners and other malware show up here
The monitoring guide covers setting up Prometheus, Grafana, and alerting in detail.
For quick wins, enable PostgreSQL logging:
-- Log all authentication failures ALTER SYSTEM SET log_connections = on; ALTER SYSTEM SET log_disconnections = on; ALTER SYSTEM SET log_statement = 'ddl'; SELECT pg_reload_conf();
Security Checklist for Production
Before going live, verify:
- [ ] All default secrets replaced with generated values
- [ ] Firewall configured, database ports not exposed
- [ ] HTTPS enabled with valid SSL certificates
- [ ] RLS enabled on all public tables
- [ ] Password policies enforced
- [ ] Rate limiting configured
- [ ] Logs being collected and monitored
- [ ] Backup encryption enabled (see backup guide)
- [ ] Regular update schedule established
How Supascale Simplifies Security
Managing security across multiple self-hosted Supabase instances multiplies the complexity. Supascale reduces this burden by:
- Automated SSL certificates via Let's Encrypt for all custom domains
- Secure secret management through the configuration UI
- OAuth provider setup without manual environment variable editing
- Centralized backup encryption to S3-compatible storage
At $39.99 for unlimited projects, you get a management layer that handles the operational security tasks while you retain full control of your infrastructure and data.
Conclusion
Securing self-hosted Supabase requires attention to multiple layers: network security, authentication hardening, database policies, and ongoing monitoring. The flexibility of self-hosting means you can implement security measures that match your compliance requirements—whether that's GDPR, HIPAA, or internal policies.
Start with the basics (secrets, firewall, SSL), then layer on RLS policies and attack prevention. The effort upfront prevents the much larger cost of a security incident.
