Every tutorial on network layers starts with a boring table: "Layer 7 is Application, Layer 6 is Presentation..." and you forget it by next week. This guide is different. We'll learn each layer by doing — capturing packets, debugging real problems, and understanding what happens byte-by-byte when you type curl https://api.example.com.
The Practical Model: TCP/IP (Not OSI)
The OSI model has 7 layers but the real internet uses the TCP/IP model with 4 layers. Every packet you've ever sent uses TCP/IP, not OSI. Let's focus on what actually matters:
What Happens When You curl a URL?
Let's trace a real request through every layer. This is the single most useful mental model for networking:
# See it yourself! Use curl with verbose output:
curl -v https://api.example.com/users 2>&1
# Output breakdown:
# * Trying 93.184.216.34:443... ← Layer 2: IP resolution
# * Connected to api.example.com ← Layer 3: TCP connection
# * SSL connection using TLSv1.3 ← Layer 4: TLS handshake
# > GET /users HTTP/2 ← Layer 4: HTTP request
# < HTTP/2 200 ← Layer 4: HTTP response
Layer 1: Network Access (The Physical Wire)
This is the only layer you can physically touch. It handles getting bits from one device to the next device on the same local network.
Real-World: Ethernet & MAC Addresses
# See your network interfaces and MAC addresses
ip link show # Linux
ifconfig # macOS
ipconfig /all # Windows
# Example output:
# eth0: 00:1A:2B:3C:4D:5E ← 48-bit MAC address (hardware address)
# wlan0: AA:BB:CC:DD:EE:FF ← WiFi adapter MAC
# See which MAC addresses your machine has talked to recently (ARP table)
arp -a
# ? (192.168.1.1) at 00:11:22:33:44:55 on en0 ← Your router's MAC
# ? (192.168.1.42) at AA:BB:CC:DD:EE:FF on en0 ← Another device
# ARP (Address Resolution Protocol) maps IP → MAC
# "Who has 192.168.1.1? Tell 192.168.1.100"
# "192.168.1.1 is at 00:11:22:33:44:55"
When you'll debug this layer:
- "My server can't reach the database on the same subnet" → Check if ARP resolution works
- "Network is slow on this machine" → Check for duplex mismatch, cable issues
- "VMs can't talk to each other" → Check virtual switch / bridge configuration
Layer 2: Internet Layer (IP — Getting Packets Across Networks)
Layer 1 handles the local network. Layer 2 (IP) handles getting packets from your network to any other network in the world via routing.
Real-World: IP Addresses & Routing
# See your IP addresses
ip addr show # Linux
ifconfig # macOS
ipconfig # Windows
# Public vs Private IPs:
# Private (local network only):
# 10.0.0.0/8 ← Large enterprise networks
# 172.16.0.0/12 ← Medium networks
# 192.168.0.0/16 ← Home/small office (your WiFi is probably here)
# Public (internet-routable):
# Everything else (e.g., 93.184.216.34)
# What's my public IP?
curl -s https://ifconfig.me
# Output: 203.0.113.42
# Trace the route from your machine to Google's servers
traceroute google.com # macOS/Linux
tracert google.com # Windows
# Output:
# 1 192.168.1.1 0.5ms ← Your home router
# 2 10.0.0.1 2.1ms ← ISP's first router
# 3 72.14.209.81 5.3ms ← ISP backbone
# 4 108.170.252.1 8.7ms ← Google's edge
# 5 142.250.80.46 10.2ms ← Google's server
# Each hop is a router making a forwarding decision based on the destination IP
# See your machine's routing table
ip route show # Linux
netstat -rn # macOS
route print # Windows
# Key routes:
# default via 192.168.1.1 ← Everything goes to router (gateway)
# 192.168.1.0/24 dev eth0 ← Local network (no routing needed)
# 10.0.0.0/8 via 10.0.0.1 ← VPN or internal network route
When you'll debug this layer:
- "Can't reach external services" → Check default route, DNS resolution
- "High latency to a specific service" →
tracerouteto find which hop is slow - "Packets getting dropped" →
pingwith different sizes, check MTU - "VPN connected but can't reach internal services" → Check route table conflicts
Layer 3: Transport (TCP & UDP — How Data Gets Delivered)
IP gets packets to the right machine. Transport protocols get data to the right application on that machine, using ports.
Real-World: TCP Deep Dive
# See all active TCP connections on your machine
ss -tuln # Linux (modern)
netstat -tuln # Linux/macOS (classic)
netstat -an # Windows
# Output:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 0 128 0.0.0.0:22 0.0.0.0:* ← SSH server
# LISTEN 0 511 0.0.0.0:80 0.0.0.0:* ← Web server
# ESTAB 0 0 10.0.1.5:43210 93.184.216.34:443 ← Active HTTPS
# TIME_WAIT 0 0 10.0.1.5:43211 93.184.216.34:443 ← Closing
# TCP 3-Way Handshake — capture it live with tcpdump
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -c 10
# Output:
# 10:00:01 IP 10.0.1.5.43210 > 93.184.216.34.443: Flags [S] ← SYN
# 10:00:01 IP 93.184.216.34.443 > 10.0.1.5.43210: Flags [S.] ← SYN-ACK
# 10:00:01 IP 10.0.1.5.43210 > 93.184.216.34.443: Flags [.] ← ACK
# Connection established! Took ~1ms (3 packets)
# Common TCP states you'll see:
# LISTEN ← Server waiting for connections
# ESTABLISHED ← Active connection (data flowing)
# TIME_WAIT ← Connection closed, waiting for stale packets to expire (2 min)
# CLOSE_WAIT ← Remote side closed, your app hasn't closed yet (BUG if stuck here)
# SYN_SENT ← Your machine sent SYN, waiting for SYN-ACK (firewall blocking?)
# 🚨 Debugging: "Too many TIME_WAIT connections"
# This means your app opens and closes tons of short-lived connections.
# Fix: Use connection pooling (requests.Session(), HTTP keep-alive)
ss -s # Show TCP state summary
# TCP: 2345 (estab 890, closed 1200, time-wait 245)
Real-World: Ports You Must Know
| Port | Protocol | Transport | What It Does |
|---|---|---|---|
| 22 | SSH | TCP | Remote shell, SCP, SFTP |
| 53 | DNS | UDP/TCP | Domain name resolution |
| 80 | HTTP | TCP | Web traffic (unencrypted) |
| 443 | HTTPS | TCP | Web traffic (encrypted) |
| 5432 | PostgreSQL | TCP | Database connections |
| 6379 | Redis | TCP | Cache / message broker |
| 8080 | HTTP (alt) | TCP | Dev servers, proxies |
| 50051 | gRPC | TCP (HTTP/2) | RPC services |
Layer 4: Application (HTTP, DNS, TLS — What Your Code Uses)
This is the layer developers interact with most. Every API call, database query, and web page uses application-layer protocols built on top of TCP/UDP.
DNS — The Internet's Phone Book
# How DNS resolution works (step by step):
# 1. You type: curl api.example.com
# 2. OS checks /etc/hosts file first (local override)
# 3. OS checks local DNS cache
# 4. Asks your configured DNS server (e.g., 8.8.8.8)
# 5. DNS server resolves recursively:
# Root server (.com) → TLD server (example.com) → Authoritative server
# 6. Returns: api.example.com → 93.184.216.34
# Query DNS manually
dig api.example.com
# ;; ANSWER SECTION:
# api.example.com. 300 IN A 93.184.216.34
# TTL=300 means this answer is cached for 5 minutes
# Query specific record types
dig example.com MX # Mail servers
dig example.com NS # Name servers
dig example.com TXT # SPF, DKIM, verification records
dig example.com AAAA # IPv6 address
dig example.com CNAME # Alias to another domain
# Trace the full DNS resolution path
dig +trace api.example.com
# Shows: root → .com → example.com → api.example.com
# Check what DNS server you're using
cat /etc/resolv.conf # Linux
scutil --dns | head -20 # macOS
# 🚨 Debugging DNS:
# "Can't resolve hostname" → dig @8.8.8.8 hostname (bypass local DNS)
# "Works from one machine, not another" → Different DNS servers, stale cache
# "Intermittent failures" → DNS TTL too low, server overloaded
# Clear DNS cache:
sudo systemd-resolve --flush-caches # Linux (systemd)
sudo dscacheutil -flushcache # macOS
HTTP/HTTPS — How the Web Works
# HTTP is a text-based request-response protocol on top of TCP
# Raw HTTP request (what curl sends):
# GET /api/users HTTP/1.1
# Host: api.example.com
# Accept: application/json
# Authorization: Bearer eyJ...
#
# (empty line = end of headers)
# Raw HTTP response (what the server returns):
# HTTP/1.1 200 OK
# Content-Type: application/json
# Content-Length: 128
# Cache-Control: max-age=300
#
# {"users": [{"id": 1, "name": "Alice"}]}
# See the full request/response exchange:
curl -v https://api.example.com/users 2>&1 | head -30
# Lines starting with > are the REQUEST
# Lines starting with < are the RESPONSE
# HTTP/2 vs HTTP/1.1:
# HTTP/1.1: One request per TCP connection (or keep-alive pipelining)
# HTTP/2: Multiplexed — many requests share one connection (used by gRPC)
# HTTP/3: Uses QUIC (UDP-based) — faster handshake, no head-of-line blocking
# Check which HTTP version a server supports:
curl -v --http2 https://api.example.com 2>&1 | grep "< HTTP"
# < HTTP/2 200
Packet Capture with tcpdump & Wireshark
The ultimate debugging tool. tcpdump captures raw packets on any interface — the network equivalent of a debugger.
# Capture all traffic on eth0
sudo tcpdump -i eth0 -nn
# Capture only HTTP traffic (port 80)
sudo tcpdump -i eth0 -nn port 80
# Capture traffic to/from a specific IP
sudo tcpdump -i eth0 -nn host 93.184.216.34
# Capture DNS queries (port 53)
sudo tcpdump -i eth0 -nn port 53
# Output:
# 10:00:01 IP 10.0.1.5.52341 > 8.8.8.8.53: A? api.example.com
# 10:00:01 IP 8.8.8.8.53 > 10.0.1.5.52341: A 93.184.216.34
# Capture only TCP SYN packets (new connections)
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn'
# Save capture to file (analyze in Wireshark later)
sudo tcpdump -i eth0 -nn -w capture.pcap -c 1000
# Wireshark: Open capture.pcap for visual analysis
# - Filter: http.request.method == "GET"
# - Filter: tcp.flags.syn == 1
# - Filter: dns.qry.name contains "example"
# - Right-click any packet → Follow TCP Stream (see full conversation)
Production Debugging Scenarios
Here are real problems you'll face and which layer to investigate:
| Symptom | Layer | Debug With | Likely Cause |
|---|---|---|---|
| Can't resolve hostname | Application (DNS) | dig, nslookup | DNS server down, wrong /etc/resolv.conf |
| Connection refused | Transport (TCP) | telnet, nc, ss | Service not running, wrong port |
| Connection timeout | Internet (IP) | ping, traceroute | Firewall blocking, routing issue |
| TLS handshake failed | Application (TLS) | openssl s_client | Expired cert, wrong hostname, cipher mismatch |
| HTTP 502 Bad Gateway | Application (HTTP) | curl -v, access logs | Backend crashed, proxy misconfigured |
| Slow responses | Transport (TCP) | tcpdump, ss | Packet loss, TCP retransmissions, small window |
| Network unreachable | Link (Physical) | ip link, ethtool | Cable unplugged, NIC down, VLAN wrong |
The Essential Networking Toolkit
# Debugging flowchart (use this every time):
# Step 1: Can I reach the host at all?
ping 93.184.216.34
# If NO → Layer 2/3 issue (routing, firewall, host down)
# Step 2: Can I resolve the hostname?
dig api.example.com
# If NO → DNS issue (Layer 4: Application)
# Step 3: Can I open a TCP connection?
nc -zv api.example.com 443
# or: telnet api.example.com 443
# If "Connection refused" → Service not listening on that port
# If "Connection timed out" → Firewall blocking the port
# Step 4: Is TLS working?
openssl s_client -connect api.example.com:443 -servername api.example.com
# Shows certificate chain, TLS version, cipher suite
# If error → Cert expired, hostname mismatch, protocol mismatch
# Step 5: Is HTTP working?
curl -v https://api.example.com/health
# If 5xx → Server-side bug
# If timeout → Back to step 1-3
# Step 6: Capture packets for deep analysis
sudo tcpdump -i eth0 -nn host 93.184.216.34 -w debug.pcap
# Open in Wireshark for visual analysis
Network Layers in Kubernetes
If you work with Kubernetes, here's how the layers map:
# Debugging networking in Kubernetes:
# What IP did my pod get?
kubectl get pod my-app -o wide
# NAME READY STATUS IP NODE
# my-app 1/1 Running 10.244.1.15 node-2
# Can my pod reach another service?
kubectl exec -it my-app -- curl -v http://other-service:8080/health
# DNS resolution inside a pod:
kubectl exec -it my-app -- nslookup other-service.default.svc.cluster.local
# Server: 10.96.0.10 (CoreDNS)
# Address: 10.96.0.10#53
# Name: other-service.default.svc.cluster.local Address: 10.96.45.123
# See Service endpoints (which pods back a Service?)
kubectl get endpoints other-service
# NAME ENDPOINTS
# other-service 10.244.1.15:8080,10.244.2.23:8080
# Debug with a network tools pod:
kubectl run debug --image=nicolaka/netshoot -it --rm -- bash
# Inside: ping, traceroute, dig, curl, tcpdump all available
Mastery Checklist
Networking isn't about memorizing layer numbers — it's about knowing which tool to reach for when something breaks at 3 AM. Start by running every command in this guide on your own machine. Then break things intentionally in a lab (block ports with iptables, poison DNS, drop packets with tc) and practice fixing them. That's how you master network layers — not by reading, but by debugging.