#14989·kong

PostgreSQL TLS handshake omits SNI, breaking Kong behind SNI-routing proxies (bundled pgmoon passes nil server_name)

Author: jmrplensCreated Sep 4, 2026Updated Sep 10, 2026

Is there an existing issue for this?

Searched open issues for pgmoon, sni, server_name and postgres; the closest is #14956, which is a different pg_ssl_* gap. Originally reported by @ArthurHoengen as a discussion in #14937, moved here because it is a defect rather than a question.

Kong version

3.9.3 (kong:latest, bundling pgmoon 1.16.2)

Current Behavior

Kong never sends a TLS SNI extension when connecting to PostgreSQL, so it cannot reach a database that sits behind a proxy which routes by SNI. That covers Traefik TCP routers, Neon, and similar managed offerings.

The cause is in the bundled pgmoon/init.lua, in the branch taken when sock_type is nginx, which is Kong's:

lua
return self.sock:sslhandshake(false, nil, self.config.ssl_verify)

The second argument to the cosocket sslhandshake is server_name and it is a literal nil. Kong passes one everywhere else it opens a TLS socket, for example sock:sslhandshake(false, conf.tls_sni, false) in the tcp-log plugin and conf.ldap_host in ldap-auth.

There is no way to work around it through configuration. kong.conf exposes pg_ssl and pg_ssl_verify for this connection and nothing that sets a server name.

Expected Behavior

The handshake should carry the configured pg_host as SNI when it is a hostname, matching what libpq has done since PostgreSQL 14.

Steps To Reproduce

A plain TLS sniffer is not sufficient, because libpq sends its cleartext SSLRequest first and only starts TLS after the server answers S. I used a listener that answers that preamble and then parses the ClientHello for a server_name extension, and compared clients against the same hostname on the same Docker network:

libpq 17 (psql)                   ClientHello 1565 bytes -> SNI = "pg.example.test"
pgmoon 1.16.2 in stock Kong 3.9.3 ClientHello  299 bytes -> SNI = none

Mounting a pgmoon patched to self.sock:sslhandshake(false, self.config.host, self.config.ssl_verify) into the same image, changing nothing else, produced ClientHello 323 bytes -> SNI = "pg.example.test".

Note that SNI carries hostnames only, so pg_host must be a name; psql against an IP correctly sends no SNI either.

Anything else?

Upstream has both halves open already, so this is likely a dependency bump rather than a patch here:

  • leafo/pgmoon#153 for the report
  • leafo/pgmoon#154 for a fix that adds an SNI test and corrects the hostname handling

Filing this so there is somewhere to track picking that up once it lands.