Troubleshooting SSL Certificate Errors in Self-Hosted Supabase

Fix SSL certificate errors in self-hosted Supabase: Let's Encrypt failures, Kong chain issues, WebSocket errors, renewal problems, and redirect loops.

Cover Image for Troubleshooting SSL Certificate Errors in Self-Hosted Supabase

Your self-hosted Supabase instance works perfectly over HTTP. Then you point a domain at it, wire up HTTPS, and suddenly nothing connects: browsers throw ERR_CERT_AUTHORITY_INVALID, Realtime subscriptions silently die, and your mobile app refuses to talk to the API at all. SSL errors are one of the most common failure points when moving a self-hosted Supabase deployment from "works on my server" to production — and the error messages rarely tell you where the problem actually is.

This guide walks through the SSL failures we see most often in self-hosted Supabase setups, how to diagnose each one, and how to fix them permanently. If you haven't set up a domain yet, start with our guide to binding custom domains first — this post assumes you have a domain pointed at your server and something is broken.

Why SSL Is a Weak Point in Self-Hosted Supabase

The official Supabase Docker Compose stack ships without any TLS termination. Kong, the API gateway, runs in db-less mode, which means it doesn't persist certificates and the ACME plugin doesn't work out of the box without extra configuration (an external store like Redis, plus setting KONG_LUA_SSL_TRUSTED_CERTIFICATE so Kong can validate the Let's Encrypt chain). Supabase's own docs acknowledge this and recommend putting a reverse proxy in front of the whole stack instead.

That leaves you with three moving parts that can each break independently:

  1. DNS — your domain must resolve to the server before any certificate can be issued
  2. The certificate itself — issuance, the chain, and renewal
  3. The proxy layer — whatever terminates TLS and forwards traffic to Kong

Almost every SSL error maps to one of these three. Let's go through them in the order you should check.

Error 1: "ERR_CERT_AUTHORITY_INVALID" or Self-Signed Certificate Warnings

Symptom: Browsers show a security warning; curl reports SSL certificate problem: self-signed certificate.

Cause: Something in your stack is serving a default or self-signed certificate instead of your real one. Common culprits:

  • Traefik serving its built-in default cert because the ACME challenge failed and it fell back silently
  • Nginx loading snakeoil certs from a default server block that shadows your Supabase config
  • Cloudflare set to "Full" SSL mode while your origin still has a self-signed cert (works) vs. "Full (strict)" (fails)

Diagnosis: Check what certificate is actually being served:

openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -dates

If the issuer says something like TRAEFIK DEFAULT CERT or CN=localhost, your proxy fell back to a placeholder. The fix is never to click through the warning — it's to find out why issuance failed, which brings us to the next section.

Error 2: Let's Encrypt Issuance Fails

Symptom: Your proxy logs show ACME challenge errors like Timeout during connect or Invalid response from http://yourdomain/.well-known/acme-challenge/....

The three usual causes, in order of frequency:

Port 80 is blocked. HTTP-01 validation requires Let's Encrypt to reach your server on port 80. Check your firewall (ufw status, cloud provider security groups) and make sure nothing else is bound to it (ss -tlnp | grep :80). A surprising number of "SSL is broken" reports are actually a VPS provider firewall silently dropping port 80.

DNS isn't propagated or points to the wrong place. Verify with dig +short api.yourdomain.com from an external machine. If you're behind Cloudflare's orange-cloud proxy, HTTP-01 challenges can fail depending on your SSL mode — either use DNS-01 validation or temporarily grey-cloud the record during issuance. If you can't open ports at all, a Cloudflare Tunnel setup sidesteps this entire class of problem by handling TLS at Cloudflare's edge.

You hit Let's Encrypt rate limits. Five failed validations per hour, and 50 certificates per registered domain per week. If you've been retrying a broken config in a loop, you may be locked out for hours. Use the staging environment (--staging in certbot, caServer in Traefik) while debugging, and only switch to production once issuance succeeds.

If you're running multiple Supabase projects on one server, consider a wildcard certificate instead of per-subdomain issuance — we cover that in our wildcard SSL guide for multi-project setups.

Error 3: Incomplete Certificate Chain

Symptom: Browsers work fine, but mobile apps, curl on some systems, or server-to-server calls fail with unable to get local issuer certificate. This one is nasty because it looks intermittent.

Cause: You're serving the leaf certificate without the intermediate CA. Browsers often fetch missing intermediates automatically (AIA fetching); most HTTP libraries and mobile TLS stacks don't.

Diagnosis:

openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com </dev/null 2>/dev/null | grep -A2 "Certificate chain"

You should see at least two certificates in the chain. If you see only one, you're serving an incomplete chain.

Fix: Always use fullchain.pem, not cert.pem, in your Nginx or Kong configuration. With certbot the distinction is easy to miss — ssl_certificate /etc/letsencrypt/live/yourdomain/fullchain.pem; is correct; cert.pem is not. Caddy and Traefik handle this automatically, which is one reason they're the recommended proxies for Supabase — see our comparison of reverse proxy options for self-hosted Supabase.

Error 4: Realtime WebSockets Fail Over HTTPS

Symptom: REST and Auth work over HTTPS, but Realtime subscriptions never receive events. The browser console shows WebSocket connection to 'wss://...' failed.

Cause: Your proxy terminates TLS correctly but doesn't upgrade WebSocket connections. Nginx needs this explicitly:

location /realtime/ {
    proxy_pass http://kong:8000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;
}

The proxy_read_timeout matters too — the default 60 seconds will kill idle WebSocket connections, which shows up as Realtime "randomly disconnecting" every minute. Caddy handles WebSocket upgrades automatically; Nginx and Kong custom routes do not.

Also check for mixed content: if your app is served over HTTPS but SUPABASE_URL still says http://, browsers block the requests before they leave the page. After enabling SSL, update API_EXTERNAL_URL, SITE_URL, and your client's SUPABASE_URL to the https:// versions, then restart the stack.

Error 5: Renewal Failures — the 90-Day Time Bomb

Symptom: Everything worked for three months, then the site went down with an expired certificate.

Let's Encrypt certificates last 90 days by design. Issuance succeeding once doesn't mean renewal will succeed forever:

  • Certbot renewal relies on a systemd timer or cron job — verify it exists with systemctl list-timers | grep certbot
  • If you issued the cert with a temporary standalone server on port 80, renewal will fail once your proxy occupies that port. Use the webroot or DNS-01 method instead
  • Containerized certbot setups often renew the cert on disk but never reload the proxy, so the old cert keeps being served. Add a deploy hook: --deploy-hook "docker exec nginx nginx -s reload"

Set up monitoring before you need it. A simple cron that alerts when the cert has under 14 days remaining catches every silent renewal failure:

openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com </dev/null 2>/dev/null | openssl x509 -noout -checkend 1209600 || echo "Cert expires in <14 days!"

Error 6: Redirect Loops Behind Cloudflare

Symptom: ERR_TOO_MANY_REDIRECTS when accessing Studio or the API through a Cloudflare-proxied domain.

Cause: Cloudflare's "Flexible" SSL mode connects to your origin over HTTP. If your origin then redirects HTTP to HTTPS, you get an infinite loop: Cloudflare requests HTTP → origin redirects to HTTPS → Cloudflare requests HTTP again.

Fix: Set Cloudflare SSL mode to Full (strict) and run a real certificate on your origin (Let's Encrypt or a free Cloudflare Origin CA cert, which lasts 15 years). Flexible mode also means traffic between Cloudflare and your server travels unencrypted — it should never be used for a database-backed API.

How Supascale Eliminates This Entire Category of Problems

Everything above is fixable by hand, but notice the pattern: none of it is a one-time task. Certificates renew every 90 days, chains change when CAs rotate intermediates, and every new project or subdomain repeats the whole dance.

Supascale handles the TLS layer for you. When you bind a custom domain to a project, it provisions a Let's Encrypt certificate automatically, serves the full chain, configures WebSocket upgrades for Realtime, and renews certificates before expiry — no certbot timers to audit, no Kong ACME plugins to configure. It's a one-time purchase of $39.99 for unlimited projects, so running five Supabase instances with five domains costs the same as running one. See the pricing page for details, or the domain binding docs for how it works under the hood.

The honest trade-off: if you enjoy running your own Traefik config and have monitoring for cert expiry already, you may not need it. Supascale exists for teams who want self-hosted Supabase's cost and data-ownership benefits without carrying the proxy-and-PKI operational load themselves.

Quick Diagnostic Checklist

When SSL breaks, work through this in order:

  1. dig +short yourdomain.com — does DNS point at your server?
  2. ss -tlnp | grep -E ':80|:443' — is the right process listening?
  3. openssl s_client — which cert is actually served, and is the chain complete?
  4. Proxy logs — did ACME issuance or renewal fail?
  5. API_EXTERNAL_URL / SITE_URL — do they use https://?
  6. WebSocket upgrade headers — is Realtime specifically broken?

Ninety percent of self-hosted Supabase SSL problems fall to one of those six checks.

Further Reading