Fix Deno 'UnsupportedCertVersion' TLS Error on Supabase
Deno Deploy throws 'UnsupportedCertVersion' TLS error when connecting to Supabase Postgres. Here's how to use --unsafely-ignore-certificate-errors and verify your connection stays secure.
You deploy to Deno Deploy, wire up a connection to your Supabase Postgres instance, and the logs spit out: TLS connection failed with message: invalid peer certificate contents: invalid peer certificate: UnsupportedCertVersion. The connection falls back to an unencrypted channel — records are still inserted, but the “success” masks that your data travels in the clear. The immediate fix is the --unsafely-ignore-certificate-errors flag, but keeping encryption enforced requires understanding why Deno’s TLS stack rejects Supabase’s certificate.
The error you see#
Here is the exact output that appears in Deno Deploy logs, reported by a developer on Stack Overflow:
TLS connection failed with message: invalid peer certificate contents:
invalid peer certificate: UnsupportedCertVersion
Defaulting to non-encrypted connectionThe log appears when you first call any database operation, such as a .create() with the denodb ORM or a raw query with the postgres driver. The connection succeeds regardless — you’ll see rows being created — but behind the scenes Postgres accepted an unencrypted TCP stream, exposing credentials and data on the network.
The code that triggers it (taken from the question) looks like this:
import { Database, PostgresConnector } from "https://deno.land/x/denodb/mod.ts";
import "https://deno.land/x/dotenv/load.ts";
export const connection = (() => {
const DENODB_PGURL = Deno.env.get('DENODB_PGURL');
if (DENODB_PGURL) {
return new PostgresConnector({uri: DENODB_PGURL});
}
const DENODB_HOST = Deno.env.get('DENODB_HOST');
if (!DENODB_HOST) throw new Error('DENODB_HOST is not set');
const DENODB_USERNAME = Deno.env.get('DENODB_USERNAME');
if (!DENODB_USERNAME) throw new Error('DENODB_USERNAME is not set');
const DENODB_PASSWORD = Deno.env.get('DENODB_PASSWORD');
if (!DENODB_PASSWORD) throw new Error('DENODB_PASSWORD is not set');
const DENODB_DATABASE = Deno.env.get('DENODB_DATABASE');
if (!DENODB_DATABASE) throw new Error('DENODB_DATABASE is not set');
return new PostgresConnector({
host: DENODB_HOST,
username: DENODB_USERNAME,
password: DENODB_PASSWORD,
database: DENODB_DATABASE,
});
})()
const db = new Database(connection);
export default db;No custom TLS options are set, so denodb uses Deno’s default TLS behaviour — which is to enforce strict certificate validation.
Root cause: Deno’s TLS stack vs. Supabase’s certificate version#
Deno’s network API delegates TLS to rustls, a Rust library that enforces strict X.509 validation, rejecting any certificate version it does not recognize. During the TLS handshake, rustls inspects the tbsCertificate.version field inside the server’s leaf certificate. If that field contains a value outside the range rustls expects (e.g. a draft version or a deprecated encoding), the handshake is aborted with UnsupportedCertVersion. This mismatch has been observed when connecting to Supabase’s managed PostgreSQL servers from certain Deno runtime versions, particularly on Deno Deploy where the environment cannot be patched by the user. The denodb library’s PostgresConnector does not expose TLS options, so it relies on Deno’s default validation.
The error does not mean the certificate is invalid or the server is malicious — it only means the certificate’s version field does not conform to the TLS library’s expectations. The fallback to “non-encrypted” occurs because the Postgres driver, upon TLS handshake failure, reconnects without encryption as a fallback mechanism. The Deno TLS layer itself terminates the connection when the certificate is invalid; the driver catches that error and attempts a plain TCP connection.
Workaround: --unsafely-ignore-certificate-errors#
When you run Deno locally, the quickest way to eliminate the error is to start the script with the --unsafely-ignore-certificate-errors flag. This flag instructs Deno to accept any certificate, regardless of version or trust chain validity:
deno run --allow-net --allow-env --unsafely-ignore-certificate-errors main.tsWith the flag, the TLS connection completes and encryption remains active — the “Defaulting to non-encrypted connection” message disappears because the fallback path is never reached. However, the connection is now blind to any certificate mismatch, including a potential man-in-the-middle attack. Use this flag only on development machines or in throwaway scripts.
On Deno Deploy you cannot pass CLI flags. Use the deno-postgres driver with tls: { enforce: false }. This tells the driver to accept any certificate, including those with unsupported versions, so the TLS handshake succeeds and encryption is maintained. The fallback to an unencrypted connection only occurs if TLS cannot be established at all (for example, if the server does not support TLS), not because of a certificate version mismatch. Therefore, enforce: false is safe and preserves encryption on Deploy; there is no need for a sidecar proxy. The denodb library does not expose a tls option natively, but you can switch to the lower-level deno-postgres driver:
import { Client } from "https://deno.land/x/postgres/mod.ts";
const client = new Client({
hostname: Deno.env.get("DENODB_HOST"),
user: Deno.env.get("DENODB_USERNAME"),
password: Deno.env.get("DENODB_PASSWORD"),
database: Deno.env.get("DENODB_DATABASE"),
tls: { enforce: false }, // accepts any certificate
});
await client.connect();The enforce: false option accepts any certificate, so the TLS handshake succeeds and encryption is maintained. Unlike the CLI flag, it only affects the PostgreSQL connection and does not disable certificate verification globally. To maintain encryption while bypassing verification locally, use the CLI flag. On Deploy, tls: { enforce: false } is the recommended approach — it preserves encryption and is simpler than a sidecar proxy.
Verify the connection is encrypted#
After applying the flag or the tls option, restart your Deno process. The log should no longer contain the fallback message. A quick way to confirm that encryption is active is to run a diagnostic query from within your application:
const result = await client.queryObject`SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid()`;
console.log(result.rows);Expected output when SSL is on:
[ { ssl: true, version: "TLSv1.3" } ]If you still see the UnsupportedCertVersion error after these steps, double-check your connection endpoint. The direct database endpoint (db.xxxxx.supabase.co) and the pooler in session mode (port 5432) may present different certificates; some users have found that the pooler's session mode certificate is accepted by Deno. Try using the pooler address on port 5432 (e.g., aws-0-us-xxxx.pooler.supabase.com:5432). Avoid port 6543 (transaction mode) for SSL-sensitive connections. If you prefer to bypass the pooler entirely, use the direct endpoint with port 5432.
FAQ#
Is --unsafely-ignore-certificate-errors related to the “peer authentication failed” error?#
No. The “peer authentication failed” error happens when Postgres’s pg_hba.conf expects an OS-level user, not a password. Fix: Peer Authentication Failed for User “postgres” covers that situation in detail. The UnsupportedCertVersion error is a pure TLS handshake failure, not an authentication mechanism mismatch.
Can using Supabase’s connection pooler avoid the TLS error?#
Yes, the pooler's session mode (port 5432) may present a certificate that Deno accepts. Use the pooler address with port 5432 (e.g., aws-0-us-xxxx.pooler.supabase.com:5432). The pooler's transaction mode uses port 6543, which is the default for pooling, but session mode on port 5432 can also be used. Check your project's connection strings in the Supabase dashboard. The Supabase Connection Pooling guide explains the port numbers and pooling modes in depth.
Why does the connection still work despite the TLS failure?#
Deno’s Postgres driver, like many others, falls back to an unencrypted connection when TLS negotiation fails, rather than refusing to connect. Postgres accepts that unencrypted session if the server is configured to allow non-SSL connections. Supabase’s infrastructure permits both SSL and non-SSL connections, so the fallback succeeds silently. The problem is invisible data exposure, not a broken pipeline.
Does this error affect Prisma when used with Deno?#
Prisma's CLI commands like prisma migrate dev run on Node.js, not Deno, so they won't encounter this TLS error. However, if you use a Deno-compatible Prisma client (e.g., prisma-client-deno), the client will use Deno's TLS stack and may hit the same UnsupportedCertVersion error when connecting to Supabase. In that case, set the directUrl field in your Prisma datasource to bypass the pooler and connect directly to the database, which may present a different certificate. Use the DATABASE_URL environment variable for the pooled connection and DIRECT_URL for the direct connection, as described in the Prisma documentation.
Further reading: How to Change a PostgreSQL User Password (Supabase).
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
How to Change a PostgreSQL User Password (Supabase)
`ALTER ROLE alice WITH PASSWORD 'newpass';` is the SQL. The psql `\password` prompt avoids logging the cleartext. In Supabase the `postgres` role password is reset from the Dashboard, not SQL. Here is each method, the scram-sha-256 default, and the three things that break after a password change.
Fix PostgreSQL Server Won't Start on Mac OS X (2026)
A fresh Homebrew install of PostgreSQL on a Mac completes without errors, then psql answers connection refused because the server was never started. Which command starts it depends on
Fix ECONNREFUSED 127.0.0.1:5432 in Docker Compose (Node)
Your Node app and Postgres both start, yet every query fails with connection refused. Read the address in the error, fix the host, and gate startup on pg_isready.


