Skip to content

PostgreSQL Client Protocol — Section Overview

Contents:

This subcategory is the backend half of the PostgreSQL frontend/backend (FE/BE) wire protocol — everything between a TCP/Unix-socket accept() and the moment a fully-formed SQL command enters the parser. Three concerns:

  1. The wire protocol itself — the length-prefixed, type-tagged message format (protocol.h’s PqMsg_* byte codes), the buffered I/O that frames those messages (libpq/pqcomm.c), the (de)serialization helpers (libpq/pqformat.c), and the steady-state message loop inside PostgresMain (tcop/postgres.c) that reads one message, dispatches on its first byte (Q simple query, P/B/E extended query, X terminate), and loops. This is the protocol’s running phase.
  2. Connection establishment and authentication — the one-time climb a connection makes before the loop: the startup packet that carries the user and database (parsed in tcop/backend_startup.c), the HBA lookup that picks an auth method, and the ClientAuthentication dispatch in libpq/auth.c that runs it — with SCRAM-SHA-256 over the SASL framing (auth-sasl.c + auth-scram.c) as the modern challenge-response default.
  3. Transport security — the optional encryption layer negotiated before the startup packet: TLS (be-secure.c over be-secure-openssl.c) and GSSAPI encryption (be-secure-gssapi.c), each requested by a magic negotiation code (or, for TLS in PG17+, established directly via ALPN).

Sharp boundaries. This section is deliberately narrow — it owns the pipe, not what flows through it:

  • Above (into the query pipeline): the message loop’s job ends when it has a message body in hand. Parsing Q/P text into a parse tree, planning, and execution belong to query-processing (postgres-overview-query-processing.md). The boundary is the call from the loop into exec_simple_query / exec_parse_message.
  • Below / sideways (into server architecture): who runs the loop — the postmaster fork()ing a B_BACKEND per connection, BackendInitialize, and PostgresMain’s place in the backend lifecycle — belongs to server-architecture (postgres-overview-server-architecture.md). This section picks up the socket after the fork and hands the authenticated session back to backend startup (InitPostgres) when auth succeeds.
  • The replication subprotocol (walsender’s COPY-both streaming, the START_REPLICATION command set) reuses this same message framing but is documented under replication-ha (postgres-overview-replication-ha.md); this section covers only the ordinary client path and notes the seam.
  • The frontend libpq client library (src/interfaces/libpq) is the peer implementation of this protocol and is out of scope — this tree analyzes the backend. The shared contract is protocol.h.

Every connection climbs one linear stack exactly once, then settles into the message loop. The diagram names the module doc that owns each band.

flowchart TB
  SOCK["accepted socket<br/>(postmaster forked a B_BACKEND)<br/>— owned by server-architecture"]

  subgraph TLSGSS["postgres-tls-gssapi.md"]
    NEG["negotiation request packet<br/>SSLRequest / GSSENCRequest<br/>(ProcessSSLStartup)"]
    ENC["establish encryption layer<br/>be_tls_open_server / secure_open_gssapi<br/>then secure_read / secure_write wrap all I/O"]
  end

  subgraph WIRE1["postgres-wire-protocol.md (establishment)"]
    SUP["startup packet<br/>ProcessStartupPacket<br/>(user, database, GUCs)"]
  end

  subgraph AUTH["postgres-authentication.md"]
    HBA["HBA lookup picks auth method<br/>(hba.c)"]
    CA["ClientAuthentication dispatch<br/>(auth.c)"]
    SCRAM["SASL / SCRAM-SHA-256 exchange<br/>CheckSASLAuth -> pg_be_scram_mech<br/>(auth-sasl.c, auth-scram.c)"]
  end

  HANDOFF["authentication succeeds<br/>-> InitPostgres / backend startup<br/>— owned by server-architecture"]

  subgraph WIRE2["postgres-wire-protocol.md (steady state)"]
    LOOP["PostgresMain message loop<br/>ReadCommand -> dispatch on first byte<br/>Q simple / P,B,E extended / X terminate"]
    FRAME["framing + (de)serialization<br/>pqcomm.c (buffered I/O)<br/>pqformat.c (PqMsg_* codes)"]
  end

  PIPE["parsed message enters parser/executor<br/>— owned by query-processing"]

  SOCK --> NEG
  NEG --> ENC
  ENC --> SUP
  NEG -. "no encryption requested" .-> SUP
  SUP --> HBA
  HBA --> CA
  CA --> SCRAM
  SCRAM --> HANDOFF
  HANDOFF --> LOOP
  LOOP <--> FRAME
  LOOP --> PIPE

Two structural facts the diagram encodes:

  • Encryption is negotiated before the startup packet, not after. A client that wants TLS sends an SSLRequest (a special length+magic-code packet with no message-type byte) and waits for a single S/N byte; only then does the real startup packet flow, now inside the encrypted channel. GSSAPI encryption works the same way with GSSENCRequest. PG17 added direct TLS (ALPN), which skips the request byte. This is why ProcessSSLStartup runs ahead of ProcessStartupPacket in backend_startup.c.
  • The same first-byte 'p' (PqMsg_PasswordMessage) carries every client-to-server auth response — cleartext password, SASL initial response, and GSS token alike (protocol.h aliases PqMsg_PasswordMessage, PqMsg_SASLInitialResponse, PqMsg_SASLResponse, PqMsg_GSSResponse to the same code). The auth state machine, not the byte, distinguishes them.

Cross-referenced-first — read the running protocol before the one-time climb, because authentication and TLS are described in terms of the message framing they reuse:

  1. postgres-wire-protocol.md — start here. The message format, the PqMsg_* codes, pqcomm/pqformat, and the PostgresMain loop are the vocabulary the other two docs lean on. It also owns the startup packet, so you see the establishment-to-loop transition end to end.
  2. postgres-authentication.md — then the auth climb that sits between the startup packet and the loop: HBA method selection, the ClientAuthentication dispatch, and the SCRAM/SASL challenge-response exchange (the modern default), plus crypt.c verifier handling.
  3. postgres-tls-gssapi.md — last, the transport layer underneath both. Read it after the wire protocol because TLS/GSS negotiation is itself a pre-startup packet exchange, and after auth because GSSAPI blurs the line between encryption and authentication.

Forward references — these module docs are planned; summaries are predictive.

Module docOne-line scope
postgres-wire-protocol.mdThe FE/BE message format (protocol.h PqMsg_* codes), buffered framing in pqcomm.c, (de)serialization in pqformat.c, the startup packet (backend_startup.c), and the PostgresMain simple-vs-extended-query message loop in tcop/postgres.c.
postgres-authentication.mdThe HBA-driven ClientAuthentication dispatch in auth.c, the SASL framing (auth-sasl.c, pg_be_sasl_mech) carrying the SCRAM-SHA-256 exchange (auth-scram.c), and password-verifier handling (crypt.c) — including the channel-binding tie to TLS.
postgres-tls-gssapi.mdThe pre-startup encryption negotiation and the secure_read/secure_write abstraction: TLS via OpenSSL (be-secure.c, be-secure-openssl.c, the SSLRequest/direct-ALPN paths in README.SSL) and GSSAPI encryption (be-secure-gssapi.c, GSSENCRequest).
  • postgres-overview-server-architecture.md — the immediate neighbor on both ends. It owns the postmaster fork that produces the B_BACKEND, BackendInitialize, and PostgresMain’s role in the backend lifecycle. This section borrows the forked socket from it and hands the authenticated session back to InitPostgres there. The boundary is “before vs. inside the message loop.”
  • postgres-overview-query-processing.md — the section the message loop feeds. Once ReadCommand returns a Q or P/B/E message body, control crosses into parse → analyze → rewrite → plan → execute. That call is the upper edge of this section.
  • postgres-overview-replication-ha.md — reuses this exact message framing for the streaming-replication subprotocol (walsender COPY-both, START_REPLICATION). The wire format is shared; the command vocabulary and the walsender loop live there, not here.
  • postgres-overview-base-infra.md — supplies the substrate the protocol layer is built on: StringInfo buffers (the backbone of pqformat), memory contexts, and elog/ereport error reporting that surfaces as the wire’s ErrorResponse (E) message.