Configuring HTTP Proxy for gRPC in C# Without Environment Variables

Published: (December 13, 2025 at 07:50 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for Configuring HTTP Proxy for gRPC in C# Without Environment Variables

The Challenge

You have a gRPC client in C# using Grpc.Core that needs to route traffic through an HTTP proxy. Sounds simple, right?

Not quite.

Common approaches you may have seen:

  • Set http_proxy environment variable ✅ Works, but affects all HTTP traffic
  • Use Grpc.Net.Client with HttpClientHandler.Proxy ✅ Clean, but requires a library migration
  • Set grpc.http_proxy channel option ❌ Doesn’t work in Grpc.Core

I needed per‑channel proxy configuration without affecting other traffic and without migrating libraries. So I dove into the gRPC C‑core source code to understand how http_proxy actually works.

Source Code References (gRPC v1.10.0)

FilePurpose
http_proxy.ccReads http_proxy env var, sets channel args
http_connect_handshaker.ccSends HTTP CONNECT request to proxy
secure_channel_create.ccSSL/TLS target name override handling
channel.ccDefault authority header logic
ChannelOptions.csC# channel option constants

What I Discovered

When gRPC honors the http_proxy environment variable, it simply:

  1. Parses the proxy URL
  2. Sets internal channel arguments
  3. Uses those arguments during connection

The key insight: these channel arguments are accessible via ChannelOption in C#!

The Solution

Understanding HTTP CONNECT Tunneling

HTTP proxies use the CONNECT method to create a TCP tunnel:

┌────────┐         ┌───────┐         ┌────────────┐
│ Client │   TCP   │ Proxy │   TCP   │ gRPC Server│
└───┬────┘         └───┬───┘         └─────┬──────┘
    │                  │                   │
    │ TCP Connect      │                   │
    │─────────────────►│                   │
    │                  │                   │
    │ CONNECT host:port HTTP/1.1          │
    │─────────────────►│                   │
    │                  │                   │
    │                  │ TCP Connect       │
    │                  │──────────────────►│
    │                  │                   │
    │ HTTP/1.1 200 Connection established │
    │◄─────────────────│                   │
    │                  │                   │
    │◄──────────── TCP Tunnel ───────────►│
    │         (TLS + gRPC flows here)      │
    │                  │                   │

Once the tunnel is established, the TLS handshake and gRPC communication flow transparently through it.

The Three Magic Channel Options

private Channel CreateChannel(string targetEndpoint, SslCredentials credentials, string proxyEndpoint)
{
    string proxy = proxyEndpoint?.Trim();

    if (!string.IsNullOrEmpty(proxy))
    {
        // Extract hostname without port for SSL validation
        string targetHost = targetEndpoint.Contains(":")
            ? targetEndpoint.Substring(0, targetEndpoint.LastIndexOf(':'))
            : targetEndpoint;

        var options = new[]
        {
            // 1. HTTP CONNECT tunnel target (host:port)
            new ChannelOption("grpc.http_connect_server", targetEndpoint),

            // 2. SSL SNI + certificate validation (hostname only)
            new ChannelOption(ChannelOptions.SslTargetNameOverride, targetHost),

            // 3. HTTP/2 :authority header (host:port)
            new ChannelOption(ChannelOptions.DefaultAuthority, targetEndpoint)
        };

        // Channel connects to PROXY, tunnels to TARGET
        return new Channel(proxy, credentials, options);
    }

    // Direct connection
    return new Channel(targetEndpoint, credentials);
}

What Each Option Does

1. grpc.http_connect_server

Purpose: Tells gRPC where to tunnel.
Effect: When gRPC connects to the channel target (the proxy), it sends:

CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443

Format: host:port (port is required).

2. SslTargetNameOverride

Purpose: SSL/TLS hostname for SNI and certificate validation.
Problem without it: gRPC would send SNI for the proxy hostname and validate the proxy’s certificate—both wrong.
Effect: TLS ClientHello contains the correct SNI (api.example.com) and certificate validation is performed against api.example.com.
Format: hostname (no port).

3. DefaultAuthority

Purpose: Sets the HTTP/2 :authority pseudo‑header.
Problem without it: The :authority header would be the proxy address, breaking server routing.
Effect:

:method: POST
:scheme: https
:authority: api.example.com:443   ← Correct!
:path: /mypackage.MyService/MyMethod

Format: host:port (port often required for server routing).

Complete Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│                        YOUR APPLICATION                          │
│  Channel target: proxy-server:8080                              │
│  Options:                                                        │
│    grpc.http_connect_server  = "api.example.com:443"            │
│    SslTargetNameOverride     = "api.example.com"                │
│    DefaultAuthority          = "api.example.com:443"            │
└──────────────────────────────┬──────────────────────────────────┘

          Step 1: TCP Connect  │

┌─────────────────────────────────────────────────────────────────┐
│                        HTTP PROXY                                │
│  Receives: CONNECT api.example.com:443 HTTP/1.1                 │
│  Action:   Opens TCP connection to api.example.com:443          │
│  Returns:  HTTP/1.1 200 Connection established                  │
└──────────────────────────────┬──────────────────────────────────┘

          Step 2: TCP Tunnel   │ (transparent passthrough)

┌─────────────────────────────────────────────────────────────────┐
│                     TLS HANDSHAKE                                │
│  ClientHello SNI: "api.example.com"    (SslTargetNameOverride)    │
│  Server sends certificate for: "api.example.com"                │
│  Client validates cert against: "api.example.com" ✓             │
│  [mTLS: Client certificate sent if configured]                  │
└─────────────────────────────────────────────────────────────────┘
Back to Blog

Related posts

Read more »

Async DNS

Article URL: https://flak.tedunangst.com/post/async-dns Comments URL: https://news.ycombinator.com/item?id=46245923 Points: 22 Comments: 3...