gRPC Performance: Keeping Your Connection Alive with Keep-Alive Pings
1. The Problem: Idle Connections Can Die
In the previous article, we established that you should reuse a gRPC channel — that is, keep a single open connection and reuse it across many calls. This is great as long as the connection stays alive.
But here is the problem: connections do not stay alive forever on their own. When a TCP connection sits completely idle — no data is being sent or received — various parts of the network infrastructure between the client and server may silently close it.
Real-world analogy: Imagine you rent a table at a coffee shop to work from. If you sit there for hours without ordering anything or appearing to use the space, the staff might eventually ask you to leave or give your table to someone else. The connection is the table. Idle time is sitting there doing nothing. A keep-alive ping is the equivalent of occasionally ordering a coffee just to show you are still there.
From your application's perspective, the channel object is still there — you did not dispose of it. But if the underlying TCP connection was closed by a network device in between, the very next gRPC call you make will fail or require reconnecting. Either way, you are paying the cost of a new connection.
2. Who Kills Idle Connections?
Connections are killed by many different actors along the network path:
- Load balancers — Cloud load balancers (such as Azure Load Balancer or AWS ALB) often have idle timeout settings. A common default is 4 minutes. If no data is transmitted for 4 minutes, the load balancer silently drops the connection.
- Firewalls and NAT devices — Firewalls maintain connection tracking tables. An entry that has been idle for too long gets removed, destroying the connection from the firewall's point of view even if both the client and server do not know about it.
- The server itself — The gRPC server (Kestrel in ASP.NET Core) may also have connection timeout settings.
- The operating system — The OS on both the client and server side may reclaim sockets that have been idle for a long time.
This problem is especially common in cloud-hosted applications where many layers of network infrastructure sit between services.
3. What Is a Keep-Alive Ping?
A keep-alive ping is a very small, lightweight message that the client sends to the server periodically, even when there is no real gRPC call to make. Its only purpose is to say: "I am still here. Please keep this connection open."
HTTP/2 has built-in support for ping frames at the protocol level. gRPC's .NET client takes advantage of this. You can configure how often pings are sent and how long the client waits for a response before deciding the connection is dead.
The pings are so small that they have essentially zero impact on your network bandwidth or server CPU. They typically carry just a few bytes.
Real-world analogy: A keep-alive ping is like the heartbeat monitor in a hospital. The patient (connection) is resting quietly (idle). The monitor sends a tiny electrical pulse every few seconds just to confirm the heart is still beating. If the pulse comes back, all is well. If no response arrives after a timeout, an alarm is raised.
4. The Performance Cost of Reconnecting
When a connection is lost silently and you then make a gRPC call, one of two things happens:
- The call fails with an error like
RpcException: Status(StatusCode="Unavailable", Detail="..."). Your code needs to handle this and retry, adding latency and complexity. - The gRPC client automatically reconnects before completing the call. This means all the expensive steps (TCP handshake, TLS negotiation, HTTP/2 setup) happen again, adding a penalty on the first call after the idle period.
In an application that handles high traffic during the day but experiences quiet periods at night or between bursts, these reconnection costs can noticeably degrade the user experience at the start of every busy period.
Keep-alive pings solve this by preventing the idle timeout from ever being hit in the first place. The connection is continuously kept warm and ready.
5. Configuring Keep-Alive Pings on the gRPC Client
Keep-alive pings are configured on the SocketsHttpHandler that you pass into your GrpcChannelOptions. Here is the complete configuration:
6. Settings Explained One by One
PooledConnectionIdleTimeout
This controls how long a connection can be idle in the connection pool before the pool considers it available for other purposes (or potentially closing it).
By default this is set to a short period. Setting it to Timeout.InfiniteTimeSpan tells the pool: "never automatically close this connection due to idleness". Combined with active keep-alive pings, this means the connection will stay alive as long as your application is running.
KeepAlivePingDelay
This is the interval between keep-alive pings. Every KeepAlivePingDelay seconds, the client sends a tiny HTTP/2 ping frame to the server.
Setting this to 60 seconds means a ping is sent once per minute. This is enough to satisfy most load balancers and firewalls, which usually have idle timeouts of several minutes.
You should set this value to be shorter than the shortest idle timeout in your network infrastructure. For example, if your load balancer has a 4-minute idle timeout, pinging every 60 seconds ensures it never reaches the timeout.
KeepAlivePingTimeout
After sending a ping, the client waits for the server to respond. If no response arrives within KeepAlivePingTimeout, the client assumes the connection is dead and closes it.
The default value is 20 seconds. In the example, we set it to 30 seconds to give slightly more tolerance for slow network responses.
If the timeout is triggered and the connection is closed, the client will automatically re-establish a new one on the next gRPC call. The keep-alive mechanism ensures this scenario is rare — but if it does happen, the client handles it gracefully.
7. Full Example: Channel with Keep-Alive and Multiple Connections
In a real application, you would typically combine keep-alive pings with the multiple-connection setting from the previous article. Here is a complete example of a wrapper class that configures both:
This class is meant to be registered as a singleton in ASP.NET Core's dependency injection container:
Because it is a singleton, the channel is created once when the application starts and lives for the entire application lifetime. The keep-alive pings run continuously in the background, keeping the connection warm at all times.
8. How to Verify It Is Working
You can verify that keep-alive pings are being sent by looking at your network traffic with a tool like Wireshark. However, for most developers, a simpler test is sufficient:
- Start your gRPC server and client applications.
- Make one gRPC call to confirm everything works.
- Wait for longer than your network's idle timeout (for example, 5 minutes if your load balancer has a 4-minute timeout).
- Make another gRPC call immediately after the wait.
- If keep-alive pings are working, the call succeeds instantly without any reconnection delay. If they are not configured, you may see a noticeable delay or an error on the first call after the idle period.
You can also enable gRPC diagnostic logging in .NET to see what is happening under the hood:
With trace-level logging for gRPC, you will see log entries whenever a ping is sent or a connection is established or torn down.
9. Summary
- Idle network connections can be silently killed by load balancers, firewalls, NAT devices, or the OS — even if neither your client nor your server intentionally closed them.
- When this happens, the next gRPC call must reconnect, paying the full cost of TCP + TLS + HTTP/2 setup again.
- Keep-alive pings are tiny periodic messages sent by the client to the server. They prevent idle timeouts from being triggered and keep the connection warm.
- Configure keep-alive pings via
SocketsHttpHandlerproperties passed inGrpcChannelOptions: PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan— never auto-close idle connections from the pool.KeepAlivePingDelay = TimeSpan.FromSeconds(60)— send a ping every minute.KeepAlivePingTimeout = TimeSpan.FromSeconds(30)— if no response in 30 seconds, assume the connection is dead.- Set
KeepAlivePingDelayto a value shorter than your network's idle timeout to ensure pings arrive before anything in the network path closes the connection. - Combine keep-alive settings with
EnableMultipleHttp2Connections = truein a singleton wrapper for a fully production-ready gRPC client configuration.