10 — Networking Fundamentals
Networking is how your Linux box talks to other machines — via TCP/IP, DNS, HTTP, SSH. This chapter covers the network stack, interface configuration (ip), routing, DNS, sockets (ss), and the everyday tools (curl, ssh, rsync).
The TCP/IP Model
┌─────────────────────────────────────┐
│ Application (HTTP, SSH, DNS, ...) │ ← your programs
├─────────────────────────────────────┤
│ Transport (TCP / UDP) │ ← ports, reliability
├─────────────────────────────────────┤
│ Internet (IP) │ ← routing, IP addresses
├─────────────────────────────────────┤
│ Link (Ethernet, Wi-Fi) │ ← MAC addresses, switches
└─────────────────────────────────────┘
Network Interfaces
# Complex Implementation: comprehensive network state in one command
ip -br addr show # brief (one line per interface: name, state, IP)
ip route # routing table
ss -tlnp # TCP listening + process
# Legacy (deprecated but common):
ifconfig # interfaces + IPs
netstat -rn # routing table (use ip route instead)
Interface names:
eth0,enp3s0,ens33— Ethernet (en= Ethernet,p= PCI bus,s= slot).wlan0,wlp2s0— Wi-Fi.lo— loopback (127.0.0.1).docker0,br-xxx— Docker bridges.
Configuring (Temporary)
sudo ip link set eth0 up # bring interface up
sudo ip addr add 192.168.1.10/24 dev eth0 # add an IP
sudo ip route add default via 192.168.1.1 # default gateway
# These are TEMPORARY (lost on reboot). Persistent config is in:
# /etc/netplan/ (Ubuntu), /etc/NetworkManager/ or nmcli (RHEL)
Ubuntu — Netplan
# /etc/netplan/01-netcfg.yaml
network:
version: 2
ethernets:
eth0:
dhcp4: true
eth1:
addresses: [192.168.1.10/24]
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [8.8.8.8, 1.1.1.1]
sudo netplan apply # apply config
sudo netplan try # test (rolls back if you don't confirm in 120s)
Routing
# Complex Implementation: diagnose "can't reach X"
ip route # show routing table
ip route get 8.8.8.8 # which route + interface for this destination?
tracepath 8.8.8.8 # trace the path (no root needed)
mtr 8.8.8.8 # continuous traceroute (live)
# default route — where packets go if no other route matches (your gateway)
# 192.168.1.0/24 dev eth0 — this subnet is directly on eth0 (no router)
DNS
# Complex Implementation: full DNS diagnostic chain
cat /etc/resolv.conf # nameservers (often auto-generated)
cat /etc/hosts # static overrides (checked BEFORE DNS)
cat /etc/nsswitch.conf | grep hosts # order: files dns?
dig example.com # full DNS query (A record)
dig @8.8.8.8 example.com # query a specific server
getent hosts example.com # via NSS (checks /etc/hosts, then DNS)
# Edge Case: /etc/hosts overrides DNS
# If /etc/hosts has "127.0.0.1 example.com", dig shows the real IP
# but getent/curl/ping use 127.0.0.1 (hosts is checked first)
Edge Case: resolv.conf Is Often Auto-Generated
# On modern systems, resolv.conf is generated by systemd-resolved or NetworkManager
# Don't edit it directly — changes are overwritten
resolvectl status # systemd-resolved status
resolvectl dns eth0 8.8.8.8 # set DNS for an interface
# Edit Netplan/NetworkManager config for persistence
Sockets — ss (Replaces netstat)
# Complex Implementation: find what's listening on port 80 + established connections
ss -tlnp | grep :80 # TCP listening + numeric + process
ss -t state established # all established TCP connections
ss -t state time-wait # connections in TIME_WAIT
ss -s # socket summary
ss -i # internal TCP info (RTT, congestion, cwnd)
Common ss flags: -t TCP, -u UDP, -l listening, -n numeric (don't resolve), -p show process.
HTTP — curl
# Complex Implementation: production HTTP health check
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://example.com
# 200 0.342s
# POST JSON with headers:
curl -H "Content-Type: application/json" \
-d '{"key":"value"}' \
https://api.example.com
# Override DNS (test a specific server without changing DNS):
curl --resolve example.com:443:1.2.3.4 https://example.com
# Debug TLS:
curl -v https://example.com 2>&1 | head
SSH — Secure Shell
# Complex Implementation: jump host (bastion) to reach an internal server
ssh -J jumpuser@jumphost alice@internal.server
# Local port forward (tunnel to an internal service):
ssh -L 8080:internal-app:80 alice@bastion
# Now localhost:8080 → internal-app:80 (via bastion)
# Dynamic SOCKS proxy:
ssh -D 1080 alice@server
# SSH config file simplifies frequent connections:
# ~/.ssh/config
# Host prod
# HostName prod.example.com
# User alice
# Port 2222
# IdentityFile ~/.ssh/id_ed25519
rsync (Preferred over scp)
# Complex Implementation: efficient sync with delete + exclude + dry-run
rsync -avz --delete --exclude='*.log' --exclude='node_modules' dir/ alice@server:/tmp/dir/
# -a: archive (recursive, preserve perms/owner/timestamps)
# -v: verbose
# -z: compress
# --delete: delete files on dest not in source
# --exclude: skip patterns
# Trailing slash matters:
# rsync dir/ dest/ → copies CONTENTS of dir into dest (dest/file1)
# rsync dir dest/ → copies dir itself into dest (dest/dir/file1)
# Always dry-run first:
rsync -avzn dir/ alice@server:/tmp/dir/
Firewall
# UFW (Ubuntu)
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw limit 22/tcp # rate-limit SSH (blocks brute force)
sudo ufw --force enable
# firewalld (RHEL)
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --reload
Caveat: Always Allow SSH Before Enabling UFW
# NAIVE: enable firewall without allowing SSH → locked out
sudo ufw enable
# PRODUCTION: allow SSH first
sudo ufw allow 22/tcp
sudo ufw enable
Diagnostics
# Complex Implementation: network diagnostic flow
ping -c 4 8.8.8.8 # is the host reachable? (ICMP)
mtr 8.8.8.8 # where does it fail? (continuous traceroute)
nc -zv example.com 80 # is port 80 open?
sudo tcpdump -i eth0 port 80 -n # capture port 80 traffic
dig example.com # DNS resolving?
curl -v https://example.com # HTTP working?
# Edge Case: ping fails but host is up
# Some firewalls block ICMP. A failed ping doesn't always mean the host is down.
# Try curl or nc instead.
💡 Tips & Tricks
- Idiom: use
ss(notnetstat) for socket statistics —ssis faster, more informative, andnetstatis deprecated.ss -tlnpis the go-to for "what's listening?" - Idiom: use
ip(notifconfig) for interface config —ifconfigis deprecated and missing features.ip -br addrgives a clean one-line-per-interface view. - Idiom: use
rsync(notscp) for file transfer —rsynconly sends diffs, resumes interrupted transfers, and preserves attributes.scpcopies the whole file every time. - Idiom: use
curl -s -o /dev/null -w "%{http_code}"for script-friendly HTTP checks — extracts just the status code, no body. - Idiom: use
dig +shortfor just the IP —dig +short example.comgives93.184.216.34(no headers, no metadata). - Debug: use
mtr(nottraceroute) for live route diagnosis —mtrupdates continuously, showing packet loss per hop.tracerouteis one-shot.
⚠️ Edge Cases & Gotchas
- "Connection refused" vs "Connection timed out": refused = something is listening but refused you (port closed, or firewall REJECT). Timed out = nothing responded (firewall DROP, or network issue). The distinction matters for diagnosis.
/etc/hostsoverrides DNS:/etc/hostsis checked before DNS (controlled bynsswitch.conf:hosts: files dns). A stale entry in/etc/hostscan silently override the correct DNS record.digbypasses/etc/hosts;getent hostsrespects it.resolv.confis often auto-generated: on systemd-resolved or NetworkManager systems, editing/etc/resolv.confdirectly is overwritten on reboot. Useresolvectlor edit Netplan/NetworkManager config.scpcan't do remote-to-remote:scp alice@server1:file alice@server2:filedoesn't work directly — it tries to copy from server1 to your local machine then to server2. Usersyncorssh alice@server1 "scp file alice@server2:file".rsynctrailing slash matters:rsync dir/ dest/copies the contents ofdir.rsync dir dest/copiesdiritself. Always dry-run first (-n).- Firewall can block ICMP (ping): a failed ping doesn't always mean the host is down. Try
curlornc -zv host port. - Port conflicts on
-p:-p 8080:80fails if host port 8080 is already in use. Check withss -tlnp | grep 8080.
🧠 Spot the Bug
An admin wants to test if a web server is reachable, but this command hangs for 60 seconds:
curl http://internal-server:80/
ping internal-server works. dig internal-server returns the correct IP. What's likely wrong, and what should they check?
Answer
"Connection timed out" (curl hanging) + "ping works" means the host is reachable at the network layer (ICMP) but the TCP port (80) is not responding. Likely causes:
- Firewall is DROPping port 80 (DROP = no response, hangs until timeout; REJECT = immediate "connection refused"). Check:
sudo ufw statusorsudo firewall-cmd --list-allorsudo iptables -L -n. - The web server isn't running and the firewall is set to DROP (not REJECT) — so instead of "connection refused," it hangs. Check:
ss -tlnp | grep :80. - A firewall on the path (not the server itself) is blocking port 80. Check:
mtr --tcp --port 80 internal-server.
# Diagnostic flow:
ss -tlnp | grep :80 # is the server listening?
nc -zv internal-server 80 # is the port open? (gives clearer error than curl)
sudo iptables -L -n # local firewall rules
mtr --tcp --port 80 internal-server # path diagnostic
The key insight: "connection refused" (fast) vs "connection timed out" (slow) tells you whether the firewall is REJECTing or DROPping, and whether anything is listening at all.