Network Security for Self-Hosted Supabase: Firewall and Access Control

Configure firewalls, IP allowlists, and network access controls for your self-hosted Supabase deployment. A complete security hardening guide.

Cover Image for Network Security for Self-Hosted Supabase: Firewall and Access Control

Your self-hosted Supabase instance exposes multiple services—PostgreSQL, the REST API, Realtime WebSockets, Storage, and Studio. Each one is a potential entry point for attackers. Without proper network security, you're essentially leaving your database doors wide open to the internet.

This guide walks you through configuring firewalls, IP allowlists, and access controls for self-hosted Supabase, turning your deployment from "technically running" to "production-hardened."

Understanding Supabase's Network Attack Surface

Before configuring anything, you need to understand what you're protecting. A standard self-hosted Supabase deployment exposes these ports:

PortServiceRisk LevelNotes
8000Kong API GatewayHighMain entry point for all API traffic
5432PostgreSQL DirectCriticalDirect database access
6543Supavisor PoolerCriticalPooled database connections
3000Studio DashboardHighFull admin access to your database
9000Storage APIMediumFile uploads and downloads
4000RealtimeMediumWebSocket connections

The critical insight: not all of these should be publicly accessible. In most production deployments, only port 443 (HTTPS via reverse proxy) needs to be exposed to the internet. Everything else should be firewalled.

Firewall Configuration by Platform

Linux Server (UFW)

If you're deploying on a bare Linux server—common for VPS providers like Hetzner, DigitalOcean, or Linode—UFW provides straightforward firewall management.

Start with a deny-all policy:

# Reset to clean state
sudo ufw reset

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (critical - don't lock yourself out)
sudo ufw allow from YOUR_ADMIN_IP to any port 22

# Allow HTTPS for public access
sudo ufw allow 443/tcp

# Allow HTTP for Let's Encrypt challenges
sudo ufw allow 80/tcp

# Enable the firewall
sudo ufw enable

Notice what's not in that list: ports 5432, 6543, 8000, and 3000. These stay closed to the internet.

For services that need internal access only, use specific IP allowlists:

# Allow Studio access only from your office IP
sudo ufw allow from 203.0.113.50 to any port 3000

# Allow direct PostgreSQL from your application servers
sudo ufw allow from 10.0.0.0/24 to any port 5432

AWS Security Groups

On AWS EC2, security groups provide stateful firewall rules. Create a security group specifically for Supabase:

# Terraform example
resource "aws_security_group" "supabase" {
  name        = "supabase-production"
  description = "Security group for self-hosted Supabase"
  vpc_id      = aws_vpc.main.id

  # SSH from admin IPs only
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.50/32"]
    description = "Admin SSH access"
  }

  # HTTPS from anywhere
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Public HTTPS"
  }

  # PostgreSQL from application subnet only
  ingress {
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["10.0.1.0/24"]
    description = "App servers"
  }

  # Allow all outbound
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

DigitalOcean Cloud Firewalls

DigitalOcean's cloud firewalls apply at the hypervisor level, providing protection even if your droplet's local firewall fails:

# Create firewall
doctl compute firewall create \
  --name supabase-production \
  --inbound-rules "protocol:tcp,ports:22,address:203.0.113.50" \
  --inbound-rules "protocol:tcp,ports:443,address:0.0.0.0/0" \
  --outbound-rules "protocol:tcp,ports:all,address:0.0.0.0/0" \
  --droplet-ids YOUR_DROPLET_ID

Reverse Proxy Security Layer

A reverse proxy (Nginx, Traefik, or Caddy) adds a crucial security layer in front of Supabase. If you've followed our reverse proxy setup guide, you already have this in place.

Here's an Nginx configuration with security hardening:

# Rate limiting zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=10r/s;

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;

    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Rate limiting for auth endpoints
    location /auth/ {
        limit_req zone=auth_limit burst=20 nodelay;
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # General API with higher limits
    location / {
        limit_req zone=api_limit burst=200 nodelay;
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

IP Allowlisting at the Reverse Proxy

For internal tools like Studio, restrict access at the proxy level:

server {
    listen 443 ssl http2;
    server_name studio.yourdomain.com;

    # Only allow specific IPs
    allow 203.0.113.50;    # Office IP
    allow 198.51.100.0/24; # VPN range
    deny all;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Anyone not on the allowlist gets a 403 Forbidden response before they even reach Supabase.

PostgreSQL Network Security

Direct database access is the highest-risk attack vector. Even with firewall rules, configure PostgreSQL's own access controls in pg_hba.conf:

# TYPE  DATABASE    USER        ADDRESS             METHOD

# Local socket connections
local   all         postgres                        peer
local   all         all                             scram-sha-256

# IPv4 connections from application servers only
host    all         all         10.0.1.0/24         scram-sha-256

# Reject everything else
host    all         all         0.0.0.0/0           reject

For self-hosted Supabase, you'll need to modify the PostgreSQL container's configuration. Add a volume mount in your docker-compose.yml:

services:
  db:
    image: supabase/postgres:15.6.1.143
    volumes:
      - ./volumes/db/data:/var/lib/postgresql/data
      - ./pg_hba_custom.conf:/etc/postgresql/pg_hba.conf:ro

Securing Internal Service Communication

Supabase services communicate over an internal Docker network. By default, this traffic is unencrypted but isolated from the host network. For higher security requirements, you can:

1. Use Docker Network Encryption

Docker Swarm supports encrypted overlay networks:

docker network create \
  --driver overlay \
  --opt encrypted \
  supabase-internal

2. Implement Service-to-Service TLS

For the paranoid (or compliant), you can configure mTLS between services. This is complex and typically overkill for most deployments, but may be required for certain compliance frameworks.

Monitoring Network Access

Visibility into who's accessing your services is as important as blocking unauthorized access. Configure logging:

Nginx Access Logging

log_format security '$remote_addr - $remote_user [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   '"$http_referer" "$http_user_agent" '
                   '$request_time $upstream_response_time';

access_log /var/log/nginx/supabase-access.log security;

PostgreSQL Connection Logging

In your PostgreSQL configuration:

ALTER SYSTEM SET log_connections = on;
ALTER SYSTEM SET log_disconnections = on;
ALTER SYSTEM SET log_statement = 'ddl';
SELECT pg_reload_conf();

Then ship these logs to your monitoring stack for alerting on suspicious activity.

Common Network Security Mistakes

After helping teams secure their self-hosted Supabase deployments, these are the mistakes I see repeatedly:

1. Exposing Studio to the Internet Studio provides full database admin access. It should never be publicly accessible. Always restrict to VPN or specific IPs.

2. Using Docker's Published Ports Publishing ports with -p 5432:5432 bypasses host firewall rules. Use Docker networks and only expose through a reverse proxy.

3. Forgetting About IPv6 If your server has IPv6 enabled, your firewall rules must cover both protocols:

sudo ufw allow from 2001:db8::/32 to any port 22

4. Not Restricting Outbound Traffic Egress filtering prevents compromised services from calling home:

# Allow only necessary outbound destinations
sudo ufw allow out to 0.0.0.0/0 port 443
sudo ufw allow out to 0.0.0.0/0 port 53
sudo ufw deny out to 0.0.0.0/0

Network Security Checklist

Before going to production, verify:

  • [ ] Only port 443 (HTTPS) is publicly accessible
  • [ ] PostgreSQL ports (5432, 6543) are firewalled or restricted to app servers
  • [ ] Studio is restricted to admin IPs or VPN
  • [ ] Reverse proxy has rate limiting configured
  • [ ] PostgreSQL pg_hba.conf restricts by IP
  • [ ] Connection logging is enabled
  • [ ] Security headers are set on all responses
  • [ ] IPv6 firewall rules match IPv4 rules

Supascale Makes This Easier

Configuring network security manually requires expertise and ongoing vigilance. Supascale handles the heavy lifting—our deployment platform implements sensible firewall defaults, manages SSL certificates automatically, and provides a secure reverse proxy configuration out of the box.

You still control your VPS and can customize access rules, but you start from a secure baseline rather than building from scratch.

Further Reading