HTTP/3 e o protocolo QUIC na prática: a próxima geração do transporte web
De HTTP/1.1 a HTTP/3: a evolução do transporte web
Os protocolos de transporte web passaram por três grandes mudanças geracionais, cada uma resolvendo os pontos críticos de seu antecessor:
HTTP/1.1: Onde tudo começou
HTTP/1.1 domina a web desde sua padronização em 1997, com os seguintes problemas centrais:
- Bloqueio Head-of-Line: Em uma única conexão TCP, as requisições seguintes precisam esperar que a anterior seja concluída
- Alto custo de conexão: Os navegadores limitam 6 conexões simultâneas por domínio, cada uma exigindo handshake TCP de 3 vias + handshake TLS
- Cabeçalhos redundantes: Cada requisição carrega os cabeçalhos completos sem compactação
HTTP/2: A promessa e a decepção do multiplexação
HTTP/2 (2015) introduziu o multiplexing, permitindo streams paralelos sobre uma única conexão TCP:
- ✅ Resolveu o bloqueio head-of-line de camada de aplicação
- ❌ O HOL blocking de camada TCP persiste — um único pacote perdido bloqueia todos os streams
- ❌ Conexões TCP não podem migrar; trocas de rede (WiFi→4G) quebram as conexões
- ❌ Os handshakes TLS 1.2/1.3 ainda exigem RTTs extras
HTTP/3: A revolução QUIC
HTTP/3 substitui o TCP por QUIC (sobre UDP), resolvendo fundamentalmente os problemas acima:
| Recurso | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Transporte | TCP | TCP | QUIC (UDP) |
| Bloqueio Head-of-Line | App + Transporte | Transporte | ❌ Nenhum |
| Estabelecimento de conexão | TCP 3-RTT + TLS 1-2RTT | TCP 1-RTT + TLS 1-RTT | QUIC 0-1RTT |
| Migração de conexão | ❌ | ❌ | ✅ Connection ID |
| Controle de fluxo | Nível de conexão | Conexão + Stream | Conexão + Stream |
| Controle de congestionamento | Kernel TCP | Kernel TCP | Personalizável em userspace |
💡 Use a ferramenta Códigos de status HTTP para consultar rapidamente o significado dos códigos de status do protocolo.
Os internos do protocolo QUIC: um mergulho profundo
QUIC (Quick UDP Internet Connections) é um protocolo de transporte projetado pelo Google e padronizado pela IETF. Ele roda sobre UDP, reimplementando toda a funcionalidade do TCP em userspace e a superando significativamente.
1. Identificadores de conexão (Connection ID)
Conexões TCP são identificadas por uma 4-tupla: (src_ip, src_port, dst_ip, dst_port). Qualquer alteração em um elemento cria uma nova conexão. O QUIC introduz o Connection ID (CID):
TCP: Connection = (192.168.1.5:52000, 10.0.0.1:443)
→ Troca de WiFi muda IP → Conexão quebra ❌
QUIC: Connection = CID: 0x8293a1f4b7c2d5e6
→ Troca de WiFi muda IP → Conexão continua ✅ (migração)
- DCID (Destination CID): Identifica o receptor, estável a longo prazo
- SCID (Source CID): Identifica o emissor, negociável
- Comprimento CID: Variável, 0-20 bytes, 8 bytes por padrão
2. Estabelecimento de conexão 0-RTT
O QUIC funde o handshake de transporte e criptográfico em uma única etapa:
# Traditional TCP + TLS 1.3 (first connection)
Client → Server: TCP SYN # 1-RTT
Server → Client: TCP SYN-ACK # 1-RTT
Client → Server: TCP ACK + TLS ClientHello # 1-RTT
Server → Client: TLS ServerHello + Finished # 1-RTT
Client → Server: TLS Finished + HTTP Request # 1-RTT
# Total: 4-RTT (TCP 3-way + TLS 2 round trips)
# QUIC first connection (1-RTT)
Client → Server: QUIC Initial + TLS ClientHello # includes transport params
Server → Client: QUIC Handshake + TLS ServerHello # includes transport params + NewSessionTicket
Client → Server: QUIC Protected + HTTP Request
# Total: 1-RTT
# QUIC resumed connection (0-RTT)
Client → Server: QUIC Initial + TLS EarlyData + HTTP Request # data sent immediately!
Server → Client: QUIC Handshake + HTTP Response
# Total: 0-RTT (data travels alongside handshake)
3. Sem bloqueio Head-of-Line
Cada stream QUIC é ordenado de forma independente, mas os streams não se bloqueiam mutuamente:
HTTP/2 over TCP:
Stream 1: ████░░░░ ← Packet lost! All streams wait for retransmission
Stream 2: ....waiting....
Stream 3: ....waiting....
HTTP/3 over QUIC:
Stream 1: ████░░░░ ← Packet lost! Only this stream waits
Stream 2: ████████ ← Normal transmission ✅
Stream 3: ████████ ← Normal transmission ✅
4. Migração de conexão na prática
# Scenario: Phone switches from WiFi to 5G
# 1. Current connection state
# WiFi: 192.168.1.5:52000 → 10.0.0.1:443
# CID: 0x8293a1f4b7c2d5e6
# 2. WiFi disconnects, 5G connects
# 5G: 100.64.0.8:38000 → 10.0.0.1:443
# CID: 0x8293a1f4b7c2d5e6 ← CID unchanged!
# 3. Client sends packets with the same CID from new path
# Server recognizes CID → maps to original connection → seamless continuation
5. Tipos de quadro QUIC
| Tipo de quadro | Propósito | Descrição |
|---|---|---|
| STREAM | Dados de aplicação | Carrega ID de stream e offset, controle de fluxo por stream |
| ACK | Confirmação | Suporta reconhecimento seletivo (SACK) |
| CRYPTO | Handshake cripto | Carrega dados do handshake TLS |
| NEW_CONNECTION_ID | Atualização CID | Validação de caminho e migração |
| PATH_CHALLENGE/RESPONSE | Validação de caminho | Verifica a alcançabilidade do novo caminho |
| CONNECTION_CLOSE | Fechar conexão | Inclui código de erro e motivo |
| MAX_DATA/MAX_STREAM_DATA | Atualização de controle de fluxo | Ajusta dinamicamente as janelas de fluxo |
| PING/PONG | Keepalive | Sondagem de vitalidade da conexão |
HTTP/3 vs HTTP/2: comparação detalhada
Comparação por camada de protocolo
| Dimensão | HTTP/2 | HTTP/3 |
|---|---|---|
| Transporte | TCP | QUIC (UDP) |
| Criptografia | Opcional (texto claro h2c) | TLS 1.3 obrigatório |
| Formato de quadro | Prefixo de comprimento fixo | Codificação de comprimento variável (Varint) |
| Compressão de cabeçalho | HPACK (tabela estática/dinâmica) | QPACK (reconhecimento assíncrono de tabela) |
| Esquema de ID de stream | Par (cliente) / Ímpar (servidor) | Iniciado por cliente: 0,4,8... / Servidor: 1,5,9... |
| Priorização | Peso + árvore de dependências | Prioridades incrementais RFC 9218 |
| Server push | PUSH_PROMISE | Obsoleto (substituído por WebTransport) |
Comparação por cenário de desempenho
| Cenário | HTTP/2 | HTTP/3 | Melhora |
|---|---|---|---|
| Primeira conexão | 2-3 RTT | 1 RTT | 50-67% |
| Conexão retomada | 1-2 RTT | 0 RTT | 100% |
| 0,1% perda de pacotes | Throughput -30% | Throughput -5% | Significativa |
| 1% perda de pacotes | Throughput -70% | Throughput -15% | Muito significativa |
| Troca de rede | Conexão quebra, reconectar | Migração transparente | Qualitativa |
| Enlace de alta latência | Acumulação de múltiplos RTT | RTT mínimo | Noteável |
| Muitos streams concorrentes | Janela de congestionamento compartilhada | Controle de fluxo independente | Mais justo |
💡 Use a ferramenta Codificação Base64 para lidar com dados binários na depuração de protocolo.
Habilitar HTTP/3 no Nginx
Configuração do Nginx 1.25+ (suporte nativo QUIC)
# nginx.conf - Main configuration
worker_processes auto;
events {
worker_connections 1024;
}
http {
# Global HTTP/3 settings
quic_retry on; # Enable QUIC retry (anti-address spoofing)
quic_active_connection_id_limit 4; # Max active CIDs
server {
listen 443 quic reuseport; # QUIC listener (UDP 443)
listen 443 ssl; # TCP/TLS fallback
http2 on; # Also support HTTP/2
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
# TLS 1.3 is mandatory for HTTP/3
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers on;
# Alt-Svc header: inform clients about HTTP/3 support
add_header Alt-Svc 'h3=":443"; ma=86400';
# 0-RTT anti-replay protection
ssl_early_data on;
location / {
proxy_pass http://backend;
proxy_set_header Early-Data $ssl_early_data;
}
}
}
Verificar HTTP/3
# Check Nginx version and modules
nginx -V 2>&1 | grep -o 'with-http_v3_module'
# Test HTTP/3 with curl
curl --http3 -I https://example.com
# Check Alt-Svc header
curl -I https://example.com | grep -i alt-svc
# Listen on UDP 443
ss -ulnp | grep :443
# Check QUIC connection stats
curl -s http://localhost:8080/status | jq '.quic'
Habilitar HTTP/3 no Caddy
O Caddy suporta HTTP/3 pronto para uso, sem configuração extra:
# Caddyfile
example.com {
# Caddy enables HTTP/3 by default
# No explicit declaration needed, auto-negotiation
# For explicit control
protocols h1 h2 h3
# TLS config (Caddy auto-manages certificates)
tls {
protocols tls1.3
}
reverse_proxy localhost:8080
}
# Multi-site configuration
api.example.com {
protocols h2 h3
reverse_proxy localhost:3000
}
# Start Caddy (auto-listens on UDP 443)
caddy run --config Caddyfile
# Verify
curl --http3 -I https://example.com
# Check Caddy supported protocols
caddy version
# Should show a version with HTTP/3 support
Habilitar HTTP/3 na Cloudflare
A Cloudflare, o maior implantador de HTTP/3 do mundo, oferece a ativação com um clique:
# Enable HTTP/3 via Cloudflare API
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/http3" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{"value":"on"}'
# Also enable 0-RTT
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/0rtt" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{"value":"on"}'
Pontos de configuração de HTTP/3 na Cloudflare
- O plano gratuito suporta HTTP/3 (ative no painel)
- Auto Alt-Svc: A Cloudflare adiciona automaticamente cabeçalhos
Alt-Svcpara guiar a atualização dos clientes - Protocolo de origem: Cloudflare → origem usa por padrão HTTP/1.1/2; configure HTTP/3 de origem separadamente
- Limitações de 0-RTT: Seguro apenas para requisições idempotentes (GET/HEAD); use POST com cautela
Desenvolvimento QUIC em Go com quic-go
Instalação e conexão básica
# Install quic-go
go get github.com/quic-go/quic-go
Servidor QUIC
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"github.com/quic-go/quic-go"
)
func main() {
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{loadCert()},
NextProtos: []string{"h3", "h3-29"},
}
listener, err := quic.ListenAddr(
"0.0.0.0:443",
tlsConfig,
&quic.Config{
MaxIdleTimeout: 30 * time.Second,
MaxIncomingStreams: 100,
Allow0RTT: true,
EnableDatagrams: false,
KeepAlivePeriod: 10 * time.Second,
},
)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
fmt.Println("QUIC server listening on :443")
for {
sess, err := listener.Accept(context.Background())
if err != nil {
log.Printf("Accept error: %v", err)
continue
}
go handleSession(sess)
}
}
func handleSession(sess quic.Connection) {
for {
stream, err := sess.AcceptStream(context.Background())
if err != nil {
log.Printf("Stream error: %v", err)
return
}
go handleStream(stream)
}
}
func handleStream(stream quic.Stream) {
buf := make([]byte, 4096)
n, err := stream.Read(buf)
if err != nil {
return
}
fmt.Printf("Received: %s\n", buf[:n])
stream.Write([]byte("Hello from QUIC!"))
stream.Close()
}
Cliente QUIC (com 0-RTT)
package main
import (
"context"
"crypto/tls"
"fmt"
"time"
"github.com/quic-go/quic-go"
)
func main() {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h3"},
}
// First connection (1-RTT)
sess, err := quic.DialAddr(
context.Background(),
"localhost:443",
tlsConfig,
&quic.Config{Allow0RTT: true},
)
if err != nil {
fmt.Printf("Dial error: %v\n", err)
return
}
// Save session ticket for 0-RTT
sessionTicket := sess.ConnectionState().TLS.SessionTicket
// Open bidirectional stream
stream, err := sess.OpenStreamSync(context.Background())
if err != nil {
fmt.Printf("Stream error: %v\n", err)
return
}
stream.Write([]byte("Hello QUIC!"))
buf := make([]byte, 4096)
n, _ := stream.Read(buf)
fmt.Printf("Response: %s\n", buf[:n])
// 0-RTT resumed connection
sess2, err := quic.DialAddrEarly(
context.Background(),
"localhost:443",
&tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h3"},
SessionTickets: []tls.SessionTicket{sessionTicket},
},
&quic.Config{Allow0RTT: true},
)
if err != nil {
fmt.Printf("0-RTT dial error: %v\n", err)
return
}
fmt.Println("0-RTT connection established!")
// Send data immediately, no need to wait for handshake
earlyStream, _ := sess2.OpenStreamSync(context.Background())
earlyStream.Write([]byte("Early data via 0-RTT!"))
}
Detecção de migração de conexão
func monitorConnectionMigration(sess quic.Connection) {
localAddr := sess.LocalAddr()
remoteAddr := sess.RemoteAddr()
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
currentLocal := sess.LocalAddr()
if !addrsEqual(localAddr, currentLocal) {
fmt.Printf("Connection migration detected!\n")
fmt.Printf(" Old address: %s\n", localAddr)
fmt.Printf(" New address: %s\n", currentLocal)
fmt.Printf(" CID: %x\n", sess.ConnectionID())
localAddr = currentLocal
}
}
}
Depurar conexões HTTP/3
Usando curl
# Basic HTTP/3 request (requires curl 7.88+ compiled with ngtcp2/quiche)
curl --http3 https://example.com
# Detailed connection info
curl --http3 -v https://example.com 2>&1 | grep -E "QUIC|HTTP/3"
# Headers only
curl --http3 -I https://example.com
# Specify max idle timeout
curl --http3 --max-idle-time 30000 https://example.com
# Send 0-RTT data
curl --http3-early-data https://example.com/api/data
# Test connection migration (continue after NIC switch)
curl --http3 --connect-timeout 5 https://example.com/large-file -o /dev/null
Depuração com Chrome DevTools
- Abrir DevTools → painel Rede
- Clic com botão direito nos cabeçalhos de coluna → marcar Protocolo
- A coluna Protocolo mostra
h3ouh3-29 - chrome://net-internals/#quic para detalhes da sessão QUIC
# Chrome launch flags to force QUIC
chrome --enable-quic --origin-to-force-quic-on=example.com:443
# View QUIC statistics
# Visit chrome://net-internals/#quic
Captura de pacotes com Wireshark
# Capture UDP port 443 traffic
tshark -i eth0 -f "udp port 443" -w quic_capture.pcap
# Filter QUIC Initial packets
tshark -r quic_capture.pcap -Y "quic.header.form==0"
# Filter by specific CID
tshark -r quic_capture.pcap -Y "quic.dcid==8293a1f4b7c2d5e6"
# View QUIC handshake process
tshark -r quic_capture.pcap -Y "quic" -T fields \
-e frame.number -e quic.header.form -e quic.packet_type \
-e quic.dcid -e quic.scid
Considerações de segurança do 0-RTT
0-RTT oferece desempenho extremo, mas introduz riscos de segurança:
Risco de ataque de replay
Attack scenario:
1. Attacker intercepts client 0-RTT request (with Early Data)
2. Replays the request to the server at a later time
3. Server may execute the same operation twice (e.g., transfer, order)
Contramedidas de segurança
| Risco | Contramedida | Implementação |
|---|---|---|
| Ataque de replay | Janela anti-replay | O servidor registra hashes recentes de ClientHello, rejeita duplicatas |
| Requisições não idempotentes | Limitar métodos Early Data | Permitir apenas GET/HEAD com 0-RTT |
| Vazamento de dados | Nenhum dado sensível no 0-RTT | Filtragem em camada de aplicação |
| Ataque de downgrade | Assinatura anti-downgrade TLS 1.3 | O servidor embute sinal anti-downgrade no ServerHello |
Configuração de segurança 0-RTT no Nginx
server {
listen 443 quic reuseport;
listen 443 ssl;
server_name api.example.com;
ssl_early_data on;
location / {
# Only allow safe requests with 0-RTT
if ($request_method !~ ^(GET|HEAD)$ ) {
return 425; # Too Early
}
proxy_pass http://backend;
proxy_set_header Early-Data $ssl_early_data;
}
location /api/ {
# Disable 0-RTT for API requests
ssl_early_data off;
proxy_pass http://backend;
}
}
Benchmarks de desempenho
Ambiente de teste
# Install test tools
go install github.com/quic-go/quic-go@latest
pip install h2load nghttp2
# Test topology
# Client (us-west) → CDN → Origin (ap-southeast)
# Baseline RTT: 180ms
Comparação de latência
| Métrica | HTTP/1.1 | HTTP/2 | HTTP/3 | Notas |
|---|---|---|---|---|
| Primeira conexão | 720ms | 360ms | 180ms | 4/2/1 RTT |
| Conexão retomada | 360ms | 180ms | 0ms | 0-RTT |
| 100 requisições TTFB | 1800ms | 360ms | 180ms | Multiplexing + sem HOL |
| Primeira requisição após 503 | 720ms | 360ms | 180ms | Reconstrução de conexão |
Comparação de throughput (várias taxas de perda de pacotes)
# h2load benchmark HTTP/2
h2load -n 100000 -c 100 -m 100 https://example.com
# h2load benchmark HTTP/3 (requires nghttp2 support)
h2load -n 100000 -c 100 -m 100 --h3 https://example.com
| Perda de pacotes | HTTP/2 req/s | HTTP/3 req/s | Melhora |
|---|---|---|---|
| 0% | 45,200 | 43,800 | -3% (sobrecarga UDP) |
| 0,1% | 31,640 | 41,610 | +31% |
| 0,5% | 18,080 | 35,040 | +94% |
| 1% | 13,560 | 30,660 | +126% |
| 2% | 9,040 | 24,280 | +169% |
| 5% | 4,520 | 15,330 | +239% |
💡 Taxas de perda de pacotes mais altas amplificam a vantagem do HTTP/3. Em redes móveis (perda 1-3%), o HTTP/3 pode melhorar o throughput em mais de 100%.
Guia de migração: de HTTP/2 para HTTP/3
Lista de verificação de migração
- Suporte TLS 1.3: HTTP/3 exige TLS 1.3; verifique compatibilidade de certificado e configuração
- Porta UDP 443: Firewall/grupos de segurança devem permitir UDP 443
- Cabeçalho Alt-Svc: Informe os clientes sobre o suporte a HTTP/3
- Mecanismo de fallback: Mantenha HTTP/2 como caminho de downgrade
- Monitoramento: Coleta de métricas QUIC/HTTP/3
Passos de migração progressiva
# Step 1: Open UDP 443 on firewall
# iptables example
- iptables -A INPUT -p udp --dport 443 -j ACCEPT
# Step 2: Nginx config supporting both h2 + h3
# listen 443 ssl; ← HTTP/2 (TCP)
# listen 443 quic; ← HTTP/3 (UDP)
# Step 3: Add Alt-Svc header
# add_header Alt-Svc 'h3=":443"; ma=86400';
# Step 4: Monitor QUIC connection ratio
# Gradually observe client migration percentage
Problemas comuns de migração
| Problema | Causa | Solução |
|---|---|---|
| UDP bloqueado | ISP/firewall bloqueia UDP | Fallback para HTTP/2, negociação gradual |
| Falha de sondagem MTU | ICMP filtrado | Definir MTU inicial menor (1200) |
| Falha de migração de conexão | Timeout de validação de caminho | Aumentar o timeout de PATH_CHALLENGE |
| 0-RTT rejeitado | Janela anti-replay muito pequena | Ajustar cache de replay do servidor |
| Alto uso de CPU | Crypto em userspace QUIC | AES/AES-GCM acelerado por hardware |
FAQ
P1: O HTTP/3 substituirá completamente o HTTP/2?
Não a curto prazo. HTTP/3 e HTTP/2 coexistirão por muito tempo:
- HTTP/3 exige suporte UDP; algumas redes ainda bloqueiam UDP
- HTTP/2 tem vantagens em redes internas de baixa perda e baixa latência
- Os navegadores negociam automaticamente via Alt-Svc, de forma transparente para o usuário
P2: O QUIC será limitado pela QoS da ISP por usar UDP?
Existe risco, mas a tendência está melhorando:
- Cloudflare, Google e Mozilla estão pressionando ISPs a reconhecer o tráfego QUIC
- A migração de conexão e a criptografia do QUIC dificultam a identificação DPI tradicional
- Testes mostram que as grandes ISPs estão relaxando gradualmente os limites de taxa UDP 443
P3: O HTTP/3 consome mais CPU que o HTTP/2?
Sim. O QUIC implementa controle de congestionamento e criptografia em userspace, aumentando a sobrecarga de CPU em ~10-20%. Soluções:
- Usar hardware com suporte AES-NI
- Ativar aceleração TLS por hardware (ex. QAT)
- Otimizar o batching em bibliotecas como quic-go/lsquic
P4: Como confirmar que um cliente está usando HTTP/3?
# Method 1: curl check
curl -sI --http3 https://example.com | head -1
# HTTP/3 200
# Method 2: Chrome DevTools → Network → Protocol column shows h3
# Method 3: Server logs
# Nginx: $protocol variable returns "HTTP/3"
# Caddy: logs show "h3"
P5: O 0-RTT é adequado para todos os cenários?
Não. O 0-RTT é adequado apenas para requisições idempotentes (GET/HEAD) que não contêm dados sensíveis. Para operações de modificação como POST/PUT, desative o 0-RTT para evitar ataques de replay.
P6: A migração de conexão QUIC afeta o WebSocket?
WebSocket sobre HTTP/3 (WebTransport) suporta nativamente a migração de conexão. A conexão WebSocket não é interrompida durante trocas de rede — uma grande vantagem em relação ao WebSocket TCP tradicional.
Resumo e perspectivas
HTTP/3 e QUIC representam o futuro do transporte web:
- Estabelecimento de conexão: 0-RTT elimina a latência de handshake, melhora em mais de 50% o first-paint
- Confiabilidade de transporte: Sem HOL blocking, melhora de throughput em mais de 100% sob perda de pacotes
- Experiência móvel: A migração de conexão elimina desconexões por troca de rede
- Evolutividade de protocolo: QUIC em userspace permite upgrades independentes de algoritmos de congestionamento
Com Nginx, Caddy e Cloudflare dando suporte completo a HTTP/3, e SDKs maduros como quic-go disponíveis, agora é o melhor momento para adotar HTTP/3.
💡 Use a ferramenta Hash e criptografia para verificar fingerprints de certificados e a integridade do ticket de sessão durante os handshakes QUIC.
Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →