The Linux a network engineer actually needs: the modern ip/ss/ethtool toolset, virtual networking, tuning for latency, and a layer-by-layer troubleshooting drill.
As a network engineer you rarely write C on Linux, but you live on it: your traffic generators, probes, route reflectors, load balancers, containers and NFV all run there. The interview goal is to be fluent at the shell — read interface counters, follow a route, inspect sockets, capture packets, and reason about where a problem sits. This page front-loads a basics primer, then goes deep on networking.
The single most valuable habit: know the iproute2 tools (ip,ss,bridge) that replacedifconfig/netstat/route, and be able to walk a fault L1 → L7 naming the exact command at each layer. Everything below builds to that.
Everything hangs off a single root / (no drive letters). The directories worth knowing:
| Path | What lives there |
|---|---|
/etc | System config — /etc/hosts, /etc/resolv.conf, /etc/network/, /etc/netplan/, /etc/sysctl.conf, systemd units. |
/proc, /sys | Kernel virtual filesystems — live state. /proc/net/, /sys/class/net/<if>/, tunables under /proc/sys/. |
/var/log | Logs — syslog/messages, plus the systemd journal under /var/log/journal. |
/bin /sbin /usr | Binaries; /sbin is admin tools (ip, ss historically here). |
/dev | Device nodes. |
/home /root | User home dirs; root's home. |
/tmp /run | Ephemeral scratch; /run is tmpfs for runtime state (pidfiles, sockets). |
A process has a PID; you inspect and control it by PID. ps is a snapshot, top/htop are live.
ps aux # every process, BSD-style columns
ps -ef | grep bird # find a routing daemon
top # live CPU/mem; press 1 to expand per-CPU, M sort by mem
htop # nicer top: tree view, per-core meters, F9 to kill
pidof frr ; pgrep -a snmpd # PID by name
kill 4021 # send SIGTERM (15) — polite "please exit"
kill -9 4021 # SIGKILL — cannot be caught, last resort
kill -HUP 4021 # SIGHUP (1) — many daemons reload config on this
pkill -f "tcpdump.*eth0" # match on full command line
| Signal | Num | Meaning |
|---|---|---|
SIGTERM | 15 | Default kill. Graceful shutdown — the process can clean up. |
SIGKILL | 9 | Force kill, uncatchable. Leaves no cleanup — use when TERM fails. |
SIGHUP | 1 | Terminal hangup; by convention "reload config" for daemons. |
SIGINT | 2 | Ctrl-C from the terminal. |
SIGSTOP/SIGCONT | 19/18 | Pause / resume a process. |
ls -l shows rwx triads for user / group / other. Octal: r=4, w=2, x=1.
-rw-r--r-- 1 root root 648 config.yaml # 644: owner rw, others r
-rwxr-xr-x 1 root root 1.2M /usr/sbin/ip # 755: owner rwx, others rx
chmod 600 ~/.ssh/id_rsa # lock a private key to owner-only
chmod +x deploy.sh # make executable
chown netops:netops app.cfg # change owner:group
umask 022 # default new-file mask
An inode is the on-disk record of a file: its type, permissions, owner, size, timestamps,
link count, and pointers to the data blocks — everything except the name. The name lives in a
directory entry that maps a filename → an inode number. That indirection explains hard links,
and why mv within a filesystem is instant (it only rewrites a directory entry).
ls -i file # show the inode number
stat file # inode + all metadata (perms, size, atime/mtime/ctime, links)
df -i # inode usage per filesystem
df shows free space? You're out of inodes —
check df -i (millions of tiny files, e.g. a mail spool or cache).df full but du doesn't add up? A deleted file still held open by a
process — space isn't reclaimed until the fd closes. Find it with lsof | grep deleted.A file descriptor (fd) is a per-process integer handle to an open file — and in Linux
"everything is a file," so sockets, pipes, and devices are fds too. 0/1/2 are
stdin/stdout/stderr.
ls -l /proc/<pid>/fd # every fd a process holds (files, sockets, pipes)
lsof -p <pid> # same, human-readable
lsof -i :179 # who has the BGP port open
ulimit -n # this shell's open-fd limit (soft)
A busy network daemon (many connections = many socket fds) can hit EMFILE "too many open
files" — raise the per-process limit (ulimit -n / a systemd unit's
LimitNOFILE=) and the system ceiling (fs.file-max). A classic production outage.
| Family | Tool | Common commands |
|---|---|---|
| Debian / Ubuntu | apt (dpkg) | apt update, apt install tcpdump, apt list --installed, dpkg -l |
| RHEL / CentOS / Fedora | dnf (was yum) | dnf install iproute-tc, dnf list installed, rpm -qa |
| SUSE | zypper | zypper in wireshark |
which tcpdump / dpkg -S $(which ip) / rpm -qf $(which ip) tell you which package owns a binary — handy when a tool is missing.
systemd is the init system and service manager on nearly all modern distros. Services are "units."
systemctl status frr # is it running? recent log lines, PID, memory
systemctl start frr # start now
systemctl stop frr # stop now
systemctl restart frr # bounce it
systemctl reload frr # re-read config without a full restart (if supported)
systemctl enable frr # start automatically at boot
systemctl disable frr # don't start at boot
systemctl enable --now frr # enable + start in one shot
systemctl list-units --type=service --state=running
journalctl -u frr # all logs for one unit
journalctl -u frr -f # follow (tail -f) live
journalctl -u frr --since "10 min ago"
journalctl -k -b # kernel messages, this boot (like dmesg)
journalctl -p err -b # only errors this boot
The chain from power-on to a login prompt — know it so you can say where a boot hangs:
| Stage | What happens |
|---|---|
| Firmware (UEFI/BIOS) | POST, then loads the bootloader from the EFI system partition / MBR. |
| Bootloader (GRUB) | Presents kernels, loads the selected vmlinuz + initramfs into memory, passes the kernel cmdline (e.g. root=). |
| Kernel + initramfs | Kernel initializes hardware/drivers; the initramfs is a temporary root with just enough drivers to find and mount the real root filesystem. |
| PID 1 (systemd) | Kernel mounts real root and execs /sbin/init → systemd, the first userspace process. |
| Default target | systemd activates units up to the default target (multi-user.target for servers, graphical.target for desktops) — networking, sshd, your daemons. |
systemctl get-default # multi-user.target (≈ old runlevel 3)
systemd-analyze # total boot time (firmware/loader/kernel/userspace)
systemd-analyze blame # slowest units — find what delays boot
systemd-analyze critical-chain # the dependency path that gated boot
journalctl --list-boots # every recorded boot
journalctl -k -b -1 # kernel log of the *previous* boot (diagnose a crash)
systemd targets replaced SysV runlevels; a unit starts at boot when it's
enabled (a symlink into the target's .wants). Networking is brought up here by
whatever's in use — systemd-networkd, NetworkManager, netplan, or ifupdown.
whoami ; id # who am I; my uid/gid/groups
sudo ip link set eth0 down # run one command as root
sudo -i # interactive root shell
usermod -aG sudo alice # add user to a group (needs re-login)
visudo # edit /etc/sudoers safely (syntax-checked)
Most networking commands that change state (bring a link up, add a route, capture packets) need root or sudo; read-only inspection (ip addr, ss) usually does not.
The classic net-tools suite (ifconfig, route, arp, netstat) is deprecated and often not even installed on new systems. Learn the iproute2 replacements — interviewers notice when you reach for ifconfig.
| Old (net-tools) | New (iproute2 / modern) | Does what |
|---|---|---|
ifconfig | ip addr, ip link | Show/set IP addresses and interface (link) state. |
route -n | ip route | Show/manipulate the routing table. |
arp -a | ip neigh | ARP / NDP neighbor cache (L2↔L3 mappings). |
netstat -tulpn | ss -tulpn | Listening/established sockets, owning process. |
netstat -i | ip -s link | Per-interface packet/byte/error counters. |
brctl | bridge, ip link ... type bridge | Linux bridge / FDB management. |
iptunnel, vconfig | ip tunnel, ip link ... type vlan | Tunnels and 802.1Q VLAN subinterfaces. |
Beyond iproute2, the tools you carry everywhere:
| Tool | Use it for |
|---|---|
ethtool | NIC-level truth: link/speed/duplex, driver counters (-S), optics DOM (-m), offloads (-k), ring buffers (-g), coalescing (-c). |
tcpdump / tshark | Packet capture and decode at the CLI. tshark = Wireshark's engine headless. |
ping / traceroute / mtr | Reachability, path, and continuous per-hop loss/latency. |
dig / host | DNS queries (forward, reverse, specific record types, specific servers). |
nc (netcat) / ncat | Open a TCP/UDP connection, test a port, throw a quick listener. |
curl / wget | HTTP(S) client — curl -v shows the whole L4→L7 exchange. |
nmcli / networkctl | Manage NetworkManager / systemd-networkd connections and state. |
iperf3 | Throughput testing between two hosts. |
# Addresses & links
ip addr show # all interfaces + IPs (short: ip a)
ip -br addr # one tidy line per interface (-brief)
ip link show eth0 # L2 state: UP/DOWN, MTU, MAC
ip link set eth0 up # admin-up the interface
ip addr add 10.0.0.5/24 dev eth0 # add an IP
ip link set eth0 mtu 9000 # jumbo frames
# Neighbors (ARP/NDP)
ip neigh show # REACHABLE / STALE / FAILED entries
ip neigh flush dev eth0 # clear the cache (force re-ARP)
# Sockets
ss -tulpn # TCP+UDP listeners, numeric, with PID
ss -tn state established # established TCP, no name resolution
ss -s # summary totals by state
When someone says "the link looks flaky," this is where you go. Two sources: the kernel's per-interface counters (ip -s link) and the driver's detailed counters (ethtool -S).
ip -s link show eth0
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
# RX: bytes packets errors dropped missed mcast
# ... ... 0 0 0 ...
# TX: bytes packets errors dropped carrier collsns
# ... ... 0 0 0 0
UP = admin-up; LOWER_UP = physical carrier present. Admin-up but no LOWER_UP means no light / no link partner — a Layer-1 problem.
ethtool -Sethtool eth0 # Speed, Duplex, Link detected: yes/no, autoneg
ethtool -S eth0 | grep -E "err|drop|crc|miss|no_buffer|fifo"
| Counter | Points to |
|---|---|
rx_crc_errors, rx_errors | Bad frames on the wire — cabling, optics, duplex mismatch, dirty fiber. Classic L1. |
rx_missed_errors, rx_no_buffer_count, rx_fifo_errors | NIC received but the host couldn't drain fast enough — ring buffer / CPU / IRQ pressure. Bump ring size, fix IRQ affinity. |
rx_dropped / tx_dropped | Kernel dropped (no buffer, qdisc full, bad VLAN). Cross-check with ip -s link. |
tx_carrier_errors | Lost carrier during TX — physical/link flapping. |
collisions | Should be 0 on any modern full-duplex link; non-zero = duplex mismatch (half-duplex somewhere). |
ethtool -g eth0 # ring buffer: current vs max (RX/TX descriptors)
ethtool -G eth0 rx 4096 # enlarge RX ring to reduce rx_missed under bursts
ethtool -m eth0 # SFP/QSFP DOM: Rx/Tx optical power, temp, vendor
# low/absent Rx power => dirty/bent fiber or dead far end
ip link show eth0 | grep mtu # confirm MTU matches both ends (mismatch => black-holed
# large frames: small pings work, big transfers hang)
dmesg -T | grep -i eth0 # link up/down events with timestamps == flaps
journalctl -k | grep -i "link is" # same from the journal
Linux is a real router. The kernel keeps a FIB (forwarding table) you read with ip route, and it can consult multiple tables selected by policy rules.
ip route # main table: default via ..., connected + static
ip route get 8.8.8.8 # which route/source/dev would be used for this dst
ip route add 10.20.0.0/16 via 10.0.0.1 dev eth0 metric 100
ip route add default via 10.0.0.1
ip route replace default via 10.0.0.1 metric 100 # idempotent change
# Lower metric wins between two candidate routes of equal prefix length;
# longest-prefix match wins across different prefix lengths.
Beyond the destination, Linux can route on source address, incoming interface, fwmark, etc. Rules (ip rule) map traffic to routing tables (in /etc/iproute2/rt_tables).
ip rule show
# 0: from all lookup local
# 32766: from all lookup main
# 32767: from all lookup default
# Source-based routing: traffic sourced from 10.0.0.5 uses table 100
echo "100 uplink2" >> /etc/iproute2/rt_tables
ip route add default via 10.0.1.1 dev eth1 table 100
ip rule add from 10.0.0.5 table 100 priority 1000
ip route show table 100 # inspect a specific table
ip rule show # confirm rule priority ordering (lower = first)
Multiple gateways / ECMP: give default several nexthops for load-sharing, or use ip rule for deterministic policy. Always verify with ip route get <dst> — it resolves rules + tables + metrics and tells you the actual forwarding decision, source IP included. That one command settles most "why did it go out the wrong interface" questions.
Containers and VMs are built from a handful of kernel primitives. Knowing them explains how Docker/Kubernetes/OpenStack networking works under the hood.
| Primitive | What it is |
|---|---|
| Network namespace | An isolated copy of the whole network stack — its own interfaces, routes, ARP table, sockets. This is the "network" of a container. |
| veth pair | A virtual cable: two linked interfaces. Put one end in a namespace, the other on a bridge — that's how a container reaches the host. |
| Linux bridge | A software L2 switch. Learns MACs into an FDB, floods unknowns. Docker's default docker0 is a bridge. |
| VLAN subinterface | 802.1Q tag on a parent NIC — eth0.100 carries tagged VLAN 100 traffic. |
| Bond / team | Aggregate several NICs for redundancy (active-backup) or throughput (LACP / 802.3ad). |
# Network namespaces
ip netns add red
ip netns exec red ip addr # run a command inside the namespace
ip netns list
# veth pair connecting host to namespace
ip link add veth0 type veth peer name veth1
ip link set veth1 netns red
ip addr add 10.10.0.1/24 dev veth0 ; ip link set veth0 up
ip netns exec red ip addr add 10.10.0.2/24 dev veth1
ip netns exec red ip link set veth1 up
# Linux bridge (software switch)
ip link add br0 type bridge
ip link set eth0 master br0 # enslave a port
ip link set br0 up
bridge link # bridge ports + state
bridge fdb show # learned MAC table (forwarding database)
# 802.1Q VLAN subinterface
ip link add link eth0 name eth0.100 type vlan id 100
ip addr add 192.168.100.2/24 dev eth0.100
# Bonding (active-backup)
ip link add bond0 type bond mode active-backup
ip link set eth1 master bond0 ; ip link set eth2 master bond0
cat /proc/net/bonding/bond0 # active slave, link status, failover count
On a trading or HFT box, the NIC and kernel path are tuned for predictable low latency, sometimes trading throughput and CPU for it. See Low-Latency & Trading Networks for the market-data context; here are the Linux knobs.
RSS (Receive Side Scaling) spreads incoming flows across multiple RX queues, each with its own interrupt, so multiple cores share the load. IRQ affinity pins each queue's interrupt to a specific core — you keep NIC interrupts off your latency-critical application cores (and on the same NUMA node as the NIC).
ethtool -l eth0 # number of RX/TX queues (channels)
cat /proc/interrupts | grep eth0 # which CPU services each queue's IRQ
# pin IRQ 129 to CPU 2:
echo 4 > /proc/irq/129/smp_affinity # bitmask: bit2 = CPU2
# (disable irqbalance so it doesn't move them back)
Offloads batch packets to cut per-packet CPU. Great for throughput, but they add and hide latency and can mangle what a local capture shows.
| Offload | What it does | Latency stance |
|---|---|---|
| TSO (TCP Segmentation Offload) | Kernel hands the NIC a big buffer; NIC cuts it into MSS segments on TX. | Often disabled for low latency / determinism. |
| GSO | Software equivalent of TSO, done just before the driver. | Disable alongside TSO. |
| GRO / LRO (Receive coalescing) | Merge many small RX packets into one before the stack sees them. | Disable — coalescing delays delivery and distorts captures. |
ethtool -k eth0 # list offload states
ethtool -K eth0 tso off gso off gro off lro off # turn them off (note -K = set)
ethtool -c eth0 # current coalescing settings
ethtool -C eth0 rx-usecs 0 rx-frames 1 # fire an IRQ per packet: lowest latency,
# highest CPU/interrupt rate
Coalescing waits to batch interrupts (good for throughput, bad for latency). Setting rx-usecs 0 minimizes delay. Busy-polling goes further: the app spins asking the NIC for packets instead of sleeping on an interrupt — set net.core.busy_poll/busy_read, or use SO_BUSY_POLL. Zero interrupt latency at the cost of a pegged core.
sysctl net.core.rmem_max net.core.wmem_max # max socket buffer sizes
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem # min/default/max TCP buffers
sysctl net.ipv4.tcp_congestion_control # cubic (default) / bbr
sysctl net.core.netdev_max_backlog # per-CPU input queue depth
sysctl -w net.ipv4.tcp_low_latency=1 # (legacy) favor latency
sysctl -w net.ipv4.tcp_timestamps=1 # RTT/PAWS; sometimes off for HFT
# persist in /etc/sysctl.conf or /etc/sysctl.d/
For a fat, high-RTT path you raise rmem/wmem so the TCP window can reach the bandwidth-delay product. For low latency you keep buffers modest to avoid bufferbloat, and pick the right congestion control (BBR on lossy/buffered paths, CUBIC otherwise).
lscpu, numactl --hardware, cat /sys/class/net/eth0/device/numa_node, run with numactl --cpunodebind=0 --membind=0).When something "can't connect," walk the stack bottom-up and stop at the first layer that fails. Name the command at each step — that structure is what interviewers are grading.
| Layer | Question | Command |
|---|---|---|
| L1 — physical | Link up? Right speed/duplex? Optics OK? Errors climbing? | ethtool eth0, ethtool -m eth0, ethtool -S eth0, ip -s link |
| L2 — link/ARP | Do I have the neighbor's MAC? Bridge learning it? Right VLAN? | ip neigh, bridge fdb show, bridge vlan |
| L3 — routing/IP | Right route & source? Gateway reachable? | ip route get <dst>, ping <gw>, traceroute/mtr <dst> |
| L4 — transport | Is the port open / listening? SYN getting through? | ss -tnp, nc -vz host 443, ss -tn state syn-sent |
| DNS | Does the name resolve, from the right server? | dig A host, dig @8.8.8.8 host, cat /etc/resolv.conf |
| L7 — application | Does the service actually answer? TLS? Redirects? | curl -v https://host/, curl -Iv, openssl s_client -connect host:443 |
Still stuck? Capture the packets — the ground truth:
tcpdump -ni eth0 host 10.0.0.5 and port 443 # numeric, one iface, filtered
tcpdump -ni eth0 'tcp[tcpflags] & (tcp-syn|tcp-rst) != 0' # SYNs and RSTs only
tcpdump -ni eth0 -w /tmp/cap.pcap host 10.0.0.5 # write for Wireshark
tshark -ni eth0 -f "port 179" -Y "bgp" # capture + decode BGP
Where the logs are: journalctl -u <svc> -f (a service), journalctl -k / dmesg -T (kernel — link flaps, OOM, drops), /var/log/syslog or /var/log/messages (general), and the app's own log under /var/log/.
Reflex checks: a lost SYN with no RST = firewall/ACL dropping silently (compare with an RST, which means "port closed / reachable but refused"). ping works but the app doesn't = it's L4+ (port/firewall/service), not the network path. Big transfers hang while small ones work = MTU/PMTU black hole.
Linux packet filtering runs in netfilter. The old front-end is iptables (per-protocol, ordered rules in chains); the modern replacement is nftables (nft) with one unified syntax, sets, and maps. On new distros iptables is often a compatibility shim over nftables.
| iptables | nftables | |
|---|---|---|
| Structure | Built-in tables (filter, nat, mangle) with fixed chains (INPUT, FORWARD, OUTPUT, PRE/POSTROUTING) | You create tables and chains; hook chains to netfilter points |
| IPv4/IPv6 | Separate (iptables / ip6tables) | Unified (inet family) |
| Matching sets | Needs ipset | Native sets/maps — faster, atomic updates |
# iptables — inspect & add
iptables -L -n -v --line-numbers # list filter table with counters
iptables -t nat -L -n -v # inspect NAT rules
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -I INPUT 1 -s 10.0.0.0/8 -j DROP
# nftables — inspect & add
nft list ruleset # everything, current state
nft add rule inet filter input tcp dport 22 accept
# conntrack — the stateful connection table (NAT + stateful rules rely on it)
conntrack -L # list tracked flows
conntrack -S # per-CPU stats: inserts, drops, early_drop
sysctl net.netfilter.nf_conntrack_max # table size limit
sysctl net.netfilter.nf_conntrack_count # current entries (full table => new
# connections dropped — a subtle outage)
Conntrack is the state engine behind NAT and -m state ESTABLISHED,RELATED rules. A conntrack table filling up (count nearing max, rising drops) silently drops new connections on busy boxes — a favorite "the load balancer randomly refuses connections" scenario.
ifconfig, netstat, and route, and what does each new command show?rx_crc_errors vs rx_missed_errors rising — what does each tell you and where do you look?ip rule + tables, ip route get)journalctl -u -f, journalctl -k/dmesg)