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: - Http1 — HTTP/1.1 - Http2 — HTTP/2 - Http3 — HTTP/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
MsQuiclibrary. - 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:
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 poor — QUIC 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:
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:
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
❌ 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
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
Protocolsbetween Kestrel and behind-the-edge — H2 client → HTTP/1.1 backend = no multiplexing benefit. - ⚠️
UseHttpsbut no server cert — runtime error. - ⚠️ AOT/Trim disabling HTTP/3 silently — opt back in with
<Http3Support>true</Http3Support>.