PostgreSQL Client Protocol — Section Overview
Contents:
What this section covers
Section titled “What this section covers”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:
- The wire protocol itself — the length-prefixed, type-tagged message
format (
protocol.h’sPqMsg_*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 insidePostgresMain(tcop/postgres.c) that reads one message, dispatches on its first byte (Qsimple query,P/B/Eextended query,Xterminate), and loops. This is the protocol’s running phase. - 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 theClientAuthenticationdispatch inlibpq/auth.cthat runs it — with SCRAM-SHA-256 over the SASL framing (auth-sasl.c+auth-scram.c) as the modern challenge-response default. - Transport security — the optional encryption layer negotiated before
the startup packet: TLS (
be-secure.coverbe-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/Ptext into a parse tree, planning, and execution belong to query-processing (postgres-overview-query-processing.md). The boundary is the call from the loop intoexec_simple_query/exec_parse_message. - Below / sideways (into server architecture): who runs the loop — the
postmaster
fork()ing aB_BACKENDper connection,BackendInitialize, andPostgresMain’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, theSTART_REPLICATIONcommand 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
libpqclient library (src/interfaces/libpq) is the peer implementation of this protocol and is out of scope — this tree analyzes the backend. The shared contract isprotocol.h.
The handshake stack
Section titled “The handshake stack”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 singleS/Nbyte; only then does the real startup packet flow, now inside the encrypted channel. GSSAPI encryption works the same way withGSSENCRequest. PG17 added direct TLS (ALPN), which skips the request byte. This is whyProcessSSLStartupruns ahead ofProcessStartupPacketinbackend_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.haliasesPqMsg_PasswordMessage,PqMsg_SASLInitialResponse,PqMsg_SASLResponse,PqMsg_GSSResponseto the same code). The auth state machine, not the byte, distinguishes them.
Reading order
Section titled “Reading order”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:
postgres-wire-protocol.md— start here. The message format, thePqMsg_*codes,pqcomm/pqformat, and thePostgresMainloop 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.postgres-authentication.md— then the auth climb that sits between the startup packet and the loop: HBA method selection, theClientAuthenticationdispatch, and the SCRAM/SASL challenge-response exchange (the modern default), pluscrypt.cverifier handling.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.
Detail-doc summaries
Section titled “Detail-doc summaries”Forward references — these module docs are planned; summaries are predictive.
| Module doc | One-line scope |
|---|---|
postgres-wire-protocol.md | The 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.md | The 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.md | The 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). |
Adjacent sections
Section titled “Adjacent sections”postgres-overview-server-architecture.md— the immediate neighbor on both ends. It owns the postmaster fork that produces theB_BACKEND,BackendInitialize, andPostgresMain’s role in the backend lifecycle. This section borrows the forked socket from it and hands the authenticated session back toInitPostgresthere. The boundary is “before vs. inside the message loop.”postgres-overview-query-processing.md— the section the message loop feeds. OnceReadCommandreturns aQorP/B/Emessage 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 (walsenderCOPY-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:StringInfobuffers (the backbone ofpqformat), memory contexts, andelog/ereporterror reporting that surfaces as the wire’sErrorResponse(E) message.