gRPC is a high-performance RPC framework that serializes messages with Protocol Buffers and carries them over HTTP/2. To debug gRPC requests you cannot just read a URL and a JSON body the way you do with REST — the method lives in an HTTP/2 path, the payload is binary Protobuf wrapped in a length-prefixed frame, and the real outcome is reported in trailers after the response, not in the HTTP status line. The sections below cover what a gRPC call actually looks like on the wire, the practical ways to observe it, and how to inspect live gRPC traffic without standing up a proxy.
What it takes to debug gRPC requests
A gRPC call is a single HTTP/2 stream. The request opens the stream with a HEADERS frame, sends one or more messages in DATA frames, and the server replies with its own HEADERS, DATA, and a final trailing HEADERS frame. Because HTTP/2 multiplexes many streams over one TCP connection, frames from different calls are interleaved on the wire and only the stream ID ties them back together.
The request line you know from HTTP/1.1 is replaced by HTTP/2
pseudo-headers. The RPC method is the :path, formatted as
/package.Service/Method, and the content type is
application/grpc. Here is the logical, decoded view of a
unary call — the bytes are HPACK-compressed and TLS-encrypted on the
actual wire:
# HEADERS frame — request opens the stream
:method: POST
:scheme: https
:path: /routeguide.RouteGuide/GetFeature
:authority: api.example.com:443
content-type: application/grpc
grpc-accept-encoding: gzip, identity
te: trailers
# DATA frame — one length-prefixed gRPC message
# byte 0 : compression flag (0 = none, 1 = compressed)
# bytes 1..4 : message length, big-endian uint32
00 00 00 00 12 <18 bytes of Protobuf>
# HEADERS frame — response
:status: 200
content-type: application/grpc
# HEADERS frame with END_STREAM — gRPC trailers
grpc-status: 0
grpc-message: The length-prefixed message framing
Every gRPC message inside a DATA frame is wrapped in a 5-byte prefix: one
byte for the compression flag (0 for none, 1 for
compressed) followed by a big-endian 32-bit length. A single DATA frame
can contain several concatenated length-prefixed messages, and one logical
message can also span multiple frames. To read a payload you decode the
prefix, slice out exactly that many bytes, and — if the compression flag
is set — inflate it using the algorithm named in grpc-encoding
(typically gzip or deflate). Only then do you
have the raw Protobuf bytes.
Status lives in the trailers, not the HTTP status
This is the single most common source of confusion. The HTTP/2 response
almost always carries :status: 200, even when the RPC failed.
The real result is the grpc-status code, usually in the
trailing HEADERS frame, with an optional human-readable
grpc-message. Some failures return a trailers-only response,
where grpc-status appears in the only response HEADERS block
with END_STREAM. A call that returns HTTP 200 but
grpc-status: 5 is a NOT_FOUND error, not a
success. The numeric codes are fixed:
| Code | Name | Typical cause |
|---|---|---|
| 0 | OK | Success |
| 3 | INVALID_ARGUMENT | Bad request payload or field |
| 4 | DEADLINE_EXCEEDED | Call exceeded its timeout |
| 5 | NOT_FOUND | Requested entity does not exist |
| 7 | PERMISSION_DENIED | Caller lacks permission |
| 12 | UNIMPLEMENTED | Method/service not registered (often a routing or version mismatch) |
| 14 | UNAVAILABLE | Server unreachable, connection dropped, or overloaded |
| 16 | UNAUTHENTICATED | Missing or invalid credentials metadata |
Why gRPC requests are hard to debug
- The payload is binary. Protobuf has no field names on
the wire — just tag numbers and bytes — so you cannot eyeball it like
JSON without the
.protoschema. - It is encrypted. Production gRPC runs over TLS, so a plain packet capture shows only ciphertext unless you can decrypt it.
- Streams are multiplexed. One connection carries many interleaved calls; reconstructing a single request/response pair means following its stream ID.
- Status hides in trailers. Tools that only show HTTP status will report success on a failed call.
- Client tools only see their own calls. A CLI client tells you what it sends, not what your already-running service or app is actually putting on the wire.
Ways to debug gRPC requests
Each approach answers a different question. Active clients let you make a call; logging and tracing tell you what the process did; on-the-wire capture shows what actually crossed the network.
| Approach | What it shows | Needs .proto? | Sees existing app traffic? |
|---|---|---|---|
| Server logging / interceptors | Decoded messages and status from inside your code | No (you have the types) | Only services you own and can redeploy |
| grpcurl / Postman | Calls you craft, with field-level Protobuf decoding via reflection or .proto | Yes, unless server reflection is on | No — only the calls the client itself sends |
Env tracing (GRPC_TRACE, Channelz) | Internal channel/connection state and verbose library logs | No | Only processes you can configure |
| Wireshark | Raw HTTP/2 frames and gRPC messages | Yes, plus a TLS keylog file to decrypt | Yes, but with heavy setup |
| Proxy-less on-the-wire capture | Live decrypted calls reassembled per stream, with status and metadata decoded | No (raw payload bytes today) | Yes — any process on the machine |
For services you own, structured logging interceptors with correlation IDs
are the cheapest win. To probe an endpoint by hand, grpcurl is the fastest
tool — enable server reflection in development so it can introspect the
service without local .proto files:
grpcurl -d '{"latitude": 409146138, "longitude": -746188906}' \
api.example.com:443 routeguide.RouteGuide/GetFeature What none of those tools do well is answer the question "what is my running browser, desktop app, or microservice actually sending right now?" That requires capturing the real traffic, already decrypted, off the wire.
Debug gRPC requests with HTTP Debugger
HTTP Debugger is a proxy-less HTTP/HTTPS sniffer for Windows that captures traffic system-wide from any process — browsers, desktop apps, CLI tools, local servers, and microservices. Because it sets no system proxy, it sees HTTP/2 gRPC traffic next to regular HTTP/1.1 with nothing to reconfigure. It decrypts HTTPS with a local root certificate, so there is no TLS keylog file and no per-app proxy setting to manage.
- Download HTTP Debugger, install it, and trust the local root certificate so HTTPS — and TLS-encrypted gRPC — is decrypted. Capture starts automatically.
-
Run the app that makes the calls and reproduce the request. In the
main grid, use the type quick filter to narrow traffic to gRPC and
cut out unrelated HTTP, JSON, and static content.
-
Select a call to open the gRPC Messages pane. The Info tab shows the
decoded call: protocol (HTTP/2), connection and stream IDs, the RPC
as
service/method, request and response message counts, request/response metadata, and thegrpc-statusmapped to its name. Each message row carries direction, time offset, byte size, and compression flag.
-
Switch between the Info, Text, Raw, and Hex tabs to read a message.
Text and JSON payloads render directly; JSON is pretty-printed.
Binary Protobuf and compressed-but-undecodable messages are shown as
a hex dump with their byte size, and the search box filters messages
by payload content.
-
Check the decoded
grpc-statusandgrpc-messagefrom the trailers, and walk the per-message offsets for streaming calls to see message ordering and timing. This is where an HTTP-200-but-failed call becomes obvious.
Common gRPC debugging scenarios
"The HTTP response is 200 but the client reports an error"
The transport succeeded; the RPC did not. Read the grpc-status
from the trailers. A 12 UNIMPLEMENTED usually means the method
name or package in :path does not match what the server
registered — often a stale stub or a versioning mismatch. A
16 UNAUTHENTICATED points at missing or malformed credentials
in the request metadata.
"Calls intermittently fail with DEADLINE_EXCEEDED"
Use the per-message time offsets to see how long the server took before the deadline fired. If the request messages were sent quickly but the first response message arrives late, the bottleneck is server-side processing, not the client deadline being too aggressive.
"A message body is empty or garbled"
Check the compression flag and grpc-encoding. If a peer sends
compressed messages with an encoding the other side did not advertise in
grpc-accept-encoding, decoding fails. Seeing the raw framing
next to the metadata makes the mismatch immediate.
"Auth works in one client but not another"
Compare the request metadata between the two calls. gRPC credentials
travel as metadata headers (for example authorization);
capturing both calls shows exactly which header is present, missing, or
differently formatted.
Common mistakes when debugging gRPC
-
Trusting the HTTP status code instead of
grpc-status— the transport can succeed while the RPC fails. - Trying to read Protobuf as if the 5-byte length prefix were part of the message. Strip the prefix first.
- Forgetting the compression flag and trying to parse compressed bytes directly.
- Capturing TLS traffic with no decryption in place and concluding "there is nothing there" when it is simply ciphertext.
- Assuming a CLI client reproduces production exactly — it shows what the client sends, which may differ from your real app's metadata, encoding, or message order.
Limitations and complementary tools
On-the-wire capture shows you exactly what crossed the network, but it
does not own your schema. Today HTTP Debugger displays binary Protobuf as
raw bytes (hex) rather than decoding it into named fields; text and
JSON-codec payloads render directly. For field-level decoding, pair the
capture with grpcurl or Postman and your .proto files. gRPC
traffic in HTTP Debugger is read-only — request modification and
auto-reply rules apply to regular HTTP/HTTPS traffic, not gRPC streams.
For everything around the call — headers, timing, sizes, and the raw
HTTP/2 protocol exchange — the
HTTP analyzer and per-request
streaming inspection cover the rest of the
picture.
FAQ
Can I see gRPC requests in browser dev tools?
Only partially, and mainly for browser-originated gRPC-Web traffic. Browser dev tools do not decode a native gRPC client's traffic — the full HTTP/2 stream, binary Protobuf framing, and trailers. For native gRPC, use a tool that captures the client process, reassembles the stream, and decodes the framing.
How do I decode a Protobuf message without the .proto file?
You cannot fully reconstruct field names without the schema, because
Protobuf puts only tag numbers and wire types on the wire. You can
still read raw bytes and partially infer structure from the tags. For
named-field decoding you need the .proto file or server
reflection, used through a tool such as grpcurl.
Can Wireshark decode gRPC traffic?
Yes, with setup. Wireshark dissects gRPC and Protobuf, but for
TLS-encrypted traffic you must supply a TLS keylog file, mark the
ports as HTTP/2 via Decode As, and point it at your
.proto files for field decoding. It is powerful for
low-level frame analysis but heavier to configure than a capture tool
that decrypts and reassembles automatically.
Why does my gRPC call return HTTP 200 but still fail?
Because gRPC reports its result in grpc-status, not the
HTTP status. The HTTP/2 response is normally 200 regardless of the
RPC outcome. A non-zero grpc-status (for example 5
NOT_FOUND or 16 UNAUTHENTICATED) is the actual error, whether it
appears in trailers or in a trailers-only response.
Can I capture gRPC traffic without a proxy?
Yes. HTTP Debugger captures gRPC over HTTP/2 system-wide from any process on Windows without configuring a system proxy, and decrypts HTTPS using its local root certificate, so you see live calls reassembled per stream with status and metadata decoded.