When you deploy Supabase on your own server, you inherit full responsibility for protecting your API from abuse. Unlike Supabase Cloud, where rate limiting and security measures come pre-configured, self-hosted deployments require manual setup. This guide walks through configuring API rate limiting and security hardening for production self-hosted Supabase instances.
Why API Security Matters for Self-Hosted Deployments
Self-hosted Supabase exposes a RESTful API via PostgREST—a thin layer directly on top of PostgreSQL. Without proper rate limiting, a single malicious actor can exhaust your database connections, spike your server costs, or perform brute-force attacks against your authentication endpoints.
The consequences of inadequate API security include:
- Database connection exhaustion: PostgreSQL has finite connections, and API abuse can quickly consume them all
- Resource starvation: Uncontrolled requests can overwhelm your CPU and memory
- Authentication attacks: Without rate limits, brute-force login attempts become trivial
- Data exfiltration: Attackers can scrape your entire database through unprotected endpoints
Recent Supabase security updates in 2025-2026 have introduced new API key models with publishable keys and revocable secrets, but these features aren't yet available for self-hosted deployments. This means you'll need to implement equivalent protections yourself.
Understanding the Self-Hosted API Architecture
Supabase uses Kong as an API gateway in front of PostgREST. This architecture provides the foundation for implementing rate limiting, but the default configuration leaves much of the security work to you.
Key Components
Client Request → Kong (API Gateway) → PostgREST → PostgreSQL
↓
Rate Limiting
Key Authentication
Request Transformation
Kong sits between incoming requests and your database, making it the natural place to implement rate limiting and security policies. However, the default Docker Compose setup doesn't include aggressive rate limiting out of the box.
Configuring Kong Rate Limiting
Kong's rate limiting plugin restricts the number of requests a client can make within a time window. Here's how to configure it for your self-hosted Supabase deployment.
Step 1: Access Your Kong Configuration
In the default Supabase Docker setup, Kong's configuration lives in kong.yml. Locate this file in your deployment:
# Typically found at ./volumes/api/kong.yml
Step 2: Add Rate Limiting Plugin
Add the rate limiting plugin to your Kong configuration:
plugins:
- name: rate-limiting
config:
minute: 100
hour: 1000
policy: local
fault_tolerant: true
hide_client_headers: false
This configuration allows 100 requests per minute and 1,000 per hour per client. Adjust these values based on your expected traffic patterns.
Step 3: Apply Per-Route Limits
Different endpoints warrant different rate limits. Authentication endpoints, for instance, should be more restrictive:
services:
- name: auth-v1
plugins:
- name: rate-limiting
config:
minute: 10
hour: 100
policy: local
This limits authentication requests to 10 per minute—enough for legitimate users but restrictive enough to slow brute-force attacks.
Step 4: Configure IP-Based Limiting
For accurate client identification behind proxies, configure the Sb-Forwarded-For header handling:
# In your environment configuration GOTRUE_SECURITY_SB_FORWARDED_FOR_ENABLED=true GOTRUE_RATE_LIMIT_HEADER=X-Forwarded-For
This ensures rate limits apply per client IP rather than per proxy server.
Implementing Database-Level Rate Limiting
Kong-level rate limiting protects against API abuse, but you can add a second layer of protection at the database level. This approach, documented in Supabase's security guides, uses PostgreSQL to track and limit requests.
Creating the Rate Limits Table
CREATE SCHEMA IF NOT EXISTS private; CREATE TABLE private.rate_limits ( id BIGSERIAL PRIMARY KEY, ip_address TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_rate_limits_ip_created ON private.rate_limits (ip_address, created_at);
Creating the Rate Limit Check Function
CREATE OR REPLACE FUNCTION public.check_rate_limit()
RETURNS VOID AS $$
DECLARE
client_ip TEXT;
request_count INT;
BEGIN
-- Get client IP from request headers
client_ip := current_setting('request.headers', true)::json->>'x-forwarded-for';
-- Count requests in the last 5 minutes
SELECT COUNT(*) INTO request_count
FROM private.rate_limits
WHERE ip_address = client_ip
AND created_at > NOW() - INTERVAL '5 minutes';
IF request_count > 100 THEN
RAISE EXCEPTION 'Rate limit exceeded'
USING ERRCODE = 'P0001';
END IF;
-- Log this request
INSERT INTO private.rate_limits (ip_address)
VALUES (client_ip);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
This function can be called from database triggers on sensitive tables. Note that database-level rate limiting only works for POST, PUT, PATCH, and DELETE requests—GET requests served by read replicas can't write to the rate limits table.
API Key Management for Self-Hosted Deployments
Self-hosted Supabase uses JWT-based keys (anon and service_role) rather than the newer publishable/secret key model available on Supabase Cloud. This means you need to be especially careful about key security.
Key Types and Usage
| Key | Purpose | Exposure |
|---|---|---|
ANON_KEY | Client-side API access | Public (protected by RLS) |
SERVICE_ROLE_KEY | Server-side admin access | Never expose publicly |
JWT_SECRET | Signs all JWTs | Never expose publicly |
Securing Your Environment File
Your .env file contains all sensitive keys. For production deployments:
# Restrict file permissions chmod 600 .env # Use a secrets manager in production # AWS Secrets Manager, HashiCorp Vault, etc.
Consider using Vault for secrets management to automate key rotation and access control.
Key Rotation Strategy
Unlike Supabase Cloud, self-hosted deployments don't have built-in key rotation. Implement your own rotation process:
- Generate new keys: Create new JWT secret and API keys
- Deploy in parallel: Configure both old and new keys to work simultaneously
- Update clients: Gradually migrate clients to new keys
- Monitor usage: Watch for requests using old keys
- Revoke old keys: Remove old keys once migration is complete
For JWT secrets specifically, wait at least one hour plus your token expiry time before revoking old secrets to avoid signing out active users.
Row-Level Security: Your Last Line of Defense
Even with rate limiting and key management in place, Row-Level Security (RLS) remains your most important protection. If a key is compromised, RLS prevents unauthorized data access.
Verifying RLS Status
Check that RLS is enabled on all tables:
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';
Any table showing rowsecurity = false is exposed via the API without protection. See our complete RLS guide for implementation details.
Common RLS Mistakes
The most frequent security issues we see in self-hosted deployments:
- RLS disabled during development: Forgetting to enable it before production
- Service role key in client code: Bypasses all RLS policies
- Overly permissive policies: Using
trueas the check expression - Missing policies on new tables: Adding tables without corresponding RLS rules
Monitoring and Alerting
Rate limiting is only effective if you can see when it's being triggered. Set up monitoring for your API security:
Kong Metrics
Enable Prometheus metrics in Kong to track:
plugins:
- name: prometheus
config:
status_code_metrics: true
latency_metrics: true
bandwidth_metrics: true
Key Metrics to Monitor
- Rate limit violations: Spike in 429 responses indicates attack or misconfiguration
- Authentication failures: Multiple failed logins from same IP
- API latency: Sudden increases may indicate resource exhaustion
- Connection pool usage: PostgreSQL connections approaching limits
For comprehensive monitoring setup, see our observability guide.
How Supascale Simplifies API Security
Configuring rate limiting, key management, and monitoring for self-hosted Supabase requires significant DevOps expertise. Supascale handles much of this complexity:
- Automated configuration: Security best practices applied by default
- OAuth provider management: Configure Google, GitHub, Discord authentication through a simple UI
- Backup security: Your backups are encrypted and stored securely in your S3-compatible storage
- One-time pricing: Pay $39.99 once for unlimited projects—no ongoing security management costs
For teams that want the cost benefits of self-hosting without the security configuration burden, Supascale provides a middle path between fully managed and DIY deployments.
Conclusion
API security for self-hosted Supabase requires attention to multiple layers: Kong rate limiting, database-level protections, key management, and Row-Level Security. While Supabase Cloud handles these concerns for you, self-hosted deployments put this responsibility squarely on your team.
Start with Kong rate limiting to prevent obvious abuse, then layer in database-level checks for sensitive operations. Treat your API keys with the same care as database credentials, and verify RLS is enabled on every table before going to production.
The security configuration described here represents baseline protection. For high-stakes deployments handling sensitive data, consider additional measures like Web Application Firewalls (WAF), intrusion detection, and regular security audits.
Ready to simplify your self-hosted Supabase security? Check out Supascale's pricing for a one-time purchase that includes automated security configuration.
