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

  1. Enable SSL alongside direct negotiation: { ssl: true, sslnegotiation: 'direct' } or add sslmode=require (or higher) to the connection string.
  2. If you do not want SSL, remove sslnegotiation='direct' and use the default 'postgres' mode (which still supports optional SSL via sslmode).
  3. 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

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


AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03). Data as JSON: /data/errors/75e3296f6188bdbe.json. Report an issue: GitHub.