Tailscale for Self-Hosted Supabase: Private Network Access

Lock your self-hosted Supabase behind Tailscale. Set up WireGuard-based private access to Studio and Postgres without exposing ports to the internet.

Cover Image for Tailscale for Self-Hosted Supabase: Private Network Access

Every self-hosted Supabase instance faces the same uncomfortable question: how much of it do you actually need to expose to the public internet? The answer, for most internal tools, staging environments, and team-only backends, is none of it. Yet the default Docker Compose setup binds Kong to a public port, Studio sits one weak password away from your entire database, and plenty of people open Postgres port 5432 to the world "just for a week" to debug something.

Tailscale offers a different model. Instead of exposing services and then defending them, you put your Supabase server on a private WireGuard mesh network that only your devices can reach. No open ports, no firewall whack-a-mole, no reverse proxy auth layers bolted on top. Interest in this approach is real — there's an active GitHub discussion asking Supabase for first-class Tailscale support, and the r/selfhosted crowd has largely stopped exposing home servers directly at all.

This guide walks through putting a self-hosted Supabase instance behind Tailscale: installation, HTTPS on your tailnet, private Postgres access, ACLs, and the hybrid setup most production teams actually want. If you haven't deployed Supabase yet, start with the installation guide and come back — everything here layers on top of a standard Docker deployment.

Why Private Network Access Beats Public Exposure

When your Supabase instance has a public IP and open ports, you inherit a defense workload:

  • Studio is a full admin panel. Behind the default setup, it's protected by a single basic-auth username and password in your .env file. We've covered securing the Studio dashboard in depth — it takes real work.
  • Postgres on 5432 gets scanned constantly. Shodan indexes exposed Postgres instances within hours, and credential-stuffing bots follow.
  • Kong's API gateway is designed to be public, but if your app is an internal tool, even that is unnecessary surface area.

The traditional answer is firewall rules, fail2ban, IP allowlists, and VPN servers you maintain yourself. That's all covered in our network security guide, and it works — but it's ongoing effort, and IP allowlists break the moment someone works from a coffee shop.

Tailscale flips the model. Every device on your tailnet gets a stable private IP in the 100.x.y.z range, connected via encrypted WireGuard tunnels that are established peer-to-peer. Your server's firewall can drop everything except the WireGuard port (and even that can be outbound-only thanks to NAT traversal). If a device isn't on your tailnet, your Supabase instance effectively doesn't exist for it.

Tailscale vs. Cloudflare Tunnel: Which One?

We've previously written about Cloudflare Tunnel for self-hosted Supabase, which solves an overlapping but different problem. Quick comparison:

TailscaleCloudflare Tunnel
Best forPrivate access (team-only, internal tools)Public access without port forwarding
Who can connectOnly devices on your tailnetAnyone on the internet (optionally gated by Access)
Postgres/TCP accessNative — any port, any protocolRequires cloudflared on every client
TLS terminationOptional, via tailscale certAt Cloudflare's edge
Third-party in data pathNo — traffic is peer-to-peer WireGuardYes — traffic flows through Cloudflare
CostFree for personal use (up to 6 users as of the 2026 pricing update)Free tier available

The honest answer: they're complementary. Cloudflare Tunnel publishes your API to end users; Tailscale keeps your admin plane private. Many teams use both.

Setting Up Tailscale on Your Supabase Server

Assuming Supabase is already running via Docker Compose on a VPS or home server:

1. Install Tailscale on the server

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --ssh

The --ssh flag enables Tailscale SSH, which means you can also close port 22 to the public internet — authentication happens via your tailnet identity instead of SSH keys sprayed across laptops.

After authenticating in the browser, check your tailnet IP:

tailscale ip -4
# 100.101.102.103

2. Bind Supabase to the tailnet (or localhost)

By default, the Supabase Docker Compose file publishes Kong on 0.0.0.0:8000, which listens on every interface. Restrict it to your tailnet IP in docker-compose.yml:

services:
  kong:
    ports:
      - "100.101.102.103:8000:8000/tcp"
      - "100.101.102.103:8443:8443/tcp"

Alternatively, bind to 127.0.0.1 and use tailscale serve to proxy (more on that below). Either way, run docker compose up -d and verify with ss -tlnp that nothing Supabase-related is listening on your public interface anymore.

3. Lock down the firewall

Now the satisfying part:

sudo ufw default deny incoming
sudo ufw allow in on tailscale0
sudo ufw allow 41641/udp   # WireGuard, helps direct connections
sudo ufw enable

Your server now accepts traffic only from the Tailscale interface. Port scans against your public IP return nothing.

4. Access Studio and the API

From any device on your tailnet:

http://100.101.102.103:8000

Or, with MagicDNS enabled (it is by default on new tailnets), use the hostname:

http://supabase-prod:8000

Your Supabase client code changes only in its URL:

const supabase = createClient(
  'http://supabase-prod:8000',
  process.env.SUPABASE_ANON_KEY
);

This works for any backend service that's also on the tailnet — a Next.js app on Vercel won't reach it (more on that trade-off later), but your own app servers, CI runners, and teammates' laptops will.

HTTPS on a Private Network

Browsers increasingly complain about plain HTTP, and some auth flows (WebAuthn/passkeys, for instance) hard-require a secure context. Tailscale solves this without exposing anything publicly:

sudo tailscale cert supabase-prod.tail1234.ts.net

This provisions a real Let's Encrypt certificate for your machine's tailnet hostname. Then let Tailscale terminate TLS and proxy to Kong:

sudo tailscale serve --bg --https=443 http://127.0.0.1:8000

Now https://supabase-prod.tail1234.ts.net serves your Supabase API and Studio with a valid certificate, and Kong itself never needs to listen beyond localhost. No nginx, no certbot cron jobs, no DNS challenges. If you later want a branded domain on a public-facing instance instead, that's a different path — see binding custom domains.

One caveat: tailscale cert names go into public Certificate Transparency logs, so your tailnet hostname (not its IP or contents) becomes discoverable. For most teams that's acceptable; know it's there.

Private Postgres Access — The Killer Feature

This is where Tailscale genuinely outshines tunnel-based approaches. Direct Postgres access — for psql, migrations, pgAdmin, TablePlus, or pg_dump backups — normally forces a bad choice: expose 5432 publicly, or SSH-tunnel every single connection.

With Tailscale, Postgres is just there:

psql "postgresql://postgres:[email protected]:5432/postgres"

Bind the port to the tailnet IP as with Kong, and every developer on your tailnet gets direct database access with zero per-connection ceremony, while the public internet sees nothing. Migration tools in CI work too — install Tailscale in your GitHub Actions workflow with the official action, and your pipeline joins the tailnet ephemerally.

Restrict Access with ACLs

By default, every tailnet device can reach every other device. For a team, tighten that with ACLs in the Tailscale admin console:

{
  "acls": [
    {
      "action": "accept",
      "src":    ["group:engineers"],
      "dst":    ["tag:supabase:8000,443"]
    },
    {
      "action": "accept",
      "src":    ["group:admins"],
      "dst":    ["tag:supabase:5432,8000,443"]
    }
  ]
}

Engineers reach the API and Studio; only admins reach Postgres directly. This is identity-based network access control that would take days to replicate with traditional VPN and firewall tooling.

The Honest Trade-Offs

Tailscale isn't the right answer for every deployment:

  • Public apps can't use it. If your Supabase instance serves a consumer app, end users obviously won't install Tailscale. The common production pattern is hybrid: Kong public (behind a reverse proxy with rate limiting), while Studio, Postgres, and metrics endpoints bind to the tailnet only.
  • Serverless platforms are awkward. Vercel and most serverless runtimes can't join a tailnet persistently. If your backend runs there, you need a public API endpoint or a subnet router workaround.
  • A third-party coordination dependency. Traffic is peer-to-peer and end-to-end encrypted, but Tailscale's coordination servers handle key exchange. If that's unacceptable, Headscale (the open-source control server) exists — with more ops burden, which somewhat defeats the purpose.
  • One more thing to onboard. Every teammate and every server needs the agent. For a solo dev this is a five-minute win; for a 50-person org it's a real rollout.

Where Supascale Fits

Whether your instance is fully private, fully public, or hybrid, someone still has to handle deployments, upgrades, backups, and OAuth configuration. Supascale manages self-hosted Supabase instances over SSH — and it works cleanly alongside Tailscale, since the management plane only needs SSH reachability, which Tailscale provides on your tailnet.

That means you can run a completely dark server — no public ports at all — and still get automated S3 backups with one-click restore, version upgrades, and per-service configuration through a UI. Check the features overview for the full list; it's a one-time $39.99 purchase for unlimited projects, which pairs well with the "own your infrastructure" philosophy that led you to self-host in the first place.

Conclusion

Putting self-hosted Supabase behind Tailscale is one of the highest-leverage security moves available: fifteen minutes of setup replaces an entire category of exposure — scanned ports, brute-forced Studio logins, publicly reachable Postgres. The trade-off is that public-facing apps still need a public API path, so most teams land on a hybrid: public data plane, private admin plane.

If you're running an internal tool, a staging environment, or an indie project where every client is under your control, go fully private. Your attack surface drops to approximately zero, and you'll stop caring about half the hardening checklist.

Ready to spend less time babysitting your instance? See what Supascale costs — one payment, unlimited projects, and it works just fine on servers the internet can't see.

Further Reading