화. 8월 12th, 2025

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 📖

  1. Why Self-Host with a Custom Domain & HTTPS?
  2. Prerequisites & Initial Setup 🛠️
  3. Understanding the Supabase API Architecture
  4. Setting Up Your Custom Domain & DNS 🌐
  5. Configuring Nginx as a Reverse Proxy (HTTP)
  6. Securing with HTTPS: Let’s Encrypt & Certbot 🔒
  7. Updating Supabase Configuration & Client Code
  8. Testing & Troubleshooting 🕵️‍♀️
  9. 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, imagine api.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 and docker-compose --version
  • 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 the POSTGRES_PASSWORD, JWT_SECRET, and generate new ANON_KEY and SERVICE_ROLE_KEY values using openssl 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 to localhost. This allows Nginx (on the same host) to easily access them. Find the ports 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: 9000
      • rest: 8000
      • realtime: 4000
      • storage: 5000
      • functions: 54321
    • Start Supabase (initially):
      docker-compose pull
      docker-compose up -d

      Verify services are running with docker-compose ps.


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.

  1. Choose Your Subdomain: A common convention is api.yourdomain.com, supabase.yourdomain.com, or backend.yourdomain.com. For this guide, we’ll use api.yourdomain.com.

  2. Access DNS Settings: Log in to your domain registrar (e.g., GoDaddy, Namecheap) or DNS provider (e.g., Cloudflare, Route 53).

  3. 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
  4. 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.

  1. Install Nginx:

    sudo apt update
    sudo apt install nginx -y
    sudo systemctl start nginx
    sudo systemctl enable nginx
  2. 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 on localhost 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 rewriting Location 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.
  3. Enable the Nginx Configuration:

    sudo ln -s /etc/nginx/sites-available/supabase_api.conf /etc/nginx/sites-enabled/
  4. Test Nginx Configuration & Restart:

    sudo nginx -t
    sudo systemctl restart nginx

    If nginx -t reports any errors, fix them before restarting.

  5. 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.

  1. 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
  2. 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.
  3. 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) ...
    }
  4. 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! 🎉

  5. 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.

  1. Update Supabase Environment Variables: Edit your .env file (or docker-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 and SERVICE_ROLE_KEY also correctly configured from your initial setup.

  2. Restart Supabase Services: For the new environment variables to take effect:

    docker-compose down
    docker-compose up -d
  3. 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.
  • 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)
  • Supabase Services:
    • Check if Docker containers are running: docker-compose ps
    • Check individual service logs: docker-compose logs auth (replace auth with rest, realtime, etc.)
  • 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.

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! 🚀🔐

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다