Post image

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

Why not just use Wireshark?

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.

Elephantshark in action

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:

% elephantsharklistening …connected at t0 = 2025-09-18 09:19:05 +0100client -> script: "\x00\x00\x00\x08\x04\xd2\x16\x2f" = SSLRequestscript -> client: "S" = SSL supportedTLSv1.3/TLS_AES_256_GCM_SHA384 connection established with client server name via SNI: ep-aged-night-a80vx88s.eastus2.azure.neon.tech.local.neon.buildclient -> 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" = endconnecting to Postgres server: ep-aged-night-a80vx88s.eastus2.azure.neon.techscript -> server: "\x00\x00\x00\x08\x04\xd2\x16\x2f" = SSLRequestserver -> script: "S" = SSL supportedTLSv1.3/TLS_AES_256_GCM_SHA384 connection established with serverforwarding client startup message to serverscript -> 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" = endforwarding all later trafficserver -> 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 bufferclient -> 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 bufferserver -> 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 bufferclient -> 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 bufferserver -> client: "R" = Authentication "\x00\x00\x00\x36" = 54 bytes "\x00\x00\x00\x0c" = AuthenticationSASLFinal "v=ZKr8JIlFdYKw/3GVRnZ1epdKZIfMjXW2Ep3I5JsvNbQ=" = SCRAM server-final-messageserver -> client: "R" = Authentication "\x00\x00\x00\x08" = 8 bytes "\x00\x00\x00\x00" = AuthenticationOkserver -> client: "S" = ParameterStatus "\x00\x00\x00\x17" = 23 bytes "in_hot_standby\x00" = key "off\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x19" = 25 bytes "integer_datetimes\x00" = key "on\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x11" = 17 bytes "TimeZone\x00" = key "GMT\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x1b" = 27 bytes "IntervalStyle\x00" = key "postgres\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x20" = 32 bytes "search_path\x00" = key "\"$user\", public\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x15" = 21 bytes "is_superuser\x00" = key "off\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x1a" = 26 bytes "application_name\x00" = key "psql\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x26" = 38 bytes "default_transaction_read_only\x00" = key "off\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x1a" = 26 bytes "scram_iterations\x00" = key "4096\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x17" = 23 bytes "DateStyle\x00" = key "ISO, MDY\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x23" = 35 bytes "standard_conforming_strings\x00" = key "on\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x27" = 39 bytes "session_authorization\x00" = key "neondb_owner\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x19" = 25 bytes "client_encoding\x00" = key "UTF8\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x22" = 34 bytes "server_version\x00" = key "17.5 (a42a079)\x00" = valueserver -> client: "S" = ParameterStatus "\x00\x00\x00\x19" = 25 bytes "server_encoding\x00" = key "UTF8\x00" = valueserver -> client: "K" = BackendKeyData "\x00\x00\x00\x0c" = 12 bytes "\x16\xee\x00\x6a" = process ID "\xa0\x00\x89\x24" = secret keyserver -> client: "Z" = ReadyForQuery "\x00\x00\x00\x05" = 5 bytes "I" = idle^^ 514 bytes forwarded at +0.76s, 0 bytes left in bufferclient -> server: "Q" = Query "\x00\x00\x00\x12" = 18 bytes "SELECT now();\x00" = query^^ 19 bytes forwarded at +2.17s, 0 bytes left in bufferserver -> 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: textserver -> 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 valueserver -> client: "C" = CommandComplete "\x00\x00\x00\x0d" = 13 bytes "SELECT 1\x00" = command tagserver -> client: "Z" = ReadyForQuery "\x00\x00\x00\x05" = 5 bytes "I" = idle^^ 89 bytes forwarded at +2.3s, 0 bytes left in bufferclient -> server: "X" = Terminate "\x00\x00\x00\x04" = 4 bytes^^ 5 bytes forwarded at +3.7s, 0 bytes left in bufferclient hung upconnection endlistening …

Get started with Elephantshark

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.