G: “
Self-hosting Supabase offers unparalleled control, cost savings, and data sovereignty. However, moving from a local development setup to a production environment comes with its own set of challenges, especially when you want your Supabase APIs to be accessible via your own custom domain, secured with HTTPS. This guide will walk you through the entire process, step-by-step, ensuring your self-hosted Supabase instance is professional, secure, and ready for prime time! 🚀
Table of Contents 📖
- Why Self-Host with a Custom Domain & HTTPS?
- Prerequisites & Initial Setup 🛠️
- Understanding the Supabase API Architecture
- Setting Up Your Custom Domain & DNS 🌐
- Configuring Nginx as a Reverse Proxy (HTTP)
- Securing with HTTPS: Let’s Encrypt & Certbot 🔒
- Updating Supabase Configuration & Client Code
- Testing & Troubleshooting 🕵️♀️
- Conclusion & Next Steps
1. Why Self-Host with a Custom Domain & HTTPS? 🤔
Self-hosting Supabase gives you full control over your data and infrastructure. But why go the extra mile for a custom API domain and HTTPS?
- Professionalism & Branding: Instead of
your-server-ip:8000/rest/v1
, imagineapi.yourdomain.com/rest/v1
. It looks cleaner, more professional, and aligns with your brand identity. ✨ - Security (HTTPS is Non-Negotiable): HTTPS encrypts data in transit, protecting your users’ sensitive information from eavesdropping and tampering. For any production application, especially those handling user data or authentication, HTTPS is an absolute must. Browsers will warn users or even block access to HTTP-only sites. 🔒
- SEO & Trust: While API endpoints aren’t directly indexed for SEO in the traditional sense, a secure connection builds trust with users and potentially other services integrating with your API. Search engines also favor secure sites.
- Simplified Client-Side Configuration: Using a single, clean domain for all your Supabase services (Auth, REST, Realtime, Storage, Functions) simplifies your client-side code and makes it easier to manage.
2. Prerequisites & Initial Setup 🛠️
Before we dive in, ensure you have the following ready:
- A Virtual Private Server (VPS):
- OS: Ubuntu 22.04+ or Debian 11+ is recommended.
- Specs: Minimum 4GB RAM, 2 CPUs. For production, consider 8GB RAM, 4 CPUs or more depending on your expected load.
- Public IP Address: Your server needs a static public IP.
- SSH Access: Ensure you can connect to your server via SSH.
- A Registered Domain Name: You’ll need access to its DNS settings (e.g., GoDaddy, Namecheap, Cloudflare).
- Docker & Docker Compose:
- Install Docker:
sudo apt update && sudo apt install docker.io -y
- Start Docker:
sudo systemctl start docker && sudo systemctl enable docker
- Install Docker Compose (usually comes with Docker now, or install it separately):
sudo apt install docker-compose -y
- Verify:
docker --version
anddocker-compose --version
- Install Docker:
- Supabase
docker-compose.yml
:- Clone the Supabase self-hosting repository:
git clone --depth 1 https://github.com/supabase/supabase cd supabase cp .env.example .env
- Important: Open
.env
and configure at least thePOSTGRES_PASSWORD
,JWT_SECRET
, and generate newANON_KEY
andSERVICE_ROLE_KEY
values usingopenssl rand -base64 32
for each. - Expose Ports (Temporary): For initial setup, it’s often easier to expose the Supabase service ports directly from
docker-compose.yml
tolocalhost
. This allows Nginx (on the same host) to easily access them. Find theports
section for each service (e.g.,rest
,auth
,realtime
,storage
,functions
) and ensure they look like:# Example for rest service (PostgREST) rest: image: supabase/rest-pg15:v1.1.2 # Check latest version on Supabase GH ports: - "8000:8000" # Maps container port 8000 to host port 8000 ...
Repeat this for all services you want to expose:
auth
: 9000rest
: 8000realtime
: 4000storage
: 5000functions
: 54321
- Start Supabase (initially):
docker-compose pull docker-compose up -d
Verify services are running with
docker-compose ps
.
- Clone the Supabase self-hosting repository:
3. Understanding the Supabase API Architecture 💡
When you self-host Supabase, you’re essentially running several microservices that work together:
- Auth (GoTrue): Handles user authentication (signup, login, password reset). Default port:
9000
. - REST (PostgREST): Exposes your PostgreSQL database as a RESTful API. Default port:
8000
. - Realtime (Realtime): Enables real-time subscriptions to database changes. Default port:
4000
. - Storage (Storage): Manages file uploads and downloads. Default port:
5000
. - Functions (Edge Functions): Hosts Deno-based serverless functions. Default port:
54321
.
Your goal is to have api.yourdomain.com
serve as a single entry point that intelligently forwards requests to the correct internal Supabase service. This is where a reverse proxy like Nginx comes in!
4. Setting Up Your Custom Domain & DNS 🌐
This is a crucial first step. You need to tell the internet that your chosen custom API domain points to your server’s IP address.
-
Choose Your Subdomain: A common convention is
api.yourdomain.com
,supabase.yourdomain.com
, orbackend.yourdomain.com
. For this guide, we’ll useapi.yourdomain.com
. -
Access DNS Settings: Log in to your domain registrar (e.g., GoDaddy, Namecheap) or DNS provider (e.g., Cloudflare, Route 53).
-
Add an A Record:
- Type:
A
- Name/Host:
api
(or whatever subdomain you chose) - Value/Points to: Your server’s public IP address.
- TTL (Time To Live): Keep it low (e.g., 300 seconds or 5 minutes) initially for faster propagation, then you can increase it later.
Example DNS Record: Type Name Value TTL A api 203.0.113.42 300 - Type:
-
Verify DNS Propagation: It can take a few minutes to several hours for DNS changes to propagate globally. You can check its status using online tools like https://dnschecker.org/. Enter
api.yourdomain.com
and select “A” record. Wait until you see your server’s IP address reflected across various locations. ✅
5. Configuring Nginx as a Reverse Proxy (HTTP)
Nginx will be our traffic cop, directing incoming requests on api.yourdomain.com
to the correct internal Supabase service.
-
Install Nginx:
sudo apt update sudo apt install nginx -y sudo systemctl start nginx sudo systemctl enable nginx
-
Create Nginx Configuration File:
sudo nano /etc/nginx/sites-available/supabase_api.conf
Paste the following configuration. Replace
api.yourdomain.com
with your actual domain.server { listen 80; server_name api.yourdomain.com; # Redirect common well-known folder for ACME challenges (Certbot) location /.well-known/acme-challenge/ { root /var/www/certbot; } # Proxy requests to Supabase services location /auth/ { proxy_pass http://localhost:9000/auth/; 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; proxy_redirect off; } location /rest/ { proxy_pass http://localhost:8000/rest/; 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; proxy_redirect off; } location /realtime/ { proxy_pass http://localhost:4000/realtime/; 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; # For Realtime, WebSocket proxying is essential proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_redirect off; } location /storage/ { proxy_pass http://localhost:5000/storage/; 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; proxy_redirect off; } location /functions/ { proxy_pass http://localhost:54321/functions/; 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; proxy_redirect off; } # Optional: Proxy the root path to a default service, e.g., REST, or return 404 # For a pure API domain, returning 404 is often fine. location / { return 404; # Or proxy_pass http://localhost:8000/; } }
- Explanation:
listen 80;
: Nginx listens on the standard HTTP port.server_name api.yourdomain.com;
: Specifies the domain this server block applies to.location /.well-known/acme-challenge/
: This is critical for Certbot to verify domain ownership when obtaining SSL certificates.location /auth/
,location /rest/
, etc.: These blocks define how requests to specific paths are handled.proxy_pass http://localhost:9000/auth/;
: This is the core of the reverse proxy. It forwards the request to the specified internal Supabase service running onlocalhost
at its exposed port. Make sure the trailing slashes match (e.g.,/auth/
on both ends).proxy_set_header ...
: These headers are important for the proxied service to correctly identify the client’s IP, original host, and protocol. Without them, services like Auth might misbehave or log incorrect information.proxy_redirect off;
: Prevents Nginx from automatically rewritingLocation
headers in responses, which can cause issues with API endpoints.proxy_http_version 1.1;
,proxy_set_header Upgrade $http_upgrade;
,proxy_set_header Connection "Upgrade";
: These are essential for the Realtime service to work correctly, as it uses WebSockets.
- Explanation:
-
Enable the Nginx Configuration:
sudo ln -s /etc/nginx/sites-available/supabase_api.conf /etc/nginx/sites-enabled/
-
Test Nginx Configuration & Restart:
sudo nginx -t sudo systemctl restart nginx
If
nginx -t
reports any errors, fix them before restarting. -
Test HTTP Access: Open your browser and navigate to
http://api.yourdomain.com/rest/v1/health
. You should see a JSON response like{"status":"OK"}
. If you get a 404 or connection error, check your DNS, firewall (ufw status
), and Nginx logs (sudo tail -f /var/log/nginx/error.log
).
6. Securing with HTTPS: Let’s Encrypt & Certbot 🔒
Now for the crucial part: securing your API with HTTPS! Let’s Encrypt provides free SSL certificates, and Certbot automates the process.
-
Install Certbot:
sudo snap install core # Ensure snapd is up-to-date sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot
-
Obtain SSL Certificate: Certbot’s Nginx plugin will automatically configure Nginx for HTTPS.
sudo certbot --nginx -d api.yourdomain.com
- You’ll be prompted to enter an email for urgent renewal notices.
- Agree to the terms of service.
- Certbot will ask if you want to redirect HTTP traffic to HTTPS. Choose 2 (Redirect). This is best practice for production.
-
Verify Nginx Configuration (Post-Certbot): Certbot modifies your
supabase_api.conf
file. Open it to see the changes:sudo nano /etc/nginx/sites-available/supabase_api.conf
You should now see two
server
blocks:- One for
listen 80;
which redirects to HTTPS. - One for
listen 443 ssl;
which includes paths to your SSL certificates (ssl_certificate
,ssl_certificate_key
) and likely a Strong HSTS header (add_header Strict-Transport-Security ...
).
Example of the
443 ssl
block added by Certbot:server { listen 443 ssl; server_name api.yourdomain.com; ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # Add HSTS to protect against protocol downgrade attacks add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # ... (your existing location blocks for /auth/, /rest/, etc. go here) ... }
- One for
-
Automate Certificate Renewal: Let’s Encrypt certificates are valid for 90 days. Certbot automatically creates a cron job or systemd timer to renew them. Test it:
sudo certbot renew --dry-run
If it completes without errors, your renewals are set up! 🎉
-
Test HTTPS Access: Open your browser and navigate to
https://api.yourdomain.com/rest/v1/health
.- You should see the JSON response.
- Crucially, check the padlock icon in your browser’s address bar – it should show a secure connection.
- Try
http://api.yourdomain.com/rest/v1/health
– it should automatically redirect to HTTPS.
7. Updating Supabase Configuration & Client Code
Now that your custom domain is working with HTTPS, you need to tell your Supabase instance and your client applications about it.
-
Update Supabase Environment Variables: Edit your
.env
file (ordocker-compose.yml
if you manage env vars there) in your Supabase directory.SUPABASE_URL
: This is the internal URL Supabase services use to talk to each other. Keep it as `http://localhost:` or the internal Docker network name. *You typically don’t change this to your custom domain unless your Nginx proxies the Supabase Studio dashboard too, which is not covered here.* SUPABASE_PUBLIC_URL
: This is the URL that client applications will use to connect to your Supabase APIs. Set this to your custom HTTPS domain.
# In your .env file: SUPABASE_URL="http://localhost:8000" # Example, ensure it matches your internal setup SUPABASE_PUBLIC_URL="https://api.yourdomain.com"
Note: Ensure you have
ANON_KEY
andSERVICE_ROLE_KEY
also correctly configured from your initial setup. -
Restart Supabase Services: For the new environment variables to take effect:
docker-compose down docker-compose up -d
-
Update Client-Side Code: In your web, mobile, or desktop application, update the Supabase client initialization to use your new custom domain.
-
JavaScript/TypeScript (Web/Node.js):
import { createClient } from '@supabase/supabase-js' const supabaseUrl = 'https://api.yourdomain.com' // <-- Your new custom domain! const supabaseAnonKey = 'YOUR_ANON_KEY' // From your .env const supabase = createClient(supabaseUrl, supabaseAnonKey)
-
Flutter/Dart:
import 'package:supabase_flutter/supabase_flutter.dart'; Future <void> main() async { await Supabase.initialize( url: 'https://api.yourdomain.com', // <-- Your new custom domain! anonKey: 'YOUR_ANON_KEY', ); runApp(MyApp()); }
- Other SDKs: The principle is the same – update the
url
parameter in your client initialization.
-
8. Testing & Troubleshooting 🕵️♀️
Even with the best guides, things can go wrong. Here’s how to debug:
- DNS Propagation: Use https://dnschecker.org/ to confirm your A record points to your server’s public IP.
- Firewall (
ufw
):- Check status:
sudo ufw status
- Ensure ports 80 (HTTP) and 443 (HTTPS) are allowed:
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw reload
- If you exposed other ports directly (e.g., 8000, 9000), consider blocking them from public access once Nginx is working:
sudo ufw deny 8000/tcp
. Nginx acts as the single public entry point.
- Check status:
- Nginx Configuration:
- Test syntax:
sudo nginx -t
- Check logs for errors:
sudo tail -f /var/log/nginx/error.log
- Check access logs:
sudo tail -f /var/log/nginx/access.log
(you should see requests coming in)
- Test syntax:
- Supabase Services:
- Check if Docker containers are running:
docker-compose ps
- Check individual service logs:
docker-compose logs auth
(replaceauth
withrest
,realtime
, etc.)
- Check if Docker containers are running:
- Browser Developer Tools:
- Open your browser’s console (F12) and network tab. Look for failed requests, SSL errors, or mixed content warnings.
- Verify the URL in network requests is your custom HTTPS domain.
curl
Commands:- Test specific endpoints:
curl -v https://api.yourdomain.com/auth/v1/health
- Look for
HTTP/1.1 200 OK
and the certificate details.
- Test specific endpoints:
9. Conclusion & Next Steps 🎉
Congratulations! You’ve successfully self-hosted Supabase with a custom API domain and robust HTTPS encryption. This is a significant step towards a production-ready application.
Remember, this guide focuses on the custom domain and HTTPS. For a truly production-grade setup, also consider:
- Database Backups: Implement a regular backup strategy for your PostgreSQL database.
- Monitoring: Set up monitoring for your server resources (CPU, RAM, disk space) and Supabase services.
- Security Hardening: Review your server’s security (SSH keys, strong passwords, fewer open ports).
- Scalability: If anticipating high load, explore options like load balancers, database replication, or scaling individual Supabase services.
- Updates: Keep your Docker images, Nginx, and Certbot up to date to benefit from new features and security patches.
By following this guide, you’ve laid a strong foundation for your self-hosted Supabase project. Happy coding! 🚀🔐