Skip to content

HTTP/2, HTTP/3 & Transports

Key Points

  • HTTP/1.1 — text-based, head-of-line blocking, one request per connection (or pipelined). Still everywhere.
  • HTTP/2 — binary, multiplexed (many requests per connection), HPACK header compression, TLS-only in browsers. Default for HTTPS in Kestrel.
  • HTTP/3 — over QUIC (UDP-based), no TCP head-of-line blocking, faster connection establishment, better on lossy networks. Opt-in in Kestrel via HttpProtocols.Http1AndHttp2AndHttp3.
  • gRPC requires HTTP/2 (gRPC-Web works over HTTP/1.1 via a proxy translation layer).
  • ALPN (Application-Layer Protocol Negotiation) is the TLS handshake mechanism for client and server to agree on protocol version.
  • Server-Sent Events (SSE) and WebSockets are upgrade-style; both interact with HTTP versions differently (SSE works on any; WebSocket has WS/WSS over HTTP/1.1, with Extended CONNECT for HTTP/2).

Concepts (deep dive)

HTTP version comparison

Aspect HTTP/1.1 HTTP/2 HTTP/3
Transport TCP TCP QUIC (UDP)
Wire format Text Binary Binary
Multiplexing No (one req at a time per conn) Yes (streams) Yes
Head-of-line blocking Yes TCP-level only None
Header compression None HPACK QPACK
Connection setup TCP + TLS = 2-3 RTT Same 1 RTT (0-RTT possible)
Browser TLS-only? No Yes Yes

Numbers illustrative; real RTT counts depend on TLS resumption.

Kestrel protocol configuration

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(5000);          // HTTP/1.1 only
    options.ListenAnyIP(5001, lo =>
    {
        lo.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        lo.UseHttps();                  // HTTPS required for H2/H3 in browsers
    });
});

HttpProtocols flags: - Http1HTTP/1.1 - Http2HTTP/2 - Http3HTTP/3 - Http1AndHttp2, Http1AndHttp2AndHttp3

Browsers require HTTPS for HTTP/2 and HTTP/3. Plain http:// falls back to HTTP/1.1.

HTTP/3 caveats

  • HTTP/3 in Kestrel (.NET 7+) is supported but disabled by default in some configurations.
  • Linux requires kernel 5.x+ and a QUIC-capable runtime; .NET ships QUIC via MsQuic library.
  • NativeAOT/PublishTrimmed disables HTTP/3 by default in .NET 10 for size — opt back in via <Http3Support>true</Http3Support>.
  • Firewall/NAT: HTTP/3 is UDP, often blocked or misbehaving on enterprise networks. Real users may downgrade to HTTP/2.

You should always advertise HTTP/3 alongside HTTP/2:

options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;

Browser/client picks HTTP/3 when reachable; HTTP/2 when not. Kestrel sends Alt-Svc: h3=":5001" for client discovery.

When HTTP/2 matters

  • gRPC (full gRPC, not gRPC-Web). gRPC servers in Kestrel require HTTP/2.
  • High-fan-out clients opening many requests to the same origin — multiplexed streams over one connection beat 6 parallel HTTP/1.1 connections.
  • Server push — though server-push is now de-emphasized; most clients ignore it.

When HTTP/3 matters

  • Mobile clients on lossy networks — packet loss in TCP causes head-of-line blocking; QUIC isolates streams.
  • Regions where TCP middleware is poorQUIC is more resilient.
  • 0-RTT resumption — for repeat connections, no handshake roundtrip.

For typical server-to-server enterprise traffic on stable networks, HTTP/2 is plenty. HTTP/3 wins for global edge / mobile.

TLS and ALPN

ALPN (RFC 7301) is the TLS extension where client and server negotiate the application protocol. The client offers a list (h3, h2, http/1.1); server picks the first it supports. ALPN is part of the TLS handshake, not a separate roundtrip.

options.Listen(IPAddress.Any, 5001, lo =>
{
    lo.UseHttps(httpsOptions =>
    {
        httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
        // ALPN protocols are auto-set from `lo.Protocols`
    });
});

gRPC and HTTP/2

gRPC requires HTTP/2 because it uses HTTP/2 frames (DATA, HEADERS, GOAWAY, etc.) directly:

builder.Services.AddGrpc();

var app = builder.Build();
app.MapGrpcService<MyService>();

The Kestrel endpoint hosting gRPC must allow HTTP/2:

options.Listen(IPAddress.Any, 5001, lo =>
{
    lo.Protocols = HttpProtocols.Http2;   // gRPC: HTTP/2 required
    lo.UseHttps();
});

For plaintext gRPC (development), set the env var:

DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT=true

gRPC-Web (HTTP/1.1 fallback)

Browsers can't speak gRPC directly. gRPC-Web is a translation layer — it tunnels gRPC over HTTP/1.1 + Base64. Kestrel hosts it via Microsoft.AspNetCore.Grpc.Web:

builder.Services.AddGrpc();
var app = builder.Build();
app.UseGrpcWeb();
app.MapGrpcService<MyService>().EnableGrpcWeb();

See gRPC for the full gRPC story.

Server-Sent Events (SSE)

SSE is a one-way server-to-client streaming protocol over plain HTTP — works on any HTTP version:

app.MapGet("/events", async (HttpContext ctx, CancellationToken ct) =>
{
    ctx.Response.Headers.ContentType = "text/event-stream";
    ctx.Response.Headers.CacheControl = "no-cache";

    while (!ct.IsCancellationRequested)
    {
        await ctx.Response.WriteAsync($"data: {DateTime.UtcNow:o}\n\n", ct);
        await ctx.Response.Body.FlushAsync(ct);
        await Task.Delay(1000, ct);
    }
});

Browser via EventSource('/events'). Simpler than WebSockets when one-way is enough.

WebSockets

app.UseWebSockets();
app.Use(async (ctx, next) =>
{
    if (ctx.WebSockets.IsWebSocketRequest)
    {
        using var ws = await ctx.WebSockets.AcceptWebSocketAsync();
        var buffer = new byte[1024];
        while (ws.State == WebSocketState.Open)
        {
            var result = await ws.ReceiveAsync(buffer, ctx.RequestAborted);
            if (result.MessageType == WebSocketMessageType.Close) break;
            await ws.SendAsync(buffer.AsMemory(0, result.Count),
                WebSocketMessageType.Text, true, ctx.RequestAborted);
        }
    }
    else await next();
});

WebSocket upgrade is HTTP/1.1's Upgrade: websocket header. Over HTTP/2, browsers use Extended CONNECT (RFC 8441) — supported in Kestrel and modern browsers.

For most apps, SignalR (see Real-Time & RPC) abstracts the transport choice (WebSocket / SSE / long-polling). Drop down to raw WebSocket only when you have a specific protocol.


Code: correct vs wrong

❌ Wrong: assuming HTTP/2 over http://

options.ListenAnyIP(5000, lo => lo.Protocols = HttpProtocols.Http2);
// ❌ no TLS; browsers won't negotiate H2 over http://

✅ Correct: HTTPS for H2/H3

options.ListenAnyIP(5001, lo =>
{
    lo.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
    lo.UseHttps();
});

❌ Wrong: gRPC over HTTP/1.1

options.ListenAnyIP(5000, lo => lo.Protocols = HttpProtocols.Http1);
app.MapGrpcService<MyService>();   // ❌ gRPC needs HTTP/2

✅ Correct

options.ListenAnyIP(5001, lo =>
{
    lo.Protocols = HttpProtocols.Http2;
    lo.UseHttps();
});

❌ Wrong: HTTP/3 with no fallback

options.ListenAnyIP(5001, lo => lo.Protocols = HttpProtocols.Http3);
// ❌ Some clients can't reach UDP; no fallback

✅ Correct: H3 + H2 + H1.1

options.ListenAnyIP(5001, lo => lo.Protocols = HttpProtocols.Http1AndHttp2AndHttp3);

Design patterns for this topic

Pattern 1 — "All three protocols on the public endpoint"

  • Intent: clients pick the best protocol they can reach.

Pattern 2 — "HTTP/2 for gRPC; HTTP/1.1 for REST"

  • Intent: dedicated gRPC endpoint when REST + gRPC coexist.

Pattern 3 — "SSE for one-way streaming"

  • Intent: simpler than WebSockets; works through any proxy.

Pattern 4 — "Set Alt-Svc for HTTP/3 discovery"

  • Intent: Kestrel does this automatically; ensure your edge proxy doesn't strip the header.

Pros & cons / trade-offs

Protocol Pros Cons
HTTP/1.1 Universal No multiplexing
HTTP/2 Multiplexed; HPACK TCP HOL blocking
HTTP/3 No HOL; better mobile UDP blocked some networks
gRPC Strongly typed; fast HTTP/2 required
SSE Simple one-way streaming One direction only
WebSockets Bidirectional Proxy quirks; protocol up to you

When to use / when to avoid

  • Always advertise HTTP/2 for HTTPS endpoints.
  • Advertise HTTP/3 when you have mobile / global users.
  • Use gRPC for service-to-service RPC.
  • Use SSE for simple one-way streams (live updates, log tailing).
  • Use SignalR or raw WebSocket for bidirectional real-time.

Interview Q&A

Q1. Why does HTTP/2 require HTTPS in browsers? By spec, browser HTTP/2 is TLS-only (h2 over TLS). Plain HTTP/2 (h2c) works server-to-server but browsers refuse it.

Q2. What's QUIC? A UDP-based transport protocol that underlies HTTP/3. Provides streams (multiplexing without TCP HOL), built-in TLS 1.3, and 0-RTT connection resumption.

Q3. Why does gRPC need HTTP/2? gRPC frames map to HTTP/2 frames directly. Multiplexing, streaming RPC, header compression — all leveraged from HTTP/2.

Q4. What's ALPN? Application-Layer Protocol Negotiation — TLS extension where client offers a list (h3/h2/http/1.1) and server picks. Part of the handshake; no extra round-trip.

Q5. When should you enable HTTP/3? When you have global edge / mobile users on lossy networks. UDP blocked on enterprise networks may downgrade clients to HTTP/2 — that's why you advertise both.

Q6. What's HPACK / QPACK? Header compression. HPACK for HTTP/2; QPACK for HTTP/3. Both use static + dynamic dictionaries to compress repeated headers.

Q7. What's Alt-Svc? HTTP header advertising alternative endpoints. Kestrel sends Alt-Svc: h3=":5001" so clients know HTTP/3 is available next time.

Q8. What's a head-of-line blocking? A queuing problem where one slow request blocks others. HTTP/1.1 has it at request level. HTTP/2 still has TCP-level HOL (one lost packet stalls all streams). HTTP/3 (over QUIC) eliminates it.

Q9. Difference between SSE and WebSockets? SSE: server → client only, plain HTTP. WebSocket: bidirectional, custom protocol. SSE simpler when one-way is enough.

Q10. How do WebSockets work over HTTP/2? Extended CONNECT (RFC 8441) — :protocol = websocket pseudo-header. Kestrel and modern browsers support it; older proxies may not.


Gotchas / common mistakes

  • ⚠️ HTTP/2 over plain HTTP (h2c) — not supported by browsers.
  • ⚠️ gRPC on HTTP/1.1 — fails at runtime.
  • ⚠️ HTTP/3 alone — UDP blocked on many networks.
  • ⚠️ Mismatched Protocols between Kestrel and behind-the-edge — H2 client → HTTP/1.1 backend = no multiplexing benefit.
  • ⚠️ UseHttps but no server cert — runtime error.
  • ⚠️ AOT/Trim disabling HTTP/3 silently — opt back in with <Http3Support>true</Http3Support>.

Further reading