The OSI layers in practice
Open in the interactive app: practice, quiz, mock interview →In one minute
- The OSI model is a shared vocabulary, not a description of the software you run. The internet actually runs the TCP/IP stack, which uses four or five layers. In system design, people say "L4" and "L7" because the OSI numbers name what a component can see.
- A device can only decide on headers it parses: a switch sees MACs, a router IP prefixes, an L4 balancer the 5-tuple, and only an L7 proxy sees URLs, cookies and gRPC methods. Choosing the layer chooses capabilities, cost and failure modes.
- Every layer wraps the payload in another header. Overlays, tunnels and VPNs add more, and the total must fit the path MTU. Most "small requests work, large ones hang" incidents are layer-budget problems.
- The layers leak in practice. NAT rewrites L4 ports inside an L3 device, TLS sits in no clean layer, and QUIC merges transport and encryption in user space because middleboxes ossified TCP.
- Debug bottom up: link, route, port, TLS, application. Each layer's tool (
ip link,traceroute,ss,openssl s_client,curl -v) rules out one class of cause.
How it works
Seven names, four implementations
OSI defines physical (1), data link (2), network (3), transport (4), session (5), presentation (6) and application (7). The TCP/IP stack that actually shipped has link, internet, transport and application (RFC 1122). Sessions live in applications (cookies, gRPC streams), and presentation lives in libraries (TLS, protobuf, gzip). So "layer 7" means "anything above transport, as parsed by a proxy". The numbers survive because they answer a design-review question precisely: what does this component read, and what does it treat as opaque bytes?
What each layer lets a box see
A layer 2 switch reads the destination MAC and forwards the frame out of the port where it learned that MAC. A loop or broadcast storm crosses the whole broadcast domain.
A layer 3 router reads the destination IP, does a longest-prefix match, decrements TTL and forwards. Equal-cost multipath (ECMP) spreads flows across links by hashing header fields.
A layer 4 load balancer reads the protocol and ports as well, so it can pin a whole connection (the 5-tuple: source IP, source port, destination IP, destination port, protocol) to one backend. Pass-through designs (Maglev, Katran, AWS Network Load Balancer) forward packets without terminating TCP; others terminate it and open a second connection. Pass-through balancers reach backends either by rewriting only the destination MAC (direct server return, which needs one shared layer 2 domain) or by wrapping each packet in GRE, which adds 24 bytes and can push a full-size packet past the MTU (Google SRE book, ch. 19). Neither kind sees the HTTP path.
A layer 7 proxy (Envoy, NGINX, HAProxy in HTTP mode, AWS Application Load Balancer) terminates TCP and usually TLS, parses the request, and can route by host, path, header or gRPC method, retry, authenticate and emit per-request metrics. The price is two connections per client, TLS crypto and more CPU per request.
Encapsulation and the MTU budget
Sending data pushes headers in front of it: TLS record framing (a 5-byte header, plus in TLS 1.3 a 1-byte inner content type and a 16-byte AEAD tag, per RFC 9846), TCP (20 bytes, usually 32 with timestamps), IPv4 (20) or IPv6 (40), and Ethernet (14 plus a 4-byte frame check sequence). The IP packet must fit the MTU of every link on the path, usually 1,500 bytes. Tunnels nest whole packets: VXLAN, common in Kubernetes overlays, adds 50 bytes, WireGuard 60 over IPv4 and 80 over IPv6, GRE 24.
Fragmentation is slow, and only the first fragment carries ports, so TCP sets Don't Fragment and relies on Path MTU Discovery. A router that cannot forward the packet drops it and returns ICMP "fragmentation needed" (IPv4) or "packet too big" (IPv6, where routers never fragment). If a firewall drops that ICMP, the sender never learns, and large packets vanish silently: a PMTUD black hole.
The robust fix stops trusting ICMP. With packetization-layer PMTUD, the transport sends probes of increasing size and learns the path MTU from which ones are acknowledged (RFC 8899). Linux TCP does this when tcp_mtu_probing is 1 (only after it suspects a black hole) or 2 (always) (kernel ip-sysctl). QUIC refuses paths that can't carry a 1,200-byte UDP payload and may probe upward from there the same way (RFC 9000).
Where the layering breaks
NAT is an L3 box rewriting L4 ports. TLS fits no layer cleanly. VXLAN carries L2 frames inside UDP. Middleboxes that assumed what TCP headers look like froze TCP, which is a large part of why QUIC encrypts its transport header and runs over UDP.
Down the stack
Layer 1The contract
Each layer serves the one above and treats its bytes as opaque. Ethernet delivers a CRC-checked frame on one segment or drops it, IP forwards best-effort, TCP provides an ordered byte stream, TLS adds confidentiality and integrity. Infrastructure contracts can be stated precisely enough to test. A pass-through L4 balancer promises that all packets of a 5-tuple reach the same backend while the backend set is stable. An L7 proxy promises per-request routing and offers no affinity unless configured. A tunnel promises to carry inner packets up to its advertised MTU and nothing larger.
Layer 2The mechanism
In Linux, a packet is an sk_buff with head, data, tail and end pointers into one buffer. Each layer prepends its header with skb_push, which moves the data pointer backwards into headroom reserved in advance, so encapsulation needs no copy (kernel sk_buff docs). On receive, skb_pull advances past each header as the stack climbs. Netfilter hooks (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING) are where iptables or nftables, conntrack and NAT act, at the IP layer but with knowledge of ports.
The 20-byte IPv4 header holds version and length, DSCP/ECN, total length, identification, flags and fragment offset, TTL, protocol (6 TCP, 17 UDP), a checksum and the two addresses. The checksum is the ones' complement of the ones' complement sum of the header's 16-bit words, which lets a router patch it incrementally after decrementing TTL (RFC 1624). Envoy mirrors the split: network filters (tcp_proxy, the TLS inspector reading SNI) work per connection, HTTP filters per request.
Layer 3The machine
A 10 Gbps link carries about 812,000 full-size 1,500-byte packets per second, or 14.88 million minimum-size 64-byte frames. Per-packet work sets the CPU cost, so NICs offload layers: checksum offload, TSO/GSO (the kernel hands down a 64 KB super-segment that the NIC slices into MSS-sized packets), GRO (merging arriving segments), and RSS, which hashes the 5-tuple to pick a receive queue and core, an L4 decision in silicon.
Moving up multiplies cost. An XDP or DPDK L4 balancer forwards millions of packets per second per core. An L7 proxy pays for TLS handshakes (roughly 0.1 to 0.3 ms of CPU each for ECDHE P-256), header parsing and socket buffers on both sides, and typically serves tens of thousands of HTTPS requests per second per core.
Why it's built this way
- No single design could cover every medium and application, so layers change independently: Ethernet went from 10 Mbps to 400 Gbps without HTTP noticing.
- Independent layers need their own addressing, so every layer adds a header that costs MTU bytes and per-packet work.
- Parsing deeper costs more CPU and state, so the network core stays at L3 and expensive L7 work moves to the edges, where it can be scaled out.
- Middleboxes optimised by reading layers they did not own, which froze those layers, so new protocols (QUIC) encrypt their headers to keep the freedom to change.
Diagram
flowchart LR
A["App: HTTP request"] --> T["TLS record"]
T --> P["TCP segment: ports, seq"]
P --> I["IP packet: src, dst, TTL"]
I --> E["Ethernet frame: MACs"]
E --> SW["Switch reads L2"]
SW --> R["Router reads L3"]
R --> L4["L4 LB reads 5-tuple"]
L4 --> L7["L7 proxy ends TLS, reads path"]
L7 --> S["Service"]Worked example
A Kubernetes cluster runs a VXLAN overlay on a 1,500-byte cloud network, and pods reach a partner over WireGuard inside the overlay.
- Underlay MTU 1,500; VXLAN takes 50, so pod interfaces must be 1,450.
- WireGuard over IPv4 takes 60, so the tunnel interface must be 1,390.
- TCP payload per segment: 1,390 minus 20 (IP) minus 32 (TCP with timestamps) is 1,338 bytes.
Efficiency falls from 1,448/1,500 (96.5%) to 1,338/1,500 (89.2%); at 2 Gbps that is about 150 Mbps of extra headers.
The incident: someone leaves WireGuard at its default 1,420. Small API calls work, but a response carrying a 5 KB certificate chain hangs. A 1,420-byte inner packet plus 60 is 1,480, which exceeds the 1,450 pod MTU; the overlay drops it, and the ICMP reply is blocked by a security group. ping -M do -s 1362 succeeds while -s 1400 fails, which confirms it. Fix the MTU on each nested interface, clamp MSS on the tunnel (--clamp-mss-to-pmtu), and allow ICMP type 3 code 4 and ICMPv6 type 2.
The layer choice also shows at the balancer. With 40 gRPC clients each holding one HTTP/2 connection through an L4 balancer to 10 backends, a client sending 5,000 requests per second puts all of them on one backend. An L7 proxy spreads per request, at the cost of TLS termination and about 1 ms.
In the wild
- Cloudflare, 2019: the price of parsing at L7. On 2 July, a new WAF rule contained a regular expression that backtracked catastrophically. It drove CPU to nearly 100% on every core serving HTTP and HTTPS worldwide, and the edge returned 502s for about 27 minutes until the WAF was disabled globally. Layers below were fine; only the component that parses requests failed. Afterwards Cloudflare restored a removed CPU guard, moved to regex engines with run-time guarantees, and staged rule rollouts (Cloudflare).
- Cloudflare, 2020: one L3 line drops half the traffic. On 17 July, a one-line router change in Atlanta, meant to shed backbone load, made the router advertise all its BGP routes with local-preference 200, above the 100 used for local servers. Traffic from 19 locations converged on Atlanta and overloaded it, and overall traffic fell by about 50% for 27 minutes. The fixes were a local-preference that stops one site attracting others' traffic and maximum-prefix limits on backbone sessions (Cloudflare).
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| L3 (ECMP, anycast) | Line rate in hardware, no per-flow state | Hash-based spread only, no health detail | Spreading across L4 balancers or PoPs |
| L4 pass-through LB | Millions of pps, preserves client IP, TLS stays end to end | Balances connections not requests, cannot route by path | Raw TCP/UDP services, front tier of a large edge |
| L4 terminating LB | Hides backends, can add TLS | Two connections, client IP needs PROXY protocol | Simple TCP services needing a stable endpoint |
| L7 proxy | Per-request routing, retries, auth, rich metrics | CPU for TLS and parsing, more latency, more config surface | HTTP/gRPC APIs, canaries, path routing |
| Sidecar mesh (L7 everywhere) | Uniform mTLS and telemetry | A proxy hop on each side of every call, more memory | Many services and teams that need the same policy |
Pitfalls in production
- PMTUD black holes. Firewalls that drop all ICMP break Path MTU Discovery, so only large transfers hang. Allow "fragmentation needed" and "packet too big", and clamp MSS on tunnels.
- Health checks at the wrong layer. A TCP connect check passes while the app returns 500 for everything. Check at the layer users depend on.
- Lost client IP after termination. Once a proxy terminates TCP, backends see the proxy's address, and rate limits and audit logs break. Use
X-Forwarded-For(trusting only your own proxies) or the PROXY protocol at L4, which prepends the original addresses as a short header before the first byte of the stream (PROXY protocol spec). - Long-lived connections behind L4. HTTP/2, gRPC and database pools stay pinned to old backends, so new instances sit idle after a scale-out. Balance at L7 or cap connection age.
Leadership lens
- In reviews, ask "at which layer is this decision made, and what can that component see?" It exposes plans to route by user ID through an L4 balancer or to rate-limit by IP behind a proxy.
- Layers map to ownership: network teams own L2 and L3, platform teams own the balancers and mesh, and product teams own L7 semantics. Incidents at the boundaries, such as MTU and ICMP, need a named owner.
- L7 everywhere has a recurring bill. Sidecars add CPU, memory and latency on every hop. Price that against the policy and telemetry they provide before adopting a mesh.
Check yourself
Why can a pass-through L4 load balancer not send
/images/to a different pool than/api/?Show answer
The path is inside the HTTP request, usually inside TLS. A pass-through L4 device forwards packets by 5-tuple without terminating TCP or decrypting, so it never sees the path.
A VPN user can load small pages but large downloads stall. Which layers would you suspect first and why?
Show answer
L3 MTU and ICMP. The tunnel adds headers so full-size packets no longer fit, and if ICMP "fragmentation needed" is blocked, the sender never shrinks its segments.
Why does QUIC encrypt most of its transport header when TCP does not?
Show answer
Middleboxes inspected and made assumptions about TCP headers, which made TCP almost impossible to evolve. Encrypting the header stops middleboxes from depending on it and keeps the protocol changeable.
Sources and further study
Primary
- RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports: probing for the path MTU without relying on ICMP.
- RFC 1624: Incremental Internet Checksum: why routers can patch the IPv4 checksum after changing TTL.
- The PROXY protocol, versions 1 and 2: carrying client addresses through L4 proxies.
Systems docs
- Linux ip-sysctl:
tcp_mtu_probing,tcp_base_mssand the other knobs that decide how the stack copes with MTU problems. - Google SRE book, chapter 19: Load Balancing at the Frontend: DNS, L4 balancing with consistent hashing, GRE encapsulation and its MTU cost.
Books
- Computer Networking: A Top-Down Approach, Kurose and Ross, section 1.5 "Protocol Layers and Their Service Models": the five-layer Internet stack against the seven-layer OSI model.
- Computer Networks: A Systems Approach, Peterson and Davie, section 1.3 "Architecture": free online; layering, encapsulation and the OSI versus Internet architecture.
- TCP/IP Illustrated, Volume 1 (2nd edition), Fall and Stevens, chapter 1 "Introduction": architectural principles, layering and encapsulation as implemented.
Other curricula
- donnemartin/system-design-primer: shows an OSI diagram at the top of its Communication section and moves straight on to HTTP, TCP and UDP.
- roadmap.sh: Software Architect: treats OSI and TCP/IP as an interoperability vocabulary (seven layers versus four), with no L4/L7 or MTU discussion.
Watch
- The OSI Model by Example (Hussein Nasser): walks a real request through each layer from a backend engineer's point of view.
- OSI Model vs TCP/IP Model: Key Differences Explained (Engineering Funda): maps the seven OSI layers onto the stack that actually shipped.
Free resources
- RFC 1122: Requirements for Internet Hosts: the TCP/IP layering as implemented, with what each layer must and may do.
- RFC 3439: Some Internet Architectural Guidelines and Philosophy: includes the argument that strict layering can be harmful.
- End-to-End Arguments in System Design (Saltzer, Reed, Clark): the classic paper on which layer should own a function.
- Path MTU discovery in practice (Cloudflare blog): how PMTUD breaks with anycast and ECMP, and how it was fixed.
- struct sk_buff (Linux kernel docs): how the kernel represents a packet as it moves between layers.
- OSI model (Wikipedia): background, history and the layer-by-layer reference.