31 min read

Debug gRPC Streaming Tunnels with grpcurl

Test local and tunneled gRPC calls with grpcurl to isolate HTTP/2, reflection, TLS, authority, timeout, streaming, and trailer failures.

Local and tunneled grpcurl paths reaching the same gRPC service through diagnostic checkpoints.
Comparing the local baseline with the tunneled call helps isolate failures introduced along the public path.
Developer Guides · gRPC Tunnel Debugging · Localtonet · 2026

Isolate local service, HTTP/2, TLS, streaming, and tunnel failures one layer at a time

A gRPC endpoint can accept a connection yet still fail during reflection, method invocation, streaming, or final status delivery. In this guide, we use grpcurl to establish a working local baseline and then repeat equivalent calls through a Localtonet public endpoint. This comparison helps determine whether a failure originates in the application, protobuf descriptors, TLS negotiation, HTTP/2 routing, request authority, deadlines, stream handling, or the remote path. Because transport behavior can vary by service and tunnel configuration, we evaluate HTTP/s, TLS, and raw TCP options instead of making a blanket compatibility claim.

🔒 Diagnose TLS and certificate validation safely 🌐 Compare local and tunneled gRPC calls ⚡ Test unary and streaming RPCs with grpcurl

Why gRPC tunnel debugging is different

Testing an ordinary web endpoint often starts with a browser or a basic HTTP request. Native gRPC requires a more specialized diagnostic approach. Messages are encoded using Protocol Buffers, methods are identified by a service and method path, calls use HTTP/2 semantics, and the final gRPC result is commonly communicated through trailers. A successful TCP connection therefore proves only that something accepted the connection. It does not prove that the complete gRPC exchange worked.

grpcurl fills the role that curl usually serves for JSON and web APIs. It can discover service descriptors through gRPC server reflection, read descriptors from local .proto or protoset files, convert JSON input into protobuf messages, invoke methods, and display decoded responses. It supports unary, client-streaming, server-streaming, and bidirectional-streaming RPCs.

The most reliable tunnel diagnostic method is comparative. First, make a known-good call directly to the service on the machine where it runs. Record the address, transport mode, service name, request body, metadata, output, timing, and final status. Next, start the selected Localtonet tunnel and repeat the same logical call against its public URL or host and port. Change only the values that must differ, such as the destination, TLS mode, or public authority.

This sequence creates a useful decision boundary:

  • If the local call fails, repair the service, reflection configuration, descriptors, credentials, or request before investigating the tunnel.
  • If the local call succeeds but the remote connection cannot be established, investigate the tunnel lifecycle, target address, target port, relay selection, and client connectivity.
  • If a remote connection succeeds but the RPC fails, investigate TLS, HTTP/2 negotiation, authority-based routing, metadata, deadlines, streaming behavior, and trailers.
  • If unary calls work but streaming calls fail, focus on stream duration, buffering, intermediate timeouts, input completion, cancellation, and final status delivery.
A reachable port is not the same as a successful gRPC call

TCP connectivity, TLS negotiation, HTTP/2 session establishment, gRPC method routing, protobuf processing, and trailer delivery are separate checkpoints. Preserve the exact error from each checkpoint instead of reducing every failure to “the tunnel is down.”

The protocol details that affect tunneled gRPC

Protocol layers for gRPC over a tunnel, including HTTP/2, TLS, authority, reflection, and trailers.
A tunneled gRPC call depends on HTTP/2 behavior, endpoint identity, service discovery, and final status trailers.

gRPC uses HTTP/2 as its transport for native calls. HTTP/2 multiplexes streams over a connection and carries headers, framed message data, and trailers. Unlike a typical request that returns one body and ends, a gRPC method may keep its HTTP/2 stream active while multiple protobuf messages move in one or both directions.

📨 Unary RPC The client sends one request message and receives one response message. This is usually the best first method for validating descriptors, routing, metadata, and basic transport behavior.
📡 Server-streaming RPC The client sends one request and the server returns multiple messages over the same RPC. The connection remains active until the server completes the stream or an error or cancellation ends it.
📤 Client-streaming RPC The client sends multiple request messages and then closes its sending side. The server ordinarily produces its response after processing the incoming stream.
🔄 Bidirectional-streaming RPC Both sides can send multiple messages. Their message timing does not have to alternate one for one, which makes interactive testing and timeout diagnosis especially important.
🏷️ HTTP/2 authority The request authority identifies the intended host. Certificate validation and virtual-host routing can depend on host-related values even when the underlying connection reaches the correct IP address.
Final trailers A gRPC call normally reports its final status using trailers such as grpc-status and, when applicable, grpc-message. Losing trailers can turn an otherwise complete response into an apparent protocol failure.

Why HTTP trailers matter

A response message is not always the complete result of a gRPC call. The server can send one or more messages and then finish with a non-success gRPC status. A client must receive and interpret the final trailers to know whether the operation completed successfully.

This matters most during streaming tests. You may see several valid response messages before the stream fails. If the final trailers are missing, malformed, or not propagated, grpcurl may report an unexpected end of stream or another transport-level error instead of the application status the server intended to return.

Plaintext does not mean HTTP/1.1

In grpcurl, -plaintext means that the connection does not use TLS. Native gRPC still requires HTTP/2 semantics, commonly using cleartext HTTP/2 when TLS is absent. It should not be interpreted as a switch that converts a native gRPC service into an HTTP/1.1 API. A component that accepts only HTTP/1.1 traffic cannot carry native gRPC merely because grpcurl was started with -plaintext.

Reflection is discovery, not authorization

Server reflection lets tools discover services, methods, and message descriptors at runtime. It does not bypass authentication or authorization. A protected RPC still requires the expected credentials and metadata. Reflection can also reveal an API inventory, so many deployments limit it to development environments or require authorization.

If reflection is unavailable, grpcurl can use local .proto files or a compiled descriptor set. This distinction is important because a failed list operation does not necessarily mean the application methods are unreachable. It may mean only that reflection is disabled.

Prerequisites and grpcurl installation

Before creating a public endpoint, collect enough information to test the service locally. Do not guess the port, TLS mode, service name, or credentials. Obtain them from the service configuration, startup output, deployment manifest, or application owner.

Information to collect

  • The local IP address or hostname on which the gRPC server listens.
  • The actual listening port.
  • Whether the local endpoint uses TLS or cleartext HTTP/2.
  • The fully qualified service and method names, such as package.Service/Method.
  • A valid JSON request for at least one small unary method.
  • Whether server reflection is enabled.
  • If reflection is disabled, the required .proto files, import directories, or protoset file.
  • Any required authorization or application metadata.
  • The expected response and normal completion behavior.
  • For streaming methods, the number and shape of input messages and the condition that ends the stream.

You also need the Localtonet client installed and running on a device that can reach the service. The service does not have to listen on the same loopback interface if the selected client device can reach it over the local network, but the target address must be reachable from that device.

Install grpcurl

Use an installation path supported by the official grpcurl project. The available version can vary by package manager, so confirm the installed build with its help output before relying on a particular option.

On macOS with Homebrew:

brew install grpcurl

On a system with Snap:

snap install grpcurl

If a supported Go SDK is already installed:

go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

The Go installation places the executable in the Go binary directory. If the shell cannot find it, add that directory to PATH or invoke the binary by its full path. Prebuilt binaries are also available from the official grpcurl release page. Windows users can download the appropriate release executable and place it in a directory included in PATH.

Confirm that the command is available:

grpcurl -help

grpcurl also has an official container image. Containerized testing introduces extra networking and file-mount considerations, however. On macOS and Windows, a container normally cannot reach a service on the host through the container’s own localhost; the documented host name for that scenario is host.docker.internal. On Linux, host networking can be used where appropriate. Proto files must be mounted into the container, and interactive or standard-input streaming requires Docker input handling.

docker pull ghcr.io/fullstorydev/grpcurl:latest
docker run ghcr.io/fullstorydev/grpcurl:latest api.grpc.me:443 list
Do not place real credentials in saved examples

Commands containing bearer tokens, client keys, private certificate paths, or sensitive request data can be retained in shell history, process listings, terminal logs, and CI output. Use your organization’s approved secret-handling process and redact diagnostic captures before sharing them.

Build a known-good local gRPC baseline

Replace every placeholder in the following examples with values from your service. The example address localhost:50051 is illustrative, not a Localtonet default and not a universal gRPC port.

1. Determine whether the local endpoint uses TLS

Use -plaintext only when the local server is intentionally configured without TLS:

grpcurl -plaintext localhost:50051 list

For a TLS endpoint whose certificate chains to a CA trusted by the local machine, omit -plaintext:

grpcurl localhost:50051 list

A protocol mismatch often fails before service discovery. If a cleartext call is sent to a TLS listener, or a TLS connection is attempted against a cleartext listener, changing reflection settings will not solve the problem. Confirm the listener’s transport configuration first.

2. Discover or supply the service schema

When reflection is enabled, list available services:

grpcurl -plaintext localhost:50051 list

Describe a service or method:

grpcurl -plaintext localhost:50051 describe example.v1.ExampleService
grpcurl -plaintext localhost:50051 describe example.v1.ExampleService.GetItem

Use the TLS form instead if the local listener uses TLS. If reflection is disabled, supply the service definition explicitly:

grpcurl \
  -plaintext \
  -import-path ./protos \
  -proto example/v1/service.proto \
  localhost:50051 \
  example.v1.ExampleService/GetItem

Proto imports must resolve from the provided import paths. A missing imported file is a local descriptor problem, not a tunnel failure. A compiled protoset can also be used when that is the artifact maintained by your project.

3. Invoke a small unary method

Start with a deterministic method that accepts a small request and returns quickly. Put all grpcurl options before the server address and method name:

grpcurl \
  -plaintext \
  -d '{"id":"test-item"}' \
  localhost:50051 \
  example.v1.ExampleService/GetItem

Save the exact command, response, exit status, and elapsed time. If the call requires metadata, add only the headers documented by the application:

grpcurl \
  -plaintext \
  -H 'authorization: Bearer <REDACTED>' \
  -d '{"id":"test-item"}' \
  localhost:50051 \
  example.v1.ExampleService/GetItem

Do not treat an authentication failure as proof that the network path is broken. An Unauthenticated or PermissionDenied status generally shows that the request reached a gRPC-aware endpoint capable of returning an application-level status.

4. Capture useful diagnostic output

grpcurl provides verbose modes in supported versions. Check grpcurl -help for the exact options available in your installed build, then repeat the known-good call with verbose output. Record:

  • Whether the connection used TLS or plaintext.
  • The authority or host value.
  • The service and method name.
  • Request and response metadata, with secrets removed.
  • The number of response messages.
  • The final gRPC status and message.
  • Whether the command completed normally, timed out, or was cancelled.
Do not proceed without a local success case

A tunnel cannot repair an incorrect method name, invalid JSON, missing protobuf imports, disabled reflection, rejected credentials, or a server that is not listening. Establish at least one successful local RPC before introducing the public path.

Choose a Localtonet tunnel based on transport requirements

Our platform supports HTTP/s, TCP, UDP, TLS, combined UDP/TCP, File Server, proxy, and VPN configurations. For a native gRPC diagnostic, the relevant candidates are generally HTTP/s, TLS, or raw TCP. UDP, File Server, and proxy-target workflows address different use cases.

The supplied Localtonet product information does not establish that every HTTP tunnel configuration preserves native gRPC behavior end to end. It would therefore be inaccurate to say that any HTTP tunnel will automatically carry every unary and streaming gRPC implementation. Select and validate the tunnel against the actual service transport, including HTTP/2, TLS, authority routing, stream duration, and trailers.

Candidate When to evaluate it What the test must prove
HTTP/s tunnel The service is intended to be reached through an HTTP-aware public endpoint. Confirm native HTTP/2 negotiation, correct authority routing, streaming behavior, and final trailer delivery for the selected configuration.
TLS tunnel The service and deployment design call for the TLS tunnel family available in the current dashboard. Confirm where TLS is expected, which certificate name grpcurl validates, and whether the public-to-local flow matches the service’s TLS expectations.
Raw TCP tunnel You need a public host and port mapped to the gRPC listener as a raw port service. Confirm that the client’s TLS or plaintext mode matches the server and that the complete HTTP/2 and gRPC exchange succeeds through the byte stream.

Raw TCP is often a useful diagnostic candidate because gRPC traffic is carried over a TCP connection. That general networking fact does not eliminate application-level requirements. The public certificate name, TLS termination design, local TLS listener, virtual-host logic, and authorization policy must still be correct.

HTTP/s configurations can be appropriate only when the complete path supports the service’s native gRPC requirements. A successful browser response or HTTP/1.1 health page on the same host does not establish native gRPC compatibility. Test the actual RPC with grpcurl.

Use the current dashboard instead of hardcoding a relay or feature assumption

Available relay servers, regions, protocols, options, and plan capabilities can vary. Select values shown for your account in the current Localtonet dashboard. Do not copy an old server code or assume that every tunnel type is included in every subscription.

Configure and start the Localtonet tunnel

The following sequence follows our documented common tunnel workflow. Exact fields can vary by the tunnel family and current dashboard. We do not invent a gRPC-specific dashboard mode where one has not been established. Instead, create the appropriate supported HTTP/s, TLS, or TCP configuration and validate it with the tests in this guide.

1

Install and run the Localtonet client

Run our client on the device that hosts the gRPC service or can reach it over the local network. The client establishes an outbound connection, so inbound router port forwarding, a public IP address, firewall changes, and VPN setup are not required for the normal tunnel workflow.

2

Authenticate or select the client device

Use the device-specific authentication token through the supported client and dashboard workflow. Treat the token as a secret. Never place it in grpcurl commands, screenshots, source control, issue reports, or article examples.

3

Select an available relay server or region

Choose from the values currently available in the dashboard. Do not hardcode a server code from another account or an older guide because availability can change by account, plan, client version, region, or deployment.

4

Create the appropriate tunnel configuration

Select HTTP/s, TLS, or TCP according to the transport decision you made above. Point the local target to the verified IP address and port that produced the successful local grpcurl call. If the client runs on another device, do not use 127.0.0.1 unless that loopback address refers to the device actually hosting the service.

5

Start the tunnel

Creating a tunnel does not mean it is running. Use the Start button and confirm that the selected Localtonet client remains connected. The public endpoint is available only while that client is connected and the tunnel is running.

6

Record the assigned public endpoint

For an HTTP/s configuration, use the assigned public URL or hostname as shown. For a raw port configuration, record both the assigned public host and port. Preserve the exact hostname because TLS validation and HTTP/2 authority routing can depend on it.

The general product documentation is available in the Localtonet documentation. Consult the current page for your selected tunnel family before relying on any account-specific option that is not described here.

Repeat the baseline against the public endpoint

Side-by-side grpcurl results for a local service and its public tunneled endpoint.
Repeating the same call against both targets reveals whether behavior changes after the tunnel is introduced.

Run the remote test from a genuinely external network or device when possible. Testing from the same host is still useful, but an external client better represents the public path and avoids local DNS, loopback, or firewall conditions that remote users will not share.

Start with connectivity and reflection

For a public TLS endpoint, replace the placeholder with the exact assigned host and port:

grpcurl public.example.invalid:443 list

The domain above is intentionally nonfunctional. Use the endpoint assigned to your running tunnel. Do not append https:// when grpcurl expects a host:port target.

For a public raw TCP endpoint that carries a cleartext local gRPC service, the corresponding diagnostic form is:

grpcurl -plaintext public.example.invalid:12345 list

Use that form only if the actual end-to-end design expects plaintext at the grpcurl connection. Do not add -plaintext simply to suppress a certificate error. That changes the transport rather than fixing certificate validation.

Repeat the same unary call

grpcurl \
  -d '{"id":"test-item"}' \
  public.example.invalid:443 \
  example.v1.ExampleService/GetItem

Keep the method, JSON body, and application metadata identical to the local baseline. If the service behavior depends on a host or authority value, compare the local and public routing requirements carefully.

Test certificate trust without disabling it permanently

If a service uses a private CA, grpcurl can be given the appropriate CA certificate:

grpcurl \
  -cacert ./certificates/ca.crt \
  -d '{"id":"test-item"}' \
  public.example.invalid:443 \
  example.v1.ExampleService/GetItem

For mutual TLS, grpcurl supports a client certificate and key in addition to a CA certificate:

grpcurl \
  -cacert ./certificates/ca.crt \
  -cert ./certificates/client.crt \
  -key ./certificates/client.key \
  -d '{"id":"test-item"}' \
  public.example.invalid:443 \
  example.v1.ExampleService/GetItem

grpcurl also supports -insecure for diagnostic situations where TLS certificate verification must temporarily be disabled. This does not mean the connection is plaintext, but it prevents normal certificate validation and can hide the precise defect you need to repair.

grpcurl \
  -insecure \
  -d '{"id":"test-item"}' \
  public.example.invalid:443 \
  example.v1.ExampleService/GetItem
Use -insecure only as a short diagnostic comparison

If the call works only with certificate verification disabled, the network path may be operational while certificate trust or hostname validation is incorrect. Install the correct CA, connect with the intended public hostname, and repair the certificate configuration instead of leaving verification disabled.

Separate connection destination, authority, and TLS server name

These values are related but not always interchangeable:

  • The connection destination tells grpcurl which host and port to contact.
  • The HTTP/2 authority identifies the intended virtual host to the receiving HTTP stack.
  • The TLS server name is used during TLS negotiation and certificate hostname validation.

grpcurl versions expose options for authority and TLS server-name handling. Check grpcurl -help in your installed version before using them, because exact option availability must match the binary being run. If the public hostname works but connecting by IP does not, that can be expected when the certificate and routing configuration are hostname-based.

Avoid replacing the assigned hostname with a relay IP merely to test reachability. Doing so can alter DNS behavior, certificate validation, server-name indication, and HTTP/2 authority at the same time. That creates a different test rather than isolating one variable.

Test each streaming pattern deliberately

Unary, server-streaming, client-streaming, and bidirectional gRPC message flows.
Each gRPC streaming pattern exercises connection lifetime, message direction, deadlines, and completion differently.

After a unary method succeeds through the public endpoint, test the actual streaming mode used by the application. A unary success proves important basics, but it does not prove that a long-lived stream, multiple request messages, half-close behavior, or final trailers will work.

Server-streaming test

A server-streaming method receives one request and prints each response message as it arrives:

grpcurl \
  -d '{"topic":"diagnostic"}' \
  public.example.invalid:443 \
  example.v1.StreamService/Watch

Determine how the method is supposed to finish. Some server streams send a finite set of messages and complete. Others remain active until the client cancels, a deadline expires, or an application event ends the stream. Do not label a continuously running watch method as hung merely because it has not returned to the shell.

Compare the timing of the first response, spacing between messages, total number of messages, and final status with the local call. If messages arrive locally in real time but appear remotely only in a batch, investigate buffering along the public path or in the application stack.

Client-streaming test

For a client-streaming method, -d @ tells grpcurl to read request messages from standard input. Multiple JSON objects can be supplied as a stream:

printf '%s\n' \
  '{"value":"first"}' \
  '{"value":"second"}' \
  '{"value":"third"}' |
grpcurl \
  -d @ \
  public.example.invalid:443 \
  example.v1.StreamService/Upload

End-of-file is significant. It tells grpcurl that no more request messages will be sent. A server that waits for the client to finish its sending side may not produce a final response until standard input closes. When testing interactively, follow the terminal’s normal end-of-file action after entering the final complete JSON message.

Bidirectional-streaming test

grpcurl can operate a bidirectional method interactively by reading JSON messages from standard input while printing server responses:

grpcurl \
  -d @ \
  public.example.invalid:443 \
  example.v1.StreamService/Chat

Enter request objects that conform to the method’s input descriptor. Bidirectional streams do not necessarily alternate request and response messages. The server may respond immediately, wait for several inputs, send asynchronous events, or wait for the client’s sending side to close. Know the method contract before deciding that silence indicates failure.

Use controlled input for repeatability

Interactive testing is valuable for observing live behavior, but a fixed input stream is easier to compare. Save only non-sensitive test messages and feed the same sequence to the local and public endpoints. Compare:

  • How quickly the connection is established.
  • Whether the first message reaches the server.
  • Whether each expected response is displayed immediately.
  • Whether client end-of-file is recognized.
  • Whether the server closes normally.
  • Whether grpcurl receives a final success or error status.

Handle deadlines as an explicit test variable

grpcurl versions provide controls for maximum execution time and connection timing. Confirm the exact flags with grpcurl -help. A short client deadline can cancel a healthy stream, while no practical upper bound can leave an automated diagnostic waiting indefinitely.

Run one test with the application’s realistic deadline and another with a deliberately generous diagnostic deadline. If only the longer test succeeds, determine whether the delay occurs during connection establishment, service processing, first-message delivery, an idle period, or stream completion. Do not solve the issue by raising every timeout without identifying which layer is expiring.

Troubleshoot gRPC tunnel failures by symptom

Decision tree linking common grpcurl tunnel symptoms to targeted protocol and configuration checks.
Grouping failures by symptom narrows the investigation to the local service, tunnel, TLS identity, HTTP/2 behavior, or stream lifecycle.
Observed symptom Likely diagnostic area Next check
Connection refused locally Service process, listener address, or port Confirm the process is running and verify its actual bind address and port.
Local TLS handshake fails TLS mode, trust chain, or hostname Confirm whether the listener expects TLS and inspect the certificate configuration.
Local list fails but a method works with proto files Reflection Treat reflection as disabled or restricted and continue with verified descriptors.
Local call succeeds, public connection is refused or times out Tunnel lifecycle or target configuration Confirm that the selected client is connected, the tunnel is started, and the public host and port are exact.
Public TLS fails unless -insecure is used Certificate trust or hostname validation Use the assigned hostname and configure the correct trusted CA instead of disabling validation.
Public endpoint returns an HTTP response that is not gRPC Wrong listener, protocol path, or HTTP routing Verify that the endpoint carries native gRPC over HTTP/2 and targets the gRPC port rather than a web or health port.
Unimplemented or unknown service Service path, package name, deployment version, or authority routing Compare the fully qualified method and public virtual-host routing with the local baseline.
Unauthenticated or PermissionDenied Application authorization Pass the required credentials securely and verify their audience, scope, and expiry.
Unary works but a stream ends early Deadline, idle timeout, cancellation, or stream handling Measure when it ends and compare that interval with client, server, and intermediate timeout settings.
Messages arrive but completion fails Trailers or final status propagation Use verbose output and compare the final local and remote gRPC status.
Client-streaming call never returns Input stream not closed or server waiting for more messages Send valid complete JSON objects and terminate standard input with end-of-file.

Reflection fails only through the public endpoint

First verify that reflection succeeds locally using the same transport mode. Then try invoking a known method remotely with local proto files. If the method succeeds while remote reflection fails, the data path can carry at least that RPC and the problem is narrower than complete gRPC failure.

Also confirm whether the reflection service is intentionally exposed in that environment. Reflection may be restricted even when application methods remain available. Do not weaken production access controls merely to make grpcurl list convenient.

The public endpoint negotiates TLS but not the expected protocol

A completed TLS handshake does not establish that native gRPC can proceed. The connection still needs the HTTP/2 behavior expected by the client and server. If grpcurl reports a response that resembles an ordinary web page, a redirect, or an HTTP status without a valid gRPC content type, verify that you did not target a web frontend, management port, health page, or incompatible HTTP route.

The certificate is valid for a hostname, but the test uses an IP

Connect using the assigned hostname whenever that is the intended public identity. An IP-based test can fail hostname validation even when the certificate is otherwise valid. It can also bypass the host value required for virtual routing. If DNS resolution itself is under investigation, change only that layer and preserve the expected TLS server name and authority using options supported by your grpcurl version.

The method works locally but returns Unimplemented remotely

Verify the exact package, service, and method path. Then determine whether the public request is reaching the same application instance and version. In an HTTP-aware path, authority or route selection may direct the call to a different backend. A valid gRPC Unimplemented response is useful evidence because it differs from an inability to establish HTTP/2.

Only long-lived streams fail

Measure the failure interval. A stream that consistently ends after the same idle or total duration often points toward a deadline or timeout rather than random packet loss. Check the grpcurl maximum time, application deadline propagation, server timeout, and any intermediate idle limit supported by the actual configuration.

Add a test method or application logging only through your normal development process. Avoid changing several timeout layers simultaneously. First determine whether traffic stops while the stream remains open, the client cancels the RPC, the server closes it, or the final trailers disappear.

Responses are buffered

Compare a server-streaming method locally and publicly using timestamped application logs. If the server emits messages periodically but grpcurl receives them together, identify the layer accumulating data. Ensure the test method actually flushes or emits each gRPC message according to its framework’s normal streaming behavior before attributing the delay to the tunnel.

The client-streaming method waits forever

Confirm that each input object is valid JSON for the protobuf descriptor and that the input stream reaches end-of-file. A server can legitimately wait because it has not received the client half-close. Test with a fixed pipeline so the shell closes its output predictably.

Messages arrive, followed by an unexpected end of stream

Inspect verbose grpcurl output and server logs for the final status. The application may have failed after sending one or more valid messages. If the local call displays a specific gRPC status but the public call reports only an unexpected transport termination, investigate final trailer propagation and connection closure.

The tunnel exists but is unavailable

Check lifecycle state before changing gRPC settings. In our platform, creating a tunnel does not start it. The selected client must be connected and the tunnel must be running. A tunnel stops being available when its client disconnects or the tunnel is stopped. Also confirm that the local target is reachable from the client device, not merely from a different workstation.

Operate a remotely reachable gRPC endpoint safely

A successful diagnostic creates a publicly reachable path to the selected local service. Treat that change as an exposure decision, not just a connectivity test.

  • Require application authentication and authorization for non-public RPCs.
  • Apply least privilege to test identities and access tokens.
  • Keep reflection restricted when exposing the API inventory is unnecessary.
  • Use certificate verification and a trusted CA for normal TLS operation.
  • Protect mutual TLS private keys and never upload them to issue trackers.
  • Avoid testing destructive methods against real data.
  • Redact request metadata, personal data, credentials, and private endpoint details from logs.
  • Set deliberate deadlines so abandoned tests do not remain active indefinitely.
  • Stop or delete a tunnel when remote access is no longer required.

Localtonet removes the need for inbound router port forwarding, firewall changes, VPN setup, or a public IP address in the normal tunnel workflow because our client establishes an outbound connection to a relay. That convenience does not replace service-level authorization. The exposed gRPC application remains responsible for deciding who may invoke each method.

Do not expose a development reflection endpoint casually

Reflection can disclose service names, methods, and message schemas. If remote reflection is needed for a controlled test, apply the same security review used for the application endpoint and remove unnecessary exposure after diagnosis.

A repeatable local-to-public diagnostic checklist

  1. Confirm the service process is running and identify its real listener address and port.
  2. Determine whether the local listener expects TLS or cleartext HTTP/2.
  3. Install grpcurl and inspect the options in the installed version.
  4. List or describe the service through reflection, or provide verified descriptors.
  5. Complete one small unary RPC locally.
  6. Record method, body, metadata, transport mode, timing, response, and final status.
  7. Test the required streaming method locally and document how it normally completes.
  8. Choose HTTP/s, TLS, or TCP based on the actual transport design.
  9. Run the Localtonet client on a device that can reach the service.
  10. Select the device token and an available relay in the current dashboard.
  11. Configure the verified local IP and port, then start the tunnel.
  12. Record the assigned public hostname and port exactly.
  13. Repeat reflection or descriptor-based discovery remotely.
  14. Repeat the same unary RPC without changing unrelated variables.
  15. Compare TLS trust, authority, metadata, response, and final status.
  16. Test server, client, or bidirectional streaming as required.
  17. Investigate timeouts and trailers if unary succeeds but streaming fails.
  18. Stop or delete the tunnel when the remote test is complete.

Frequently asked questions

Can I test a gRPC service with regular curl?

Regular curl is not a practical substitute for grpcurl when invoking native protobuf-based gRPC methods. grpcurl understands service descriptors, converts JSON input into protobuf messages, invokes gRPC methods, supports streaming, and displays decoded responses and gRPC statuses.

Does grpcurl require server reflection?

No. Reflection is the most convenient way to discover services and descriptors at runtime, but grpcurl can also use local .proto files or compiled protoset files. A failed reflection request does not prove that application RPCs are unavailable.

Does -plaintext make grpcurl use HTTP/1.1?

No. In grpcurl, -plaintext means the connection does not use TLS. Native gRPC still depends on HTTP/2 semantics. The option does not convert a native gRPC service into an HTTP/1.1 endpoint.

Which Localtonet tunnel type should I use for gRPC?

Evaluate HTTP/s, TLS, or raw TCP according to the service’s real transport requirements. The available evidence does not support a blanket claim that every HTTP tunnel configuration carries every native gRPC workload. Test HTTP/2 negotiation, TLS, authority routing, streaming, and final trailers with the actual public endpoint.

Why does the local grpcurl call work while the public call fails?

The difference can be caused by an inactive tunnel, an incorrect local target, TLS trust, certificate hostname validation, authority-based routing, metadata changes, HTTP/2 incompatibility, deadlines, buffering, or missing trailers. Keep the method and request constant, then compare each transport layer in sequence.

Why does a streaming command keep running?

The method may be designed to remain active. A server-streaming watch can wait for future events, while a client-streaming or bidirectional method may wait for more standard input. Review the method contract, close input with end-of-file when appropriate, and use a deliberate diagnostic deadline.

Is it safe to leave grpcurl -insecure enabled?

It should be used only as a temporary diagnostic comparison. It disables certificate verification and can conceal an incorrect hostname or trust chain. Normal operation should use the intended public hostname and a certificate chain trusted by the client.

Does creating a Localtonet tunnel make it immediately available?

No. The tunnel must be started, and its selected client device must remain connected. It is available only while the client is connected and the tunnel is running. You can later stop or delete it when remote access is no longer needed.

Test your local gRPC service through a controlled public endpoint

Establish a successful grpcurl baseline, select the Localtonet tunnel family that matches your transport, and compare the local and public RPC one layer at a time.

Get Started Free →

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support