gRPC With. Net Performance Created: 26 Mar 2026 Updated: 26 Mar 2026

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:

  1. 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.
  2. 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.
  3. The server itself — The gRPC server (Kestrel in ASP.NET Core) may also have connection timeout settings.
  4. 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:

  1. The call fails with an error like RpcException: Status(StatusCode="Unavailable", Detail="..."). Your code needs to handle this and retry, adding latency and complexity.
  2. 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:

using System;
using System.Net.Http;
using Grpc.Net.Client;

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
// How long a pooled connection can be idle before it is eligible for reuse check
PooledConnectionIdleTimeout = System.Threading.Timeout.InfiniteTimeSpan,

// How often a keep-alive ping is sent to the server
KeepAlivePingDelay = TimeSpan.FromSeconds(60),

// How long to wait for a response to the ping before closing the connection
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),

// Also allow new connections when concurrency limit is reached
EnableMultipleHttp2Connections = true,
}
});

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.

PooledConnectionIdleTimeout = System.Threading.Timeout.InfiniteTimeSpan

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.

KeepAlivePingDelay = TimeSpan.FromSeconds(60)

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.

KeepAlivePingTimeout = TimeSpan.FromSeconds(30)

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:

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Performance;

namespace ApiGateway
{
internal class GrpcPerformanceClient : IGrpcPerformanceClient, IDisposable
{
private readonly GrpcChannel channel;

public GrpcPerformanceClient(string serverUrl)
{
channel = GrpcChannel.ForAddress(serverUrl, new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
// Keep the pooled connection alive indefinitely
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,

// Send a ping every minute to keep the connection fresh
KeepAlivePingDelay = TimeSpan.FromSeconds(60),

// Close the connection if server does not respond to ping within 30s
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),

// Allow extra connections when concurrency limit is reached
EnableMultipleHttp2Connections = true,
}
});
}

public async Task<ResponseModel.PerformanceStatusModel> GetPerformanceStatus(string clientName)
{
var client = new Monitor.MonitorClient(channel);
var response = await client.GetPerformanceAsync(new PerformanceStatusRequest
{
ClientName = clientName
});

return new ResponseModel.PerformanceStatusModel
{
CpuPercentageUsage = response.CpuPercentageUsage,
MemoryUsage = response.MemoryUsage,
ProcessesRunning = response.ProcessesRunning,
ActiveConnections = response.ActiveConnections
};
}

public void Dispose()
{
channel.Dispose();
}
}
}

This class is meant to be registered as a singleton in ASP.NET Core's dependency injection container:

// Program.cs
builder.Services.AddSingleton<IGrpcPerformanceClient>(provider =>
new GrpcPerformanceClient(builder.Configuration["ServerUrl"]));

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:

  1. Start your gRPC server and client applications.
  2. Make one gRPC call to confirm everything works.
  3. Wait for longer than your network's idle timeout (for example, 5 minutes if your load balancer has a 4-minute timeout).
  4. Make another gRPC call immediately after the wait.
  5. 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:

// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Grpc": "Trace"
}
}
}

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

  1. 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.
  2. When this happens, the next gRPC call must reconnect, paying the full cost of TCP + TLS + HTTP/2 setup again.
  3. 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.
  4. Configure keep-alive pings via SocketsHttpHandler properties passed in GrpcChannelOptions:
  5. PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan — never auto-close idle connections from the pool.
  6. KeepAlivePingDelay = TimeSpan.FromSeconds(60) — send a ping every minute.
  7. KeepAlivePingTimeout = TimeSpan.FromSeconds(30) — if no response in 30 seconds, assume the connection is dead.
  8. Set KeepAlivePingDelay to a value shorter than your network's idle timeout to ensure pings arrive before anything in the network path closes the connection.
  9. Combine keep-alive settings with EnableMultipleHttp2Connections = true in a singleton wrapper for a fully production-ready gRPC client configuration.
Share this lesson: