neondatabase/neon · error · QueryError
direct SSL negotiation but no TLS support
Error message
direct SSL negotiation but no TLS support
What it means
In process_startup_message, a FeStartupPacket::SslRequest with direct=true means the client initiated the modern direct TLS negotiation (TLS ClientHello as the very first bytes, no plaintext SSLRequest roundtrip). If the server was started without a TLS configuration (tls_config is None), it cannot honor direct SSL and raises QueryError::Other immediately.
Source
Thrown at libs/postgres_backend/src/lib.rs:676
/// - transition to Authentication if auth type is NeonJWT.
/// - or perform TLS handshake -- then need to call this again to receive
/// actual startup packet.
async fn process_startup_message(
&mut self,
handler: &mut impl Handler<IO>,
msg: FeStartupPacket,
) -> Result<(), QueryError> {
assert!(self.state < ProtoState::Authentication);
let have_tls = self.tls_config.is_some();
match msg {
FeStartupPacket::SslRequest { direct } => {
debug!("SSL requested");
if !direct {
self.write_message(&BeMessage::EncryptionResponse(have_tls))
.await?;
} else if !have_tls {
return Err(QueryError::Other(anyhow::anyhow!(
"direct SSL negotiation but no TLS support"
)));
}
if have_tls {
self.start_tls().await?;
self.state = ProtoState::Encrypted;
}
}
FeStartupPacket::GssEncRequest => {
debug!("GSS requested");
self.write_message(&BeMessage::EncryptionResponse(false))
.await?;
}
FeStartupPacket::StartupMessage { .. } => {
if have_tls && !matches!(self.state, ProtoState::Encrypted) {
self.write_message(&BeMessage::ErrorResponse("must connect with TLS", None))
.await?;View on GitHub (pinned to 8f60b04da4)
Solutions
- Start the server with TLS enabled: provide the certificate chain and private key so tls_config is Some
- Client-side, disable direct SSL negotiation or fall back to the classic sslmode=prefer flow
- If TLS terminates at a proxy in front, make sure the proxy handles direct-SSL detection (first-byte sniffing) itself
- Verify with 'openssl s_client' whether the endpoint expects TLS from the first byte
Example fix
# before: server started without TLS, client uses direct SSL pageserver ... # no --tls-cert/--tls-key psql 'postgresql://...?sslmode=direct' # -> direct SSL negotiation but no TLS support # after: enable TLS on the server pageserver ... --tls-cert=server.crt --tls-key=server.key
Defensive patterns
Strategy: validation
Validate before calling
// Server-side: fail fast with a clear log if clients may use direct SSL but TLS is off.
let tls_config = match (cert_path, key_path) {
(Some(cert), Some(key)) => Some(
Arc::new(tls_certs::load_certified_key(&key, &cert).await?)
),
_ => None,
};
if tls_config.is_none() {
tracing::warn!(
"TLS not configured: clients using direct SSL negotiation or sslmode=require will be rejected"
);
} Try / catch
// Client-side: detect the failure and fall back to classic negotiation. // With tokio-postgres, prefer ssl_mode(Prefer) over custom direct-TLS sockets so a // TLS-less server keeps working; direct SSL only when you know the endpoint supports it.
Prevention
- Enable TLS (--tls-cert/--tls-key) on any listener reachable by modern clients that default to direct SSL
- In deployment docs, state explicitly whether each postgres endpoint is plaintext, optional-TLS, or TLS-only
- Health-check TLS readiness (e.g. openssl s_client -noservername) right after deploy
- Keep cert/key rotation automated so operators never disable TLS to 'fix' renewal
When it happens
Trigger: Connecting with a client/libpq version that uses direct SSL negotiation (e.g. sslmode=direct or newer postgres clients with direct-SSL support enabled by default) to a PostgresBackend started without --tls-cert/--tls-key (no tls_config). Non-direct SSLRequest on a TLS-less server is fine: the server just answers 'N' and continues in plaintext.
Common situations: Local dev deployments started without TLS flags while the client library was upgraded to one that prefers direct SSL; staging configs copied to production where TLS termination was expected elsewhere; proxies stripping TLS so the backend sees plaintext while the client assumes TLS.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client did not connect with TLS
- no private key found in {}
- pageserver connection information should be provided
- safekeeper connstrings should be provided
- tenant id should be provided
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/43da8fcdb912d0e7.
Report an issue: GitHub.