brianc/node-postgres · error · Error
sslnegotiation=direct requires SSL to be enabled
Error message
sslnegotiation=direct requires SSL to be enabled
What it means
Thrown by the ConnectionParameters constructor (connection-parameters.js:110-112) when sslnegotiation is set to 'direct' but SSL is not enabled. Direct SSL negotiation means the client begins the TLS handshake immediately upon TCP connect — there is no plaintext fallback — so it is meaningless without SSL. The guard prevents a configuration that would send a TLS ClientHello to a server expecting a PostgreSQL StartupMessage (or vice versa), which would hang or produce a confusing protocol error.
Source
Thrown at packages/pg/lib/connection-parameters.js:111
if (this.ssl === 'no-verify') {
this.ssl = { rejectUnauthorized: false }
}
if (this.ssl && this.ssl.key) {
Object.defineProperty(this.ssl, 'key', {
enumerable: false,
})
}
// How to negotiate SSL: 'postgres' (default, the traditional SSLRequest
// handshake) or 'direct' (start the TLS handshake immediately on connect).
this.sslnegotiation = val('sslnegotiation', config, 'PGSSLNEGOTIATION')
if (this.sslnegotiation !== undefined && this.sslnegotiation !== 'postgres' && this.sslnegotiation !== 'direct') {
throw new Error(
`Invalid sslnegotiation value: "${this.sslnegotiation}". Valid values are "postgres" and "direct".`
)
}
if (this.sslnegotiation === 'direct' && !this.ssl) {
throw new Error('sslnegotiation=direct requires SSL to be enabled')
}
this.client_encoding = val('client_encoding', config)
this.replication = val('replication', config)
// a domain socket begins with '/'
this.isDomainSocket = !(this.host || '').indexOf('/')
this.application_name = val('application_name', config, 'PGAPPNAME')
this.fallback_application_name = val('fallback_application_name', config, false)
this.statement_timeout = val('statement_timeout', config, false)
this.lock_timeout = val('lock_timeout', config, false)
this.idle_in_transaction_session_timeout = val('idle_in_transaction_session_timeout', config, false)
this.query_timeout = val('query_timeout', config, false)
if (config.connectionTimeoutMillis === undefined) {
this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0
} else {
this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)View on GitHub (pinned to c5e8c9a57b)
Solutions
- Enable SSL alongside direct negotiation: { ssl: true, sslnegotiation: 'direct' } or add sslmode=require (or higher) to the connection string.
- If you do not want SSL, remove sslnegotiation='direct' and use the default 'postgres' mode (which still supports optional SSL via sslmode).
- Verify that your PostgreSQL server is configured to accept direct TLS connections (ssl=on in postgresql.conf).
Example fix
// before
const client = new Client({ sslnegotiation: 'direct' }); // ssl not set
// after
const client = new Client({ ssl: true, sslnegotiation: 'direct' });
// or connection string: postgres://host/db?sslmode=require&sslnegotiation=direct Defensive patterns
Strategy: validation
Validate before calling
function validateDirectSsl(config) {
const neg = config.sslnegotiation ?? process.env.PGSSLNEGOTIATION;
if (neg === 'direct' && !config.ssl) {
throw new Error('sslnegotiation=direct requires SSL to be enabled (set ssl: true or sslmode=require+).');
}
} Prevention
- Always pair sslnegotiation='direct' with an enabled ssl option.
- When deploying, set PGSSLMODE=require (or higher) alongside PGSSLNEGOTIATION=direct.
- Add a config validator that checks SSL/negotiation consistency before constructing the Client.
When it happens
Trigger: Passing { sslnegotiation: 'direct' } without an ssl option, or with ssl: false. Also via connection string '?sslnegotiation=direct' without an ssl-related parameter when PGSSLMODE is unset/disable. The check at line 110 tests this.sslnegotiation === 'direct' && !this.ssl.
Common situations: Setting sslnegotiation=direct for performance (avoids an extra round-trip) but forgetting to also enable ssl. A deployment where PGSSLMODE was previously set but is now disabled, while PGSSLNEGOTIATION=direct remains.
Related errors
- Both useLibpqCompat and uselibpqcompat are set. Please use o
- Invalid sslnegotiation value: "${this.sslnegotiation}". Vali
- SECURITY WARNING: Using sslmode=verify-ca requires specifyin
- Invalid ${key}: ${value}
- Client was passed a null or undefined query
AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03).
Data as JSON: /data/errors/75e3296f6188bdbe.json.
Report an issue: GitHub.