Networking & Web — The Complete Guide

Protocols and architectural patterns every backend and networking engineer should master: HTTP, TCP, gRPC, TLS, DNS, and WebSocket.

🌱 Seedling·created: ·category:Engineering

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

  1. TCP/IP
  2. DNS
  3. TLS
  4. HTTP/1.1
  5. HTTP/2
  6. HTTP/3
  7. REST
  8. WebSocket
  9. gRPC
  10. Reverse Proxy
  11. Load Balancer
  12. Connection Pooling
  13. 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):

LayerExamplesResponsibility
ApplicationHTTP, DNS, gRPCApp-level data exchange
TransportTCP, UDPEnd-to-end delivery, ports
InternetIP, ICMPRouting, addressing
LinkEthernet, Wi-FiPhysical/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: cwnd grows 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

TCPUDP
ConnectionConnection-orientedConnectionless
ReliabilityGuaranteed, orderedBest-effort
OverheadHigher (headers, handshake)Minimal
Use casesHTTP, gRPC, databasesDNS queries, video streaming, QUIC/HTTP3, gaming

1.5 Best Practices

  • Tune net.ipv4.tcp_congestion_control to bbr for high-latency/high-bandwidth links (video, CDNs).
  • Increase somaxconn and backlog for high-concurrency servers.
  • Use SO_REUSEPORT to 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

RecordPurpose
AHostname → IPv4
AAAAHostname → IPv6
CNAMEAlias to another hostname
MXMail exchange servers
TXTArbitrary text (SPF, DKIM, verification)
NSAuthoritative name servers
SOAZone authority info
SRVService location (host+port), used by gRPC/Kubernetes
PTRReverse 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 HSTS headers 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-Length upfront.
  • 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) via Content-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; prefer Link: rel=preload or 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 Hints over deprecated Server Push for preloading critical assets.
  • Tune SETTINGS_MAX_CONCURRENT_STREAMS server-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, h2 ALPN 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.1HTTP/2HTTP/3
TransportTCPTCPQUIC (UDP)
MultiplexingNo (pipelining unused)Yes, single TCP connYes, independent streams
HOL blockingYes (connection-level)Partial (TCP-level only)Solved (per-stream)
HandshakeTCP + TLS (2–3 RTT)TCP + TLS (2–3 RTT)1-RTT (0-RTT resume)
Header compressionNoneHPACKQPACK (HOL-blocking-safe)
Connection migrationNoNoYes

6.4 Best Practices

  • Advertise HTTP/3 via Alt-Svc header 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)

  1. Client-server separation
  2. Statelessness — each request contains all context needed; no server-side session state
  3. Cacheability — responses explicitly marked cacheable/non-cacheable
  4. Uniform interface — resources identified by URIs, manipulated via representations, self-descriptive messages, HATEOAS
  5. Layered system — client can’t tell if connected directly to the server or an intermediary (proxy, gateway)
  6. Code on demand (optional) — server can extend client functionality (e.g., JS)

7.3 Resource & Method Design

MethodSemanticsIdempotentSafe
GETRead resourceYesYes
POSTCreate / non-idempotent actionNoNo
PUTReplace resource entirelyYesNo
PATCHPartial updateNo*No
DELETERemove resourceYesNo
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 (+ Location header), 202 Accepted (async processing), 204 No Content
  • 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests
  • 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

7.5 Best Practices

  • Use nouns, not verbs, in URIs (/orders/123/cancel is 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-Match on 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 .proto files, 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 .proto messages 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.
ToolNotes
NginxBattle-tested, config-file driven, huge ecosystem
EnvoyL7-aware, dynamic config via xDS API, gRPC-native, service mesh data plane
HAProxyExtremely high performance L4/L7 LB/proxy
TraefikAuto-discovery (Docker/Kubernetes labels), dynamic config
CaddyAutomatic 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-IP so backends know original client context.
  • Set explicit timeouts at every hop (connect, read, write) — mismatched timeouts between proxy and backend cause confusing 502/504 errors.
  • 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

AlgorithmDescriptionGood for
Round robinCycles through backends sequentiallyUniform backend capacity
Weighted round robinRound robin with capacity weightsHeterogeneous backend sizes
Least connectionsRoutes to backend with fewest active connectionsVariable request duration
Least response timeCombines connection count + latencyLatency-sensitive services
IP hash / consistent hashingSame client (or key) → same backendSession affinity, cache locality
Random (with 2 choices)Pick 2 random backends, choose less loadedSimpler 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 OK always) — 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

ParameterPurpose
Min pool sizeConnections kept warm even when idle
Max pool sizeCeiling to protect backend from overload
Idle timeoutClose connections unused beyond N seconds
Max lifetimeForce-recycle connections periodically (avoid stale/leaked state, enable rolling backend restarts)
Acquisition timeoutHow long a caller waits for a free connection before erroring
Validation/health checkTest 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/finally or 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-Svc to 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.

This note is part of the Digital Garden — a collection of connected, evolving thoughts.