Networking & Web — The Complete Guide
Protocols and architectural patterns every backend and networking engineer should master: HTTP, TCP, gRPC, TLS, DNS, and WebSocket.
A deep, practical reference covering the protocols and architectural patterns every backend/networking engineer should master: HTTP/1.1, HTTP/2, HTTP/3, TCP/IP, TLS, DNS, WebSocket, gRPC, REST, Reverse Proxy, Load Balancer, and Connection Pooling.
Table of Contents
- TCP/IP
- DNS
- TLS
- HTTP/1.1
- HTTP/2
- HTTP/3
- REST
- WebSocket
- gRPC
- Reverse Proxy
- Load Balancer
- Connection Pooling
- Cross-Cutting Best Practices
1. TCP/IP
1.1 Overview
TCP/IP is the foundational protocol suite of the internet, organized in four conceptual layers (vs OSI’s seven):
| Layer | Examples | Responsibility |
|---|---|---|
| Application | HTTP, DNS, gRPC | App-level data exchange |
| Transport | TCP, UDP | End-to-end delivery, ports |
| Internet | IP, ICMP | Routing, addressing |
| Link | Ethernet, Wi-Fi | Physical/frame delivery |
1.2 The TCP Three-Way Handshake
Client Server
| ---- SYN (seq=x) ----> |
| <-- SYN-ACK (seq=y, |
| ack=x+1) --------- |
| ---- ACK (ack=y+1) --> |
| connection open |
- SYN: client proposes an initial sequence number.
- SYN-ACK: server acknowledges and proposes its own.
- ACK: client acknowledges; connection is now
ESTABLISHED. - Connection teardown uses a 4-way FIN/ACK exchange (or RST for abrupt close).
1.3 Key TCP Mechanisms
- Reliability: sequence numbers + acknowledgments + retransmission timers (RTO).
- Flow control: receive window (
rwnd) prevents overwhelming the receiver. - Congestion control:
cwndgrows via slow start → congestion avoidance; algorithms include Reno, CUBIC (Linux default), BBR (used by Google, increasingly common). - Nagle’s algorithm: batches small writes to avoid tiny packets; interacts badly with
TCP_NODELAY-sensitive latency-critical apps (disable Nagle for real-time traffic). - Delayed ACK: receiver waits briefly before ACKing, can combine with Nagle to cause ~40ms stalls — a classic latency bug.
1.4 TCP vs UDP
| TCP | UDP | |
|---|---|---|
| Connection | Connection-oriented | Connectionless |
| Reliability | Guaranteed, ordered | Best-effort |
| Overhead | Higher (headers, handshake) | Minimal |
| Use cases | HTTP, gRPC, databases | DNS queries, video streaming, QUIC/HTTP3, gaming |
1.5 Best Practices
- Tune
net.ipv4.tcp_congestion_controltobbrfor high-latency/high-bandwidth links (video, CDNs). - Increase
somaxconnand backlog for high-concurrency servers. - Use
SO_REUSEPORTto let multiple processes/threads accept on the same port (scales accept-loop across cores). - Monitor for TIME_WAIT exhaustion on servers making many short-lived outbound connections; use connection pooling (see §12) instead of reconnecting per request.
- Enable TCP keepalive for long-lived idle connections to detect dead peers (NAT timeouts, crashed hosts).
1.6 Common Pitfalls
- Ignoring MTU/MSS mismatches causing fragmentation or blackholed packets (Path MTU Discovery issues).
- Not handling half-open connections (peer crashed without FIN) — mitigate with keepalive + application-level heartbeats.
- Assuming TCP send() = data delivered — it only means buffered locally.
2. DNS
2.1 Overview
DNS (Domain Name System) is a hierarchical, distributed naming system translating human-readable domains into IP addresses (and other records).
2.2 Resolution Flow
Client → Recursive Resolver (ISP/8.8.8.8)
→ Root Server (.)
→ TLD Server (.com)
→ Authoritative Server (example.com)
→ Answer cached & returned
2.3 Record Types
| Record | Purpose |
|---|---|
| A | Hostname → IPv4 |
| AAAA | Hostname → IPv6 |
| CNAME | Alias to another hostname |
| MX | Mail exchange servers |
| TXT | Arbitrary text (SPF, DKIM, verification) |
| NS | Authoritative name servers |
| SOA | Zone authority info |
| SRV | Service location (host+port), used by gRPC/Kubernetes |
| PTR | Reverse lookup (IP → hostname) |
2.4 Caching & TTL
- Every record has a TTL (seconds) controlling how long resolvers cache it.
- Low TTL (e.g., 60s) → faster failover, more DNS load. High TTL (e.g., 24h) → less load, slower propagation.
- Negative caching: NXDOMAIN responses are cached too (per SOA minimum TTL).
2.5 Modern Patterns
- DNS-based load balancing: multiple A records, round-robin or geo/latency-based (GeoDNS).
- Anycast DNS: same IP announced from multiple locations via BGP; routers deliver to the topologically nearest instance — used by root servers and CDNs.
- DNSSEC: cryptographically signs records to prevent spoofing/cache poisoning.
- DoH / DoT (DNS over HTTPS/TLS): encrypts DNS queries for privacy, bypassing plaintext UDP:53 interception.
- Split-horizon DNS: different answers for internal vs external clients (common in enterprise/VPC setups).
2.6 Best Practices
- Keep TTLs low before planned infrastructure migrations, raise afterward.
- Use health-checked DNS failover for disaster recovery, but remember client-side caching means failover isn’t instant.
- Avoid deep CNAME chains (extra round trips, some resolvers cap chain depth).
- Monitor for DNS as a single point of failure — use multiple authoritative providers if uptime is critical.
2.7 Common Pitfalls
- Forgetting that not all clients respect TTL (some OS/browsers cache more aggressively, “sticky DNS”).
- CNAME at zone apex (
example.com) is disallowed by spec — use ALIAS/ANAME or A records instead. - DNS amplification abuse: open recursive resolvers can be weaponized in DDoS attacks — restrict recursion to trusted clients.
3. TLS
3.1 Overview
TLS (Transport Layer Security) provides confidentiality, integrity, and authentication over TCP (or UDP, via DTLS/QUIC). Current standard: TLS 1.3 (RFC 8446); TLS 1.0/1.1 are deprecated, TLS 1.2 still widely supported.
3.2 Handshake (TLS 1.3 — abbreviated, 1-RTT)
Client Server
---- ClientHello (key_share) ------->
<-- ServerHello (key_share)
+ EncryptedExtensions
+ Certificate
+ CertificateVerify
+ Finished
---- Finished ---------------------->
==== Application Data (encrypted) ====
- TLS 1.3 cuts the handshake to 1 round trip (vs 2 in TLS 1.2), and supports 0-RTT resumption for repeat connections (with replay-attack caveats).
- Removes weak ciphers (RC4, CBC-mode issues), static RSA key exchange (no forward secrecy) — only forward-secret (EC)DHE key exchanges remain.
3.3 Core Concepts
- Certificate chain: leaf cert → intermediate CA(s) → root CA (trusted by OS/browser trust store).
- SNI (Server Name Indication): client sends target hostname in cleartext during handshake, enabling multiple TLS certs on one IP (virtual hosting). (ECH — Encrypted Client Hello — encrypts this too, emerging standard.)
- Forward secrecy: session keys derived per-connection (ephemeral Diffie-Hellman) so a compromised long-term key doesn’t expose past traffic.
- Mutual TLS (mTLS): both client and server present certificates — common in service-to-service (zero-trust) architectures and gRPC.
- ALPN (Application-Layer Protocol Negotiation): negotiates HTTP/1.1 vs HTTP/2 vs HTTP/3 during the TLS handshake.
3.4 Best Practices
- Terminate TLS at the edge (load balancer/reverse proxy) unless end-to-end encryption is required (then re-encrypt internally, or use mTLS mesh).
- Use TLS 1.3 where possible; disable TLS 1.0/1.1 entirely.
- Automate certificate issuance/renewal (Let’s Encrypt + ACME, cert-manager in Kubernetes).
- Enable OCSP stapling to avoid client-side revocation-check latency.
- Use strong cipher suites only (AEAD ciphers:
TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256). - Set
HSTSheaders to force HTTPS on subsequent visits and prevent downgrade attacks.
3.5 Common Pitfalls
- Long certificate chains without proper intermediate bundling → “works in browser, fails in curl/mobile app.”
- Clock skew on servers breaking certificate validity checks.
- Mixing TLS termination points inconsistently, leaking internal traffic unencrypted (“TLS termination sprawl”).
- Session resumption / 0-RTT replay risk for non-idempotent requests — disable 0-RTT for mutating endpoints.
4. HTTP/1.1
4.1 Overview
HTTP/1.1 (RFC 7230–7235, 1997) is a text-based, request-response protocol over TCP. Still the baseline that HTTP/2 and HTTP/3 semantically build on.
4.2 Key Features
- Persistent connections (
Connection: keep-alive) — default in 1.1, avoiding a new TCP handshake per request. - Pipelining: send multiple requests without waiting for responses — theoretically allowed but almost universally disabled due to head-of-line (HOL) blocking (responses must return in order).
- Chunked transfer encoding: stream response body without knowing
Content-Lengthupfront. - Caching headers:
Cache-Control,ETag,Last-Modified,If-None-Match. - Content negotiation:
Accept,Accept-Encoding,Accept-Language.
4.3 Request/Response Anatomy
GET /users/42 HTTP/1.1
Host: api.example.com
Accept: application/json
Connection: keep-alive
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 128
Cache-Control: max-age=60
{"id":42,"name":"Ada"}
4.4 Best Practices
- Use connection reuse aggressively — avoid per-request TCP+TLS setup cost.
- Apply domain sharding cautiously (was a workaround for the 6-connections-per-host browser limit) — largely obsolete with HTTP/2 multiplexing.
- Compress responses (
gzip,br) viaContent-Encoding. - Use conditional requests (
If-Modified-Since/ETag) to cut bandwidth. - Set explicit timeouts (client & server) — HTTP/1.1 connections can hang indefinitely without them.
4.5 Common Pitfalls
- Head-of-line blocking: one slow request blocks the entire connection’s queue if pipelining is used — hence most clients open multiple parallel connections (browsers: ~6 per host) instead.
- Verbose, repeated headers per request (cookies, user-agent) — no compression of headers, unlike HTTP/2’s HPACK.
- Relying on default keep-alive timeouts that don’t match load balancer idle timeouts, causing intermittent
502/connection reset errors.
5. HTTP/2
5.1 Overview
HTTP/2 (RFC 7540, 2015, based on Google’s SPDY) keeps HTTP/1.1 semantics (methods, status codes, headers) but changes the wire format to a binary, multiplexed protocol over a single TCP connection.
5.2 Key Features
- Multiplexing: many concurrent request/response streams over one TCP connection — no more per-request connection overhead or 6-connection browser limit.
- Stream prioritization: clients can hint relative importance of streams (partially deprecated in practice due to complexity — HTTP/3 rethinks this).
- HPACK header compression: eliminates redundant header transmission via a shared, indexed table.
- Server Push (
PUSH_PROMISE): server proactively sends resources it predicts the client needs — largely deprecated (removed from Chrome in 2022) due to poor cache alignment and complexity; preferLink: rel=preloador 103 Early Hints instead. - Flow control: per-stream and per-connection windows.
5.3 Binary Framing
All communication is broken into frames (HEADERS, DATA, SETTINGS, WINDOW_UPDATE, RST_STREAM, PING, GOAWAY) tagged with a stream ID, multiplexed onto one connection.
Connection
├─ Stream 1: HEADERS → DATA → DATA (response body)
├─ Stream 3: HEADERS → DATA
└─ Stream 5: HEADERS (request in flight)
5.4 Best Practices
- Let go of HTTP/1.1-era optimizations: domain sharding, spriting, concatenation — they actively hurt HTTP/2 (fewer, larger connections are better than many small ones).
- Use a single connection per origin; rely on multiplexing instead of parallel connections.
- Prefer
103 Early Hintsover deprecated Server Push for preloading critical assets. - Tune
SETTINGS_MAX_CONCURRENT_STREAMSserver-side to prevent resource exhaustion from stream-flooding clients.
5.5 Common Pitfalls
- TCP-level head-of-line blocking: HTTP/2 solves application-level HOL blocking, but a single lost TCP packet still stalls all multiplexed streams (this is HTTP/3’s core motivation).
- Requires TLS in practice (all major browsers only support HTTP/2 over TLS,
h2ALPN token) even though the spec allows cleartext (h2c). - Misconfigured intermediaries (old proxies/WAFs) that don’t understand HTTP/2 framing can silently break connections — HTTP/2 downgrade fallback is essential.
6. HTTP/3
6.1 Overview
HTTP/3 (RFC 9114, 2022) replaces TCP with QUIC (RFC 9000), a UDP-based transport protocol built by Google, standardized at the IETF. Goal: fix HTTP/2’s TCP-level HOL blocking and reduce connection setup latency.
6.2 QUIC Fundamentals
- Runs over UDP, implementing its own reliability, congestion control, and multiplexing in user space (faster iteration than kernel-level TCP changes).
- Streams are independent at the transport layer: a lost packet only stalls the stream it belongs to, not the whole connection — solves HTTP/2’s TCP HOL blocking.
- Integrated TLS 1.3: the handshake is the TLS handshake — connection setup and crypto negotiation happen together in 1-RTT (or 0-RTT for resumed connections).
- Connection migration: connections are identified by a Connection ID, not the IP:port 4-tuple — a client switching from Wi-Fi to cellular can keep its connection alive (huge for mobile).
6.3 Comparison
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC (UDP) |
| Multiplexing | No (pipelining unused) | Yes, single TCP conn | Yes, independent streams |
| HOL blocking | Yes (connection-level) | Partial (TCP-level only) | Solved (per-stream) |
| Handshake | TCP + TLS (2–3 RTT) | TCP + TLS (2–3 RTT) | 1-RTT (0-RTT resume) |
| Header compression | None | HPACK | QPACK (HOL-blocking-safe) |
| Connection migration | No | No | Yes |
6.4 Best Practices
- Advertise HTTP/3 via
Alt-Svcheader so clients can opportunistically upgrade from HTTP/2. - Ensure UDP/443 is open on firewalls/load balancers — many enterprise networks block UDP by default, so always keep an HTTP/2 fallback.
- Use CDNs/edge providers with mature QUIC implementations (most major CDNs support it now) rather than hand-rolling QUIC termination.
- Monitor for UDP-based amplification/DoS vectors when self-hosting QUIC endpoints.
6.5 Common Pitfalls
- Some middleboxes/NATs handle UDP poorly (shorter idle timeouts than TCP) — requires PING frames to keep NAT bindings alive.
- Debugging is harder — QUIC encrypts more of the handshake than TLS-over-TCP, so traditional packet-capture-based debugging needs QUIC-aware tools (
qlog, Wireshark QUIC dissector). - CPU cost: QUIC’s userspace crypto/congestion-control work is more CPU-intensive per byte than kernel-optimized TCP — matters at very high throughput.
7. REST
7.1 Overview
REST (Representational State Transfer), defined by Roy Fielding’s 2000 dissertation, is an architectural style — not a protocol — for designing networked APIs, typically implemented over HTTP.
7.2 Constraints (the real definition of REST)
- Client-server separation
- Statelessness — each request contains all context needed; no server-side session state
- Cacheability — responses explicitly marked cacheable/non-cacheable
- Uniform interface — resources identified by URIs, manipulated via representations, self-descriptive messages, HATEOAS
- Layered system — client can’t tell if connected directly to the server or an intermediary (proxy, gateway)
- Code on demand (optional) — server can extend client functionality (e.g., JS)
7.3 Resource & Method Design
| Method | Semantics | Idempotent | Safe |
|---|---|---|---|
| GET | Read resource | Yes | Yes |
| POST | Create / non-idempotent action | No | No |
| PUT | Replace resource entirely | Yes | No |
| PATCH | Partial update | No* | No |
| DELETE | Remove resource | Yes | No |
GET /orders → list orders
POST /orders → create order
GET /orders/123 → get order 123
PUT /orders/123 → replace order 123
PATCH /orders/123 → partially update order 123
DELETE /orders/123 → delete order 123
7.4 Status Codes That Matter
200 OK,201 Created(+Locationheader),202 Accepted(async processing),204 No Content400 Bad Request,401 Unauthorized,403 Forbidden,404 Not Found,409 Conflict,422 Unprocessable Entity,429 Too Many Requests500 Internal Server Error,502 Bad Gateway,503 Service Unavailable,504 Gateway Timeout
7.5 Best Practices
- Use nouns, not verbs, in URIs (
/orders/123/cancelis a pragmatic exception for actions without a clean resource mapping). - Version APIs deliberately: URI (
/v1/orders), header (Accept: application/vnd.api+json;version=1), or content negotiation — pick one and be consistent. - Support pagination (
?cursor=or?page=&limit=) — prefer cursor-based over offset-based for large/changing datasets. - Use ETags + conditional requests for optimistic concurrency control (
If-Matchon PUT/PATCH). - Return consistent error bodies (e.g.,
application/problem+json, RFC 9457) with machine-readable error codes. - Design for idempotency keys on POST for payment/critical operations to safely handle retries.
- Document with OpenAPI/Swagger; validate requests/responses against the schema in CI.
7.6 Common Pitfalls
- “RPC-over-HTTP” disguised as REST (verbs in URIs, ignoring status codes, everything is POST) — fine as a style choice, but don’t call it REST.
- Chatty APIs causing N+1 request patterns client-side — consider batching, GraphQL, or BFF (Backend-for-Frontend) patterns.
- Leaking internal DB schema directly as API shape — couples clients to internal implementation.
- Ignoring HATEOAS entirely is common and mostly fine in practice — but be aware you’re using a pragmatic subset of REST, not the full Fielding model.
8. WebSocket
8.1 Overview
WebSocket (RFC 6455) provides a full-duplex, persistent communication channel over a single TCP connection, initiated via an HTTP Upgrade handshake.
8.2 Handshake
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After the 101 response, the TCP connection is repurposed for the WebSocket framing protocol — no more HTTP request/response semantics.
8.3 Key Features
- Full-duplex: either side can send frames at any time.
- Lightweight framing (2–14 byte overhead per frame vs full HTTP headers).
- Supports text and binary frames; ping/pong frames for keepalive/liveness.
- No built-in reconnection, backoff, or message ordering guarantees beyond a single connection’s lifetime — the application must implement these.
8.4 Best Practices
- Implement heartbeat (ping/pong) at the application layer to detect dead connections through proxies/NATs that silently drop idle TCP.
- Implement client-side reconnection with exponential backoff + jitter.
- Design messages with a type/version envelope (
{"type": "chat.message", "v": 1, "payload": {...}}) for forward compatibility. - Use a message broker (Redis Pub/Sub, Kafka, NATS) behind WebSocket servers to fan out messages across horizontally scaled instances — a single WS server can’t broadcast to clients connected to other instances.
- Authenticate at handshake time (token in query param or
Sec-WebSocket-Protocol/cookie), since custom headers aren’t available in browser WebSocket APIs. - Set sensible idle timeouts and enforce message size limits to prevent resource exhaustion.
8.5 Common Pitfalls
- Assuming WebSocket connections survive load balancer restarts/deploys — plan for graceful reconnect on the client.
- Sticky-session requirements: if server holds in-memory state per connection, LB must route reconnects consistently (or externalize state to Redis/etc.).
- Not handling backpressure — a slow consumer can cause unbounded server-side buffering; use bounded queues and drop/close on overflow.
- Using WebSocket where Server-Sent Events (SSE) would suffice (simpler, HTTP-native, auto-reconnect, one-way server→client) — don’t reach for WebSocket by default for unidirectional streaming.
9. gRPC
9.1 Overview
gRPC is a high-performance RPC framework (by Google) built on HTTP/2 and Protocol Buffers (protobuf), designed for efficient service-to-service communication.
9.2 Key Features
- Strongly-typed contracts via
.protofiles, code-generated clients/servers in many languages. - Binary serialization (protobuf) — smaller, faster than JSON.
- Four call types:
- Unary (request → response, like normal REST)
- Server streaming (one request → stream of responses)
- Client streaming (stream of requests → one response)
- Bidirectional streaming (both sides stream independently)
- Built on HTTP/2 multiplexing — many concurrent RPCs over one connection.
- Deadlines/timeouts and cancellation propagate automatically across service call chains.
- Interceptors (middleware) for auth, logging, retries, tracing.
9.3 Example .proto
syntax = "proto3";
service OrderService {
rpc GetOrder (OrderRequest) returns (Order);
rpc StreamOrderUpdates (OrderRequest) returns (stream Order);
}
message OrderRequest { string order_id = 1; }
message Order {
string id = 1;
string status = 2;
double total = 3;
}
9.4 Best Practices
- Use gRPC for internal service-to-service communication; expose REST/GraphQL at the edge for browser/public clients (browsers can’t natively speak gRPC’s HTTP/2 trailers-based framing without gRPC-Web + a proxy translation layer).
- Set deadlines on every call — without them, requests can hang indefinitely, cascading failures upstream.
- Use mTLS for service authentication in a mesh/zero-trust environment.
- Version
.protomessages carefully: only add optional fields, never renumber/reuse field numbers, use reserved keyword for removed fields. - Use retries with backoff only on idempotent methods; mark methods explicitly.
- Leverage load balancing at the client (client-side LB with service discovery) since long-lived HTTP/2 connections don’t rebalance well through simple L4 LBs.
9.5 Common Pitfalls
- L4 (TCP-level) load balancers don’t distribute gRPC calls well because a single HTTP/2 connection carries many RPCs — need L7-aware LBs (Envoy, Linkerd) or client-side load balancing.
- Large messages over streaming RPCs without flow control tuning can cause memory pressure.
- Forgetting that protobuf field numbers, once shipped, are essentially permanent API contracts.
- Browser clients require gRPC-Web + an Envoy/proxy translation layer — plain gRPC doesn’t work directly from browser JS.
10. Reverse Proxy
10.1 Overview
A reverse proxy sits in front of one or more backend servers, forwarding client requests to them and returning responses — the client only ever talks to the proxy, unaware of backend topology.
Client → Reverse Proxy → [Backend A, Backend B, Backend C]
10.2 Common Uses
- TLS termination: decrypt HTTPS at the edge, forward plaintext (or re-encrypted mTLS) internally.
- Load balancing: distribute requests across backend instances (see §11).
- Caching: cache static/semi-static responses close to the client.
- Compression: gzip/brotli responses centrally instead of per-service.
- Request routing: path-based (
/api/*→ service A,/static/*→ CDN), host-based (virtual hosting). - Security: WAF rules, rate limiting, IP allow/deny lists, hiding internal topology.
- Protocol translation: HTTP/1.1 client ↔ HTTP/2 or gRPC backend.
10.3 Popular Implementations
| Tool | Notes |
|---|---|
| Nginx | Battle-tested, config-file driven, huge ecosystem |
| Envoy | L7-aware, dynamic config via xDS API, gRPC-native, service mesh data plane |
| HAProxy | Extremely high performance L4/L7 LB/proxy |
| Traefik | Auto-discovery (Docker/Kubernetes labels), dynamic config |
| Caddy | Automatic HTTPS via Let’s Encrypt, simple config |
10.4 Example (Nginx)
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.crt;
ssl_certificate_key /etc/ssl/private/api.key;
location /v1/ {
proxy_pass http://backend_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 30s;
}
}
10.5 Best Practices
- Always forward
X-Forwarded-For,X-Forwarded-Proto,X-Real-IPso backends know original client context. - Set explicit timeouts at every hop (connect, read, write) — mismatched timeouts between proxy and backend cause confusing
502/504errors. - Terminate TLS at the proxy but consider re-encrypting internally (mTLS) in zero-trust environments — don’t assume internal networks are safe.
- Keep proxy layer stateless where possible for easy horizontal scaling.
- Use health checks to automatically remove unhealthy backends from rotation.
10.6 Common Pitfalls
- Forgetting to strip hop-by-hop headers (
Connection,Keep-Alive) when proxying. - Buffering large request/response bodies in memory at the proxy — causes OOM under load; stream instead.
- Reverse proxy vs forward proxy confusion: forward proxy represents the client (e.g., corporate egress proxy); reverse proxy represents the server.
11. Load Balancer
11.1 Overview
A load balancer distributes incoming traffic across multiple backend instances to improve availability, scalability, and fault tolerance.
11.2 Layers
- L4 (Transport layer): routes based on IP/port, no visibility into HTTP content — very fast, protocol-agnostic (works for TCP/UDP generally). Examples: AWS NLB, IPVS, HAProxy in TCP mode.
- L7 (Application layer): understands HTTP/gRPC — can route by path, header, cookie; supports TLS termination, retries, circuit breaking. Examples: AWS ALB, Envoy, Nginx.
11.3 Algorithms
| Algorithm | Description | Good for |
|---|---|---|
| Round robin | Cycles through backends sequentially | Uniform backend capacity |
| Weighted round robin | Round robin with capacity weights | Heterogeneous backend sizes |
| Least connections | Routes to backend with fewest active connections | Variable request duration |
| Least response time | Combines connection count + latency | Latency-sensitive services |
| IP hash / consistent hashing | Same client (or key) → same backend | Session affinity, cache locality |
| Random (with 2 choices) | Pick 2 random backends, choose less loaded | Simpler than least-connections, scales well |
11.4 High Availability Patterns
- Active-passive: standby LB takes over via VRRP/keepalived on primary failure.
- Active-active: multiple LBs behind DNS round-robin or Anycast, all serving traffic simultaneously.
- Health checks: active (LB polls
/healthz) vs passive (LB observes real traffic failures, ejects on error-rate threshold — “outlier detection”).
11.5 Best Practices
- Prefer least connections or power-of-two-choices over naive round robin for uneven request durations.
- Implement circuit breaking: stop sending traffic to a backend after repeated failures, retry gradually (half-open state).
- Use connection draining (graceful deregistration) during deploys — stop sending new requests to an instance while letting in-flight ones finish.
- Combine L4 for raw throughput + L7 at the edge for smart routing where needed — don’t always default to full L7 if it’s not required (extra latency/CPU cost).
- Avoid session affinity/sticky sessions when possible — externalize session state (Redis) so any backend can serve any request; if unavoidable (e.g., WebSocket), use consistent hashing.
11.6 Common Pitfalls
- Thundering herd on backend restart: LB immediately sending full traffic to a freshly-started instance before it’s warmed up — use slow-start ramping.
- Health check endpoint too shallow (
200 OKalways) — doesn’t reflect real backend health (DB connectivity, dependency status). - L4 LB in front of gRPC/HTTP2 causing imbalance (see §9.5) — long-lived multiplexed connections stick to one backend.
12. Connection Pooling
12.1 Overview
Connection pooling reuses a set of pre-established connections (TCP, TLS, DB) instead of creating/tearing one down per operation — critical because connection setup (TCP handshake + TLS handshake + auth) is expensive relative to the actual work.
12.2 Why It Matters
Without pooling: [connect][TLS][auth][query][close] ← repeated every request
With pooling: [borrow from pool][query][return to pool]
- Avoids repeated 3-way TCP handshake + TLS handshake latency (can be 2-3 RTTs saved per request).
- Reduces server-side resource churn (file descriptors, ephemeral ports, TIME_WAIT buildup).
- Essential for databases, HTTP clients making many outbound calls, gRPC channels.
12.3 Key Parameters
| Parameter | Purpose |
|---|---|
| Min pool size | Connections kept warm even when idle |
| Max pool size | Ceiling to protect backend from overload |
| Idle timeout | Close connections unused beyond N seconds |
| Max lifetime | Force-recycle connections periodically (avoid stale/leaked state, enable rolling backend restarts) |
| Acquisition timeout | How long a caller waits for a free connection before erroring |
| Validation/health check | Test connection liveness before handing it out (e.g., SELECT 1) |
12.4 Best Practices
- Size pools based on actual concurrency needs, not guesswork —
max_pool_size ≈ (avg concurrent requests) × (avg connection hold time); oversized pools can overwhelm the backend (e.g., DB connection limits), undersized pools cause queueing. - Use a shared pool per process, not per-request or per-thread pools — enables reuse across the whole application.
- Set max lifetime on pooled connections to allow safe DNS/backend rotation (avoids “pinned to a dead backend behind round-robin DNS” issues).
- Monitor pool metrics: active/idle count, wait time, timeout rate — pool exhaustion is a top cause of cascading latency spikes.
- For HTTP clients, ensure keep-alive is enabled and connection reuse works (many HTTP client libraries default to not reusing connections unless explicitly configured with a shared client/transport instance).
- For databases, prefer external poolers (PgBouncer for Postgres) when the app itself opens many short-lived processes (serverless/lambda) that can’t hold pools well individually.
12.5 Common Pitfalls
- Creating a new HTTP client (and thus new connection pool) per request — extremely common bug in code that instantiates
http.Client()/axios.create()inside a request handler instead of at app startup. - Pool exhaustion under load causing a thundering herd of timeouts, which triggers client retries, which increases load further — a classic retry storm.
- Not matching pool size to backend’s own connection limits (e.g., 50 app instances × 20-connection pools = 1000 connections hitting a DB configured for 200 max).
- Leaking connections (not returning them to the pool on error paths) — always use
try/finallyor RAII-style resource management. - Stale connections in the pool after a backend restart/failover — mitigate with max lifetime + liveness checks.
13. Cross-Cutting Best Practices
- Timeouts everywhere, at every hop: client → proxy → LB → backend → database. A single missing timeout can cause indefinite hangs and resource exhaustion.
- Idempotency as a first-class design concern for anything that might be retried (network is unreliable — always assume retries happen).
- Observability: structured logs, distributed tracing (OpenTelemetry) correlating a request across proxy → LB → service → gRPC calls → DB, and metrics (RED: Rate, Errors, Duration).
- Graceful degradation: circuit breakers, bulkheads, fallback responses — don’t let one failing dependency cascade into total outage.
- Defense in depth: TLS everywhere (including internal traffic in zero-trust models), rate limiting at multiple layers, input validation at every boundary.
- Protocol negotiation: use ALPN/
Alt-Svcto let clients upgrade opportunistically (HTTP/1.1 → HTTP/2 → HTTP/3) without breaking older clients. - Capacity planning: understand how connection pooling, load balancer algorithm, and backend concurrency limits interact under real traffic patterns — load test with realistic connection reuse behavior, not just raw request rate.
End of guide. For a Turkish version, see networking-web-guide-tr.md.