How to Fix PostgreSQL "SSL Error: Certificate Verify Failed" on Heroku / Supabase
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-fullor 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.