PostgreSQL

How to Fix PostgreSQL "SSL Error: Certificate Verify Failed" on Heroku / Supabase

4 min read by DebuggedIt

Quick answer

Connecting to a managed PostgreSQL database on Heroku or Supabase fails with a certificate verification error, even though the database is genuinely reachable...

Connecting to a managed PostgreSQL database on Heroku or Supabase fails with a certificate verification error, even though the database is genuinely reachable and the credentials are correct. Managed platforms require SSL for connections by default, and this error means your client's SSL verification settings don't align with how that platform's certificate is actually set up.

The Problem

A connection attempt fails specifically at the TLS handshake stage, before authentication is even reached:

$ psql "postgres://user:pass@db.xxxxxxxx.supabase.co:5432/postgres"
psql: error: connection to server at "db.xxxxxxxx.supabase.co" failed: SSL error: certificate verify failed

In application code, the equivalent error surfaces through whatever driver you're using:

Error: self signed certificate in certificate chain
    at TLSSocket.onConnectSecure (node:_tls_wrap:1660:34)

Why It Happens

Both Heroku Postgres and Supabase require SSL/TLS for connections, and depending on the specific platform and your client library's default behavior, this error can come from a couple of different, related causes:

  • Your client is trying to fully verify the certificate chain (sslmode=verify-full or an equivalent strict setting) against a CA bundle that doesn't include the specific certificate authority the platform uses, causing verification to fail even though the certificate itself is legitimate.
  • The platform uses a certificate that requires a specific CA bundle to be explicitly trusted β€” Supabase and Heroku both provide connection strings and, in some cases, specific CA certificates meant to be used together with strict verification modes, and skipping that step causes exactly this failure.
  • An outdated system CA trust store on your local machine or deployment environment that doesn't yet include a newer certificate authority the platform has since migrated to.
  • Your database client or ORM defaults to a stricter SSL mode than what's actually configured, or conversely defaults to a mode too loose for the platform's requirements, depending on the specific library and version.

The Fix

Check what SSL mode your connection string or client configuration currently specifies:

echo $DATABASE_URL
postgres://user:pass@db.xxxxxxxx.supabase.co:5432/postgres?sslmode=verify-full

For most application use cases connecting to Supabase or Heroku, require mode is the standard, well-supported choice β€” it enforces an encrypted connection without requiring you to separately manage a specific CA certificate file, which is the more common source of this exact error than a genuine security concern:

postgres://user:pass@db.xxxxxxxx.supabase.co:5432/postgres?sslmode=require

If your security requirements genuinely need full certificate chain verification (common in regulated environments), download the platform's specific CA certificate and reference it explicitly rather than relying on your system's default trust store:

# Supabase example β€” download the CA cert from your project's connection settings
curl -o supabase-ca.crt https://supabase.com/dashboard/project/xxxxxxxx/settings/database
postgres://user:pass@db.xxxxxxxx.supabase.co:5432/postgres?sslmode=verify-full&sslrootcert=/path/to/supabase-ca.crt

For Node.js applications using the pg library directly rather than a connection string, configure SSL explicitly in code:

const {Pool} = require('pg');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: true,
    ca: fs.readFileSync('/path/to/supabase-ca.crt').toString(),
  },
});

For quick local development or debugging only β€” never in production β€” you can temporarily disable strict verification while you sort out the certificate configuration, but treat this as a diagnostic step, not a permanent fix, since it weakens the security guarantees SSL is meant to provide:

postgres://user:pass@db.xxxxxxxx.supabase.co:5432/postgres?sslmode=require&rejectUnauthorized=false

If the underlying issue is an outdated system CA store rather than anything platform-specific, updating your OS's certificate bundle often resolves it without needing any platform-specific CA file at all:

# Debian/Ubuntu
sudo apt update && sudo apt install ca-certificates
sudo update-ca-certificates

Still Not Working?

If you're deploying to a serverless or containerized environment and the connection works locally but fails specifically in that deployed environment, the deployment environment's own CA trust store may differ from your local machine's β€” some minimal container base images ship without a full set of trusted CAs by default:

# Alpine-based Docker images commonly need this explicitly
RUN apk add --no-cache ca-certificates

Adding the CA certificates package to your Dockerfile (or equivalent for your base image) ensures the deployed environment has the same trust store your local machine already has, which resolves certificate verification failures that only show up after deployment despite working perfectly during local development and testing.