Quase todo problema de produção em sistemas distribuídos passa por rede em algum ponto. Latência subiu? Provavelmente alguma camada de rede mudou. Conexões resetando? Algum middleware está fechando antes do esperado. Certificado expirou? TLS quebrou. Resolução intermitente? DNS instável. A probabilidade de que sua próxima crise envolva rede é alta — e a diferença entre depurar em minutos ou em horas é exatamente quanto você sabe sobre o que está acontecendo embaixo das bibliotecas HTTP que você usa todo dia.
Este conceito não é um curso de redes — quem quer essa profundidade tem Computer Networks do Tanenbaum ou TCP/IP Illustrated do Stevens. O foco aqui é o vocabulário operacional que um engenheiro backend sênior precisa: como TCP estabelece e mantém conexões, o que mudou de HTTP/1.1 para HTTP/2 e HTTP/3, como funciona o handshake TLS e por que certificados expiram, e como DNS realmente resolve nomes — incluindo onde ele costuma falhar silenciosamente.
TCP — confiabilidade construída sobre IP não-confiável
A camada de rede (IP) entrega pacotes "best-effort": pacotes podem se
perder, chegar fora de ordem, ou duplicar. TCP é a camada acima que
transforma isso em fluxo confiável de bytes. As três propriedades garantidas
por TCP — entrega, ordem, sem duplicação — são o que você assume quando
escreve conn.Read(buf) em qualquer linguagem. Mas elas custam
algo, e entender o custo é entender por que TCP às vezes parece "lento" sem
motivo aparente.
O three-way handshake
Antes de qualquer dado, cliente e servidor trocam três mensagens para estabelecer a conexão:
- Cliente envia SYN (sincronize) com seu número de sequência inicial.
- Servidor responde SYN-ACK (sincronize-acknowledge) com seu número de sequência e o ACK do cliente.
- Cliente envia ACK finalizando o estabelecimento.
Esse handshake custa um round-trip completo (RTT) — tempo de ida e volta cliente-servidor. Em redes locais, é menos de 1ms; entre data centers próximos, ~10ms; transcontinental, ~150ms; satélite, ~600ms. Esse é o custo mínimo de qualquer conexão TCP nova, antes mesmo de transmitir um byte de dados. Por isso reuso de conexões (keep-alive, connection pooling) é tão importante: evita pagar handshake repetidamente.
Slow start e congestion control
TCP não envia dados na velocidade máxima imediatamente. Começa devagar, acelera enquanto detecta sucesso, e desacelera quando detecta perda. Esse é o mecanismo de congestion control — TCP cooperando com a rede compartilhada para não saturá-la. Algoritmos: Tahoe (1988), Reno, NewReno, CUBIC (default no Linux desde 2006), BBR (Google, 2016, cada vez mais adotado).
A consequência prática é que conexões novas têm throughput menor que conexões antigas. Uma conexão que existe há minutos e já "treinou" o congestion window pode transmitir muito mais por segundo que uma recém-aberta. Esse é outro motivo para reusar conexões — não só evita o handshake, evita o slow start.
Termino — FIN, RST, e o problema de TIME_WAIT
Conexões TCP terminam ordenadamente via troca de FIN, ou abruptamente via RST. O encerramento ordenado deixa o socket em estado TIME_WAIT por ~60 segundos (em Linux), durante o qual ele não pode ser reutilizado para a mesma quádrupla (IP origem, porta origem, IP destino, porta destino).
Em servidores que recebem muitas conexões curtas (proxy reverso, load
balancer), TIME_WAIT pode acumular dezenas de milhares de sockets em
estado pendente — e o servidor "esgota portas efêmeras" mesmo com tráfego
baixo. Sintomas: novas conexões falhando com "address already in use",
ss -s mostrando milhares de TIME_WAIT. Soluções: keep-alive
(evita criar tantas conexões), tuning de net.ipv4.ip_local_port_range
e net.ipv4.tcp_tw_reuse em Linux. Detalhes que entram em
runbooks de SRE.
HTTP — três versões, três modelos diferentes
HTTP/1.1 — uma requisição por vez por conexão
HTTP/1.1 (RFC 2616, 1999) é texto sobre TCP. Você abre conexão, escreve
GET /path HTTP/1.1\r\nHost: ...\r\n\r\n, lê resposta, e ou
fecha ou envia próxima requisição. A limitação fundamental: cada conexão
processa uma requisição por vez. Pipelining (enviar várias sem esperar
respostas) existia mas era pouco implementado e tinha problemas.
Para paralelismo, browsers abriam múltiplas conexões TCP em paralelo (tipicamente 6 por origem). Isso desperdiçava recursos, mas funcionava. Em sites com muitos assets (CSS, JS, imagens), era a única forma de não serializar tudo. Por anos, essa limitação moldou como front-ends eram construídos (sprite sheets, concatenação de JS, asset domains).
HTTP/2 — multiplexing sobre uma única conexão
HTTP/2 (RFC 7540, 2015) resolveu o problema fundamental: múltiplas requisições em paralelo sobre uma única conexão TCP. Bytes são organizados em "frames" e "streams"; cada requisição é um stream que pode estar sendo transmitido entrelaçado com outros. Tem também:
- Header compression (HPACK): cabeçalhos repetitivos comprimidos via tabela compartilhada.
- Server push: servidor podia enviar recursos antes de pedidos. Pouco usado, removido em rascunhos posteriores.
- Priorização de streams: cliente pode dizer "esse CSS é mais importante que essa imagem".
HTTP/2 quase sempre roda sobre TLS na prática (HTTP/2 cleartext existe mas é raro), o que casa com o desejo da indústria de mover tudo para HTTPS. Hoje é universalmente suportado: virtualmente todo browser, todo servidor moderno (nginx, Apache, IIS, Caddy), todo client HTTP.
HTTP/3 — sobre QUIC, sobre UDP
HTTP/2 ainda sofre de um problema chamado head-of-line blocking no nível TCP: se um pacote TCP se perde, todos os streams sobre aquela conexão pausam até retransmissão, mesmo que apenas um deles dependa do pacote perdido. TCP não sabe da existência de streams.
HTTP/3 (RFC 9114, 2022) resolve isso movendo para QUIC — um protocolo de transporte construído sobre UDP, com criptografia integrada (TLS 1.3 dentro do próprio protocolo) e streams independentes. Cada stream tem suas próprias garantias; perda em um não afeta outros. Em redes ruins (mobile, alta latência, alta perda), HTTP/3 ganha visivelmente. Em LAN, a diferença é negligenciável.
Adoção atual: cerca de 30% do tráfego web global é HTTP/3 (2025). Cloudflare, Google, Meta operam pesado nele. Para APIs internas em data centers, HTTP/2 ainda domina por inércia e simplicidade. Para clientes móveis ou globais, HTTP/3 vira diferencial.
TLS — criptografia em transit
TLS (Transport Layer Security) é a evolução do SSL (que tem o nome preservado em "SSL certificates" por inércia). Versão atual padrão é TLS 1.3 (RFC 8446, 2018). TLS dá três coisas: confidencialidade (terceiros não leem), integridade (terceiros não modificam sem detectar), e autenticação (você sabe que está falando com quem disse que estava falando).
O handshake TLS
TLS 1.2 levava 2 RTTs para handshake; TLS 1.3 reduziu para 1 RTT (e tem modo 0-RTT para reconexões, com trade-offs de segurança). Em alto nível, cliente e servidor:
- Negociam versão TLS, ciphers suportadas e extensões.
- Servidor envia certificado (cadeia de certificados, na verdade).
- Cliente valida o certificado: assinatura, validade, hostname, cadeia até CA confiável.
- Acordam material criptográfico via troca Diffie-Hellman.
- Trocam dados criptografados.
TLS 1.3 também eliminou ciphers obsoletas (RC4, 3DES, MD5), forçou forward secrecy, e simplificou o protocolo. Para você como engineering: TLS 1.3 é o default desejável; bloquear TLS 1.0 e 1.1 está virando padrão da indústria; TLS 1.2 ainda existe por compatibilidade.
Certificados e CAs
O modelo de certificados X.509 é hierárquico: certificados raiz de Certificate Authorities (CAs) são pré-instalados em sistemas operacionais e browsers (Mozilla CA Bundle é referência); CAs assinam certificados intermediários; intermediários assinam certificados de servidores. Quando seu navegador valida um certificado, ele segue essa cadeia até chegar a uma CA confiável.
Pontos práticos onde isso costuma falhar:
- Certificado expirado: certificados têm validade (atualmente máximo 13 meses). Esquecer de renovar derruba o serviço. Let's Encrypt automatizou isso para a maioria dos casos; ainda assim, certificados internos de empresas frequentemente são esquecidos.
- Cadeia incompleta: servidor envia só o cert dele, sem os intermediários. Browsers modernos buscam intermediários automaticamente; clients de API frequentemente não. Sintoma: navegador funciona, curl falha.
-
Hostname mismatch: certificado emitido para
app.com, acessado viawww.app.com. SAN (Subject Alternative Name) deveria cobrir os dois, mas erros de configuração acontecem. - Clock skew: relógio do servidor errado faz validação de validade falhar. NTP (Network Time Protocol) deveria manter o relógio sincronizado; quando não funciona, TLS quebra de formas confusas.
"Funciona no curl mas não no app" frequentemente é cadeia de
certificados incompleta. Use openssl s_client -connect host:443
-showcerts para inspecionar o que o servidor manda. Se faltam
intermediários, configure no servidor (em Nginx: ssl_certificate
deve incluir certificado + intermediários concatenados em PEM).
DNS — onde nomes viram IPs
DNS (Domain Name System) traduz nomes (api.example.com) em IPs
(93.184.215.123). É hierárquico (root → TLD → autoritativo) e
cacheável em múltiplas camadas. A resolução típica:
- App pede ao resolver local (libc, runtime).
- Resolver local consulta cache local (do app, do SO).
- Se miss, consulta resolver da máquina (em
/etc/resolv.conf). - Resolver vai para servidor DNS (geralmente do ISP ou público — 8.8.8.8, 1.1.1.1).
- Servidor DNS recursivo navega a hierarquia, cacheia, devolve resposta.
Cada nível tem TTL (time-to-live) próprio. Mudanças em DNS demoram a propagar — pelo TTL configurado, somado a caches em vários níveis. Quando um time muda IP de um serviço e "ainda vejo o IP antigo", quase sempre é cache em alguma camada.
Tipos de registros que importam
- A: nome → IPv4. O básico.
- AAAA: nome → IPv6.
- CNAME: nome → outro nome (alias). Resolve recursivamente.
- MX: servidores de e-mail do domínio.
- TXT: texto arbitrário (SPF, DKIM, verificações de propriedade).
- NS: servidores autoritativos do domínio.
- SRV: localização de serviços específicos (porta + host).
Onde DNS falha em produção
DNS é uma das fontes de problemas mais comuns e mais subestimadas em sistemas distribuídos. Padrões clássicos:
-
Resolução lenta: app pega 5s para iniciar porque o
primeiro DNS resolver no
/etc/resolv.confestá timing out antes de cair pro segundo. Solução:options timeout:1 attempts:1. - Cache stale após mudança: app cacheou um IP que mudou. Apps Java históricos cacheavam DNS pelo tempo de vida da JVM (!). Hoje a maioria respeita TTL, mas pools de conexão ainda podem segurar sockets para IPs velhos.
- Saturação do resolver em containers: pods em Kubernetes usam CoreDNS por default; sob alta carga, o resolver vira gargalo. ndots:5 default faz cada lookup tentar 5 sufixos; quando combinado com muitas chamadas, multiplica DNS queries por 5.
- IPv6 surpresa: AAAA records existem mas a rede não roteia IPv6; resolução demora porque tenta IPv6 primeiro, falha, cai para IPv4. Sintoma: latência de inicialização misteriosa.
Ferramentas para depurar rede
O kit mínimo de quem trabalha com sistemas distribuídos:
dig: queries DNS detalhadas.dig +tracemostra a hierarquia inteira.nslookup: alternativa antiga, ainda útil para diagnósticos rápidos.curl -v: mostra DNS, conexão, TLS handshake, headers.curl --trace-timeadiciona timestamps a cada evento.openssl s_client -connect host:443: inspeção detalhada de TLS.ss: estado de sockets locais.ss -tnpmostra TCP estabelecidos com PIDs. Substituiunetstat.tcpdump: captura pacotes. Para diagnósticos profundos quando ferramentas de alto nível não bastam.mtr: traceroute contínuo. Mostra perda em cada hop.- Wireshark: GUI para análise pesada de captura.
Aprender o uso básico dessas ferramentas economiza horas em incidentes. Quando algo está estranho na rede, alguém vai precisar usá-las — e quem sabe vira o herói; quem não sabe vira espectador.
Como cada linguagem expõe rede
// HttpClient é o padrão moderno (não use WebClient).
// Importante: HttpClient deveria ser singleton ou via IHttpClientFactory
// — instâncias novas a cada request leak sockets em TIME_WAIT.
using var http = new HttpClient();
var response = await http.GetAsync("https://api.example.com/users");
// Para baixo nível, Socket:
using var sock = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
await sock.ConnectAsync("example.com", 443);
// ... TLS via SslStream se quiser handshake manual
.NET 10 tem suporte nativo a HTTP/3 via QUIC (System.Net.Quic). IHttpClientFactory resolve o problema de sockets vazados.
# httpx é o cliente moderno (sucessor de requests, com async).
import httpx
async with httpx.AsyncClient() as client:
r = await client.get("https://api.example.com/users")
# Para baixo nível, socket stdlib
import socket, ssl
ctx = ssl.create_default_context()
with socket.create_connection(("example.com", 443)) as sock:
with ctx.wrap_socket(sock, server_hostname="example.com") as ssock:
ssock.send(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
print(ssock.recv(4096))
httpx suporta HTTP/2 nativamente (com httpx[http2]). HTTP/3 via libraries específicas (aioquic).
// stdlib net/http é production-grade. Servidor e cliente.
import "net/http"
resp, err := http.Get("https://api.example.com/users")
if err != nil { return err }
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
// Para baixo nível, package net:
conn, err := net.Dial("tcp", "example.com:443")
defer conn.Close()
// + crypto/tls.Client para TLS
Go 1.21+ tem suporte experimental a HTTP/3 via golang.org/x/net/http3.
Stdlib aceita TLS 1.3 por default. Importante: sempre defer
resp.Body.Close() — esquecer vaza connection pool.
Como praticar
-
Inspecione um handshake TLS completo. Rode
openssl s_client -connect google.com:443 -showcerts. Veja a cadeia de certificados, ciphers negociadas, versão TLS. Tente forçar TLS 1.2 com-tls1_2e veja a diferença. -
Resolva DNS na mão.
dig +trace example.commostra a navegação root → TLD → autoritativo. Veja TTLs. Compare comdig +shortque pula direto para o resultado cached. -
Compare HTTP/1.1 vs HTTP/2 vs HTTP/3. Use
curl --http1.1 -w "%{http_version}\n" -o /dev/null https://...; depois--http2, depois--http3. Compare também tempo total e número de conexões viacurl -v --trace-time.
Referências para aprofundar
- livro TCP/IP Illustrated, Vol. 1: The Protocols — W. Richard Stevens (2nd ed., Fall 2011, Wright & Stevens).
- livro Computer Networking: A Top-Down Approach (8th ed.) — Kurose & Ross (2020).
- livro High Performance Browser Networking — Ilya Grigorik (2013).
- artigo How to Read RFCs — IETF.
- artigo The QUIC Transport Protocol — Cloudflare blog.
- artigo Everything You Need To Know About TCP — Brad Fitzpatrick.
- artigo The Tangled Web of HTTPS — Julia Evans.
- docs RFC 9110 — HTTP Semantics.
- docs RFC 8446 — TLS 1.3.
- docs Mozilla SSL Configuration Generator.
- vídeo HTTP/3 Deep-Dive — Robin Marx.
- paper Congestion Avoidance and Control — Van Jacobson (1988).