Skip to content

What a pasted managed PostgreSQL URL configures

Host-heuristic SSL turns encryption on without verifying the certificate, and two sslmode values are declined by name rather than guessed at.

You copy a URL out of a Neon, Supabase or RDS console, paste it into a connection form, and the connection opens. It looks finished. What actually happened is that the URL’s sslmode parameter was read out of the query string and either mapped onto a form field, declined by name, or ignored - and if no mode was named at all, TLS was switched on by a rule that matches the host name and does not check the certificate.

That last state is the one worth reading about, because an encrypted connection that verifies nothing looks identical to one that does.

What the parser reads out of the URL

The paste box parses the URL and fills the form. Host, port, database, user and password come off the URL structure. The query string is read too, so postgresql://host/db?sslmode=verify-full arrives with SSL Mode already set on the form rather than left at whatever the default was.

Four libpq values map one to one:

In the URL On the form
sslmode=disable Disable
sslmode=require Require
sslmode=verify-ca Verify CA
sslmode=verify-full Verify Full

Two more spellings are handled because they are what JDBC and Heroku write. ?ssl=false maps to disable. ?ssl=true maps to verify-system, which is the form’s own mode name - not a libpq one - for “verify the chain and the host name against the runtime’s trust store, with no PEM to paste”. That mapping follows a stated rule: a boolean TLS spelling maps onto the mode that matches what the engine’s own driver does with it, never onto a weaker one. Handed ssl: true, pg connects with Node’s default rejectUnauthorized: true, so verify-system is the mode that describes it.

It used to map to require, which here means rejectUnauthorized: false: encrypted, chain not verified. The form had no mode that both verified and asked the user for nothing, and pointing ?ssl=true at verify-ca or verify-full turned a working paste into a connection nobody could complete without a certificate they did not have. verify-system is that missing mode.

Because verify-system is the form’s mode name rather than libpq’s, ?sslmode=verify-system in a URL is reported as a parameter that cannot be honoured rather than accepted. A spelling libpq does not define is not a spelling the parser will act on, even when the form happens to use the same word.

Encrypted is not authenticated

Now the case where the URL names no mode at all.

shouldEnableSSL() in the shared SQL base looks at the host. If it matches a known managed pattern - supabase, render, neon, planetscale, aws, azure, gcp, cloud - SSL is enabled. What it enables is { rejectUnauthorized: false }.

Read that literally. The cloud SSL auto-detect does not verify the server certificate. The connection is encrypted and not authenticated, which leaves it open to a machine in the middle that terminates TLS and presents a certificate nobody checked. The bytes on the wire are unreadable to a passive observer. The identity of the party at the other end is unestablished. Those are different properties and only the first one is delivered by the heuristic.

The precedence in buildSSLConfig() is what makes this recoverable: an explicit connection.ssl is resolved first, and the host heuristic only runs when the connection carries no ssl object at all. Choosing a mode other than Disable is not a workaround for the heuristic - it is the branch that takes priority over it.

Disable is the exception, and worth knowing before you rely on it. The connection form opens at SSL Mode disable and writes no ssl object while it is there: buildConnection() in use-connection-form.ts builds one only when the mode is something else, which is how turning TLS off clears a certificate the form was holding. An absent ssl object is exactly the input the heuristic acts on, so on a matching host the field reading Disable still opens an encrypted, unverified connection.

Mode What pg gets
disable no SSL
require rejectUnauthorized: false - the one mode that encrypts without verifying
verify-system rejectUnauthorized: true, no ca passed, so Node’s trust store checks chain and host name
verify-ca / verify-full rejectUnauthorized: true, with the caCert you paste set as ca

pg exposes no separate host-name check, so verify-ca and verify-full build the same object here. That is a property of the driver.

The two sslmode values that are declined rather than mapped

prefer and allow are not mapped. Neither is any spelling the table above does not know. Both mean “encrypt if the server offers it, otherwise do not”, and no mode on the form expresses that, because every mode on the form is a decision.

Guessing in either direction is wrong against a live server. Measured on PostgreSQL 18 with no server certificate configured: ?sslmode=prefer connects and pg_stat_ssl.ssl reads f, while ?sslmode=require is refused outright with server does not support SSL, but SSL was required. Map prefer to require and a URL that worked stops working. Map it to disable and a URL that would have been encrypted against a TLS-capable server silently stops being encrypted.

So the mode the form already holds is left alone, and the paste banner names the parameter it declined to act on. You get told sslmode=prefer was not applied, in the same moment you paste, rather than discovering months later that a connection you believed was configured from the URL was configured from a default. Set SSL Mode yourself in the SSL / TLS panel.

Certificate paths belong to the machine that wrote the string

sslrootcert, sslcert and sslkey are ignored.

They are ignored because of where this software runs. Those three parameters are filesystem paths, and they are paths on the machine that composed the connection string - a laptop, a CI runner, someone’s terminal. LibreDB Studio deploys as a container beside the database, so the process that opens the connection is the server, and /Users/someone/.postgresql/root.crt does not exist there. A path that resolves on the writer’s machine and not on the reader’s is worse than absent: it fails at connect time with a file error that reads like a permissions problem.

The SSL / TLS panel holds PEM text, not a path. Paste the certificate contents - caCert, clientCert and clientKey map onto pg’s ca, cert and key. The security page states the same boundary alongside the others this build publishes.

Setting a verified TLS mode by hand

For a managed provider whose certificate is signed by a CA the runtime already trusts - which is the ordinary case for the vendors the heuristic matches - verify-system needs nothing pasted:

const connection = {
  id: 'pg-prod',
  name: 'Production',
  type: 'postgres',
  connectionString: 'postgresql://user:secret@ep-x.eu-central-1.aws.neon.tech/main',
  ssl: { mode: 'verify-system' },
  createdAt: new Date(),
};

That sets rejectUnauthorized: true with no ca, so Node verifies the chain and the host name against its own trust store. Pick verify-ca or verify-full, and supply caCert, only when the server’s certificate is signed by a CA the runtime does not already trust - a private CA, or a provider that publishes its own root.

Two caveats on the vendor list. The host patterns are string matches, not a verified compatibility claim: no managed PostgreSQL service has been probed through this provider, and Neon, Supabase, Cloud SQL, Aurora and AlloyDB are recorded as no instance was reachable. A host that matches gets encryption enabled; that is all the match means. And a host that does not match any pattern gets nothing from the heuristic: SSL falls to the driver default unless you set a mode. That includes every self-hosted server and every managed provider on a custom domain.

The shortest correct habit: after pasting a URL, open the SSL / TLS panel and read the mode. A mode you chose is the mode you get. A mode you did not choose - including the Disable the form opens on - hands the decision to a host-name match that encrypts without checking who answered. The engine reference carries the rest of the PostgreSQL provider’s transport detail.