Elephantshark helps you monitor, understand and troubleshoot Postgres network traffic: that’s Postgres servers, clients, drivers and ORMs talking to Postgres servers, proxies and poolers.
Elephantshark sits between the two parties in a Postgres-protocol exchange, forwarding messages in both directions while parsing and logging them. It is an open-source Ruby script published by Neon and works with any and all Postgres-protocol network traffic. That includes, but isn’t limited to, traffic to and from Neon databases.
https://github.com/neondatabase-labs/elephantshark
Ordinarily Wireshark is great for this kind of thing, but using Wireshark is difficult if a connection is SSL/TLS-encrypted. SSLKEYLOGFILE
support was recently merged into libpq , but it won’t be available in a release version for some time. Plus, not all Postgres connections are made with libpq
.
To get round this problem, Elephantshark decrypts and re-encrypts a Postgres connection. It then logs and annotates the messages passing through. Or if you prefer to use Wireshark, Elephantshark can enable that too by writing keys to an SSLKEYLOGFILE
.
Run elephantshark in one terminal:
% elephantshark
listening ...
In a second terminal, connect to and query a Neon Postgres database via Elephantshark by (1) appending .local.neon.build
to the host name and (2) changing channel_binding=require
to channel_binding=disable
:
% psql 'postgresql://neondb_owner:fake_password@ep-crimson-sound-a8nnh11s.eastus2.azure.neon.tech.local.neon.build/neondb?sslmode=require&channel_binding=disable'
psql (17.5 (Homebrew))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: postgresql )
Type "help" for help.
neondb => SELECT now ();
now
-------------------------------
2025-07-02 11:51:01.721628+00
( 1 row )
neondb => \q
Back in the first terminal, see what bytes got exchanged:
% elephantshark
listening …
connected at t0 = 2025-09-18 09:19:05 +0100
client -> script: "\x00\x00\x00\x08\x04\xd2\x16\x2f" = SSLRequest
script -> client: "S" = SSL supported
TLSv1.3/TLS_AES_256_GCM_SHA384 connection established with client
server name via SNI: ep-aged-night-a80vx88s.eastus2.azure.neon.tech.local.neon.build
client -> script: "\x00\x00\x00\x56" = 86 bytes of startup message "\x00\x03\x00\x00" = protocol version
"user\x00" = key "neondb_owner\x00" = value
"database\x00" = key "neondb\x00" = value
"application_name\x00" = key "psql\x00" = value
"client_encoding\x00" = key "UTF8\x00" = value
"\x00" = end
connecting to Postgres server: ep-aged-night-a80vx88s.eastus2.azure.neon.tech
script -> server: "\x00\x00\x00\x08\x04\xd2\x16\x2f" = SSLRequest
server -> script: "S" = SSL supported
TLSv1.3/TLS_AES_256_GCM_SHA384 connection established with server
forwarding client startup message to server
script -> server: "\x00\x00\x00\x56" = 86 bytes of startup message "\x00\x03\x00\x00" = protocol version
"user\x00" = key "neondb_owner\x00" = value
"database\x00" = key "neondb\x00" = value
"application_name\x00" = key "psql\x00" = value
"client_encoding\x00" = key "UTF8\x00" = value
"\x00" = end
forwarding all later traffic
server -> client: "R" = Authentication "\x00\x00\x00\x2a" = 42 bytes "\x00\x00\x00\x0a" = AuthenticationSASL
"SCRAM-SHA-256-PLUS\x00" = SASL mechanism
"SCRAM-SHA-256\x00" = SASL mechanism
"\x00" = end
^^ 43 bytes forwarded at +0.55s, 0 bytes left in buffer
client -> server: "p" = SASLInitialResponse "\x00\x00\x00\x36" = 54 bytes
"SCRAM-SHA-256\x00" = selected mechanism
"\x00\x00\x00\x20" = 32 bytes follow
"n,,n=,r=oyCbUH3BAFTR5K7ky/6sT6sl" = SCRAM client-first-message
^^ 55 bytes forwarded at +0.55s, 0 bytes left in buffer
server -> client: "R" = Authentication "\x00\x00\x00\x5c" = 92 bytes "\x00\x00\x00\x0b" = AuthenticationSASLContinue
"r=oyCbUH3BAFTR5K7ky/6sT6slO/L2RQWlqi8k5hbEe9Ch4TW1,s=sua0GGw9khvJmqzfirvr4w==,i=4096" = SCRAM server-first-message
^^ 93 bytes forwarded at +0.65s, 0 bytes left in buffer
client -> server: "p" = SASLResponse "\x00\x00\x00\x6c" = 108 bytes
"c=biws,r=oyCbUH3BAFTR5K7ky/6sT6slO/L2RQWlqi8k5hbEe9Ch4TW1,p=F4I92rJgKR987t7tf93xdumCRuktShWrNvh6MY/rj8M=" = SCRAM client-final-message
^^ 109 bytes forwarded at +0.65s, 0 bytes left in buffer
server -> client: "R" = Authentication "\x00\x00\x00\x36" = 54 bytes "\x00\x00\x00\x0c" = AuthenticationSASLFinal
"v=ZKr8JIlFdYKw/3GVRnZ1epdKZIfMjXW2Ep3I5JsvNbQ=" = SCRAM server-final-message
server -> client: "R" = Authentication "\x00\x00\x00\x08" = 8 bytes "\x00\x00\x00\x00" = AuthenticationOk
server -> client: "S" = ParameterStatus "\x00\x00\x00\x17" = 23 bytes "in_hot_standby\x00" = key "off\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x19" = 25 bytes "integer_datetimes\x00" = key "on\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x11" = 17 bytes "TimeZone\x00" = key "GMT\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x1b" = 27 bytes "IntervalStyle\x00" = key "postgres\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x20" = 32 bytes "search_path\x00" = key "\"$user\", public\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x15" = 21 bytes "is_superuser\x00" = key "off\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x1a" = 26 bytes "application_name\x00" = key "psql\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x26" = 38 bytes "default_transaction_read_only\x00" = key "off\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x1a" = 26 bytes "scram_iterations\x00" = key "4096\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x17" = 23 bytes "DateStyle\x00" = key "ISO, MDY\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x23" = 35 bytes "standard_conforming_strings\x00" = key "on\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x27" = 39 bytes "session_authorization\x00" = key "neondb_owner\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x19" = 25 bytes "client_encoding\x00" = key "UTF8\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x22" = 34 bytes "server_version\x00" = key "17.5 (a42a079)\x00" = value
server -> client: "S" = ParameterStatus "\x00\x00\x00\x19" = 25 bytes "server_encoding\x00" = key "UTF8\x00" = value
server -> client: "K" = BackendKeyData "\x00\x00\x00\x0c" = 12 bytes "\x16\xee\x00\x6a" = process ID "\xa0\x00\x89\x24" = secret key
server -> client: "Z" = ReadyForQuery "\x00\x00\x00\x05" = 5 bytes "I" = idle
^^ 514 bytes forwarded at +0.76s, 0 bytes left in buffer
client -> server: "Q" = Query "\x00\x00\x00\x12" = 18 bytes "SELECT now();\x00" = query
^^ 19 bytes forwarded at +2.17s, 0 bytes left in buffer
server -> client: "T" = RowDescription "\x00\x00\x00\x1c" = 28 bytes "\x00\x01" = 1 columns follow
"now\x00" = column name "\x00\x00\x00\x00" = table OID: 0 "\x00\x00" = table attrib no: 0
"\x00\x00\x04\xa0" = type OID: 1184 "\x00\x08" = type length: 8 "\xff\xff\xff\xff" = type modifier: -1 "\x00\x00" = format: text
server -> client: "D" = DataRow "\x00\x00\x00\x27" = 39 bytes "\x00\x01" = 1 columns follow
"\x00\x00\x00\x1d" = 29 bytes "2025-09-18 08:19:08.270142+00" = column value
server -> client: "C" = CommandComplete "\x00\x00\x00\x0d" = 13 bytes "SELECT 1\x00" = command tag
server -> client: "Z" = ReadyForQuery "\x00\x00\x00\x05" = 5 bytes "I" = idle
^^ 89 bytes forwarded at +2.3s, 0 bytes left in buffer
client -> server: "X" = Terminate "\x00\x00\x00\x04" = 4 bytes
^^ 5 bytes forwarded at +3.7s, 0 bytes left in buffer
client hung up
connection end
listening …
To find out more and/or to install Elephantshark, check out the README on GitHub. You can also find out more about Neon , or sign up today for free.