Skip to content

ip

Ask a Linux server what its own address is and you'll get an answer from one of two commands. ifconfig is the one most tutorials still show, and it's the one that has been unmaintained in the net-tools package for well over a decade. ip is the one that actually reflects what the kernel is doing — and on a minimal Ubuntu Server or a container image, it's frequently the only one installed.

That last point matters more than the philosophical argument about which is better. Exec into a Debian-based container and type ifconfig, and you'll usually get command not found. Type ip a and you get an answer.

One command, several objects

ip isn't a single tool with flags; it's a dispatcher. The word right after ip names the object you're asking about, and the word after that names the operation:

ip <object> <command> [arguments]

Four objects cover almost everything a DevOps engineer needs day to day:

Object What it manages Most-used form
link Layer 2 — the interface itself, its MAC address, its MTU, whether the cable is up ip link show
addr Layer 3 — the IP addresses bound to an interface ip addr show
route The routing table — where packets go next ip route show
neigh The ARP/NDP neighbour cache — which MAC belongs to which local IP ip neigh show

Every object name can be abbreviated to any unambiguous prefix, which is why you'll see ip a, ip r, and ip l in other people's notes and in your own muscle memory soon enough. show is the default operation, so ip addr and ip addr show are the same thing.

Reading ip addr line by line

ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.10/24 brd 192.168.1.255 scope global dynamic eth0
       valid_lft 84532sec preferred_lft 84532sec
    inet6 fe80::5054:ff:fe12:3456/64 scope link
       valid_lft forever preferred_lft forever

Your interface name, addresses, and MAC will differ. What each part means:

  • 2: — the kernel's interface index. Not cosmetic: tcpdump and packet-capture filters occasionally identify an interface by this number, and inside containers you'll see it referenced when tracing which host-side interface a container's virtual interface is paired with.
  • <BROADCAST,MULTICAST,UP,LOWER_UP> — interface flags. UP means an administrator has enabled the interface. LOWER_UP means the physical layer has a signal — the cable is plugged in and the link partner answers. Those two are separate for a reason: an interface can be UP with no LOWER_UP, which is exactly what an unplugged cable or a dead switch port looks like.
  • mtu 1500 — the largest payload this interface will send in one frame, the standard Ethernet value. A tunnel or VPN interface will show something smaller.
  • state UP — the operational state, the summary of the two flags above.
  • link/ether 52:54:00:… — the MAC address. The 52:54:00 prefix here is QEMU/KVM's, so this output is from a virtual machine.
  • inet 192.168.1.10/24 — the IPv4 address and its prefix length, together in one field. This is a genuine improvement over ifconfig, which printed the address and the mask as two separate values you had to mentally recombine.
  • scope global dynamic — reachable from anywhere (as opposed to scope host, which is loopback-only), and assigned by DHCP rather than configured statically.
  • valid_lft 84532sec — how much of the DHCP lease remains. When someone insists a server has a static address and this line shows a countdown, they're wrong, and that's usually the whole bug.

When you only want the summary across every interface, the brief form fits on one line each:

ip -brief addr show
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             192.168.1.10/24 fe80::5054:ff:fe12:3456/64
docker0          DOWN           172.17.0.1/16

-brief can be shortened to -br, and it accepts -c alongside it for colour. This is the form worth building the habit around — on a host with a dozen virtual interfaces, the full output scrolls off the screen and the brief form doesn't.

The routing table, and the one command that answers the real question

ip route show
default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.10 metric 100
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10 metric 100

Two entries, and between them they decide the fate of every packet this host sends.

The second line is the directly connected route: everything in 192.168.1.0/24 is on the same wire, so the host resolves the destination's MAC address with ARP and sends the frame straight there. No router involved. proto kernel means nobody configured this route — the kernel created it automatically the moment an address with a /24 prefix was assigned.

The first line is the default route: anything that didn't match a more specific entry goes to 192.168.1.1, the router. A host with no default route can reach its own LAN perfectly and nothing else, which produces the confusing symptom of "ping 192.168.1.5 works but ping 8.8.8.8 says network is unreachable."

Reading the table and mentally running the longest-prefix match is fine for two routes. On a host with a VPN, a container bridge, and a second NIC it stops being fine, and there's a better command:

ip route get 1.1.1.1
1.1.1.1 via 192.168.1.1 dev eth0 src 192.168.1.10 uid 1000
    cache

This asks the kernel to make the actual decision for one specific destination and report it: which gateway, out of which interface, and — the field people miss — which source address the packet will carry. src 192.168.1.10 is what the far end will see and what its firewall rules will match against. On a multi-homed host, "the firewall allows our office IP but the traffic arrives from the wrong address" is a real and common outage, and this one line diagnoses it.

ip route get is the routing command to reach for first. It replaces guesswork about which of six routes wins with the kernel's own answer.

Nothing here survives a reboot

ip talks to the running kernel. It does not write a configuration file, and it has no concept of one.

sudo ip addr add 192.168.1.50/24 dev eth0

That address exists immediately and is gone after systemctl restart systemd-networkd or a reboot. Which is occasionally exactly what you want — a temporary second address to test a migration, removed with ip addr del when you're done — but it's a trap when you think you've configured a server. Persistent configuration on Ubuntu goes through netplan, and this course's Linux section covers that separately.

Changing routes or addresses over SSH can end the session that's running the command

sudo ip route del default or sudo ip addr flush dev eth0 on a remote host takes effect on the packet you're typing into. There is no confirmation prompt and no undo.

Before touching either, save the current state so you can put it back:

ip addr show > ~/net-before.txt
ip route show >> ~/net-before.txt

Then make the change only if one of these is true: you're in a lab VM with console access, you have out-of-band access (a cloud provider's serial console, IPMI), or you've queued an automatic rollback — for example sudo sh -c 'sleep 120; ip route add default via 192.168.1.1 dev eth0' & before the change, so the working route comes back on its own if you lock yourself out. Cancel the timer once you've confirmed the new configuration works.

A short investigation: the interface that's up but deaf

A monitoring alert says a database host stopped answering. SSH still works, because you're coming in over a second interface. Start at the bottom:

ip -br link show
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             52:54:00:12:34:56 <BROADCAST,MULTICAST,UP,LOWER_UP>
eth1             DOWN           52:54:00:aa:bb:cc <NO-CARRIER,BROADCAST,MULTICAST,UP>

eth1 is where the database listens. Its flags say UP — an administrator enabled it — but NO-CARRIER, and LOWER_UP is absent. The kernel is willing; the physical layer isn't answering. That rules out addressing, routing, firewalls, and the database itself in one command, and points at a cable, a switch port, or a virtual NIC detached from its virtual switch.

Compare that with a different failure on the same interface:

eth1             UP             52:54:00:aa:bb:cc <BROADCAST,MULTICAST,UP,LOWER_UP>

Link is fine here. If the service is still unreachable, the next question is whether eth1 has an address at all (ip -br addr), and after that whether anything is listening on the port — which is ss's job, not ip's.

Interface counters can settle the "is it dropping traffic" question without a packet capture:

ip -s link show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:aa:bb:cc brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
     184927362   204817      0       0       0    1204
    TX:  bytes packets errors dropped carrier collsns
      37281940   152003      0       0       0       0

Errors and drops at zero means the NIC and its driver are not the problem, and you can stop looking there. Non-zero and climbing means they are. (The exact column headers shifted between iproute2 releases — older versions print overrun where newer ones print missed — so read the header row rather than counting columns.)

Practice

  1. Run ip -br addr and ifconfig on the same machine. List two pieces of information the ip output gives you that the ifconfig output doesn't.
  2. Use ip route get with three destinations: 127.0.0.1, another address on your own LAN, and a public address. Explain, for each, why the chosen interface and source address are what they are.
  3. Look at ip neigh show. Find one entry in STALE state and one in REACHABLE, and work out from the output alone which of those two machines this host has spoken to most recently.
  4. On a lab VM only, add a second address to an interface with ip addr add, confirm it with ip -br addr, reboot, and confirm it's gone. Then explain in one sentence why a colleague's "I definitely set that IP" claim deserves a valid_lft check.

Exercise 3 hints at something ip deliberately doesn't answer. It shows you the machine's addresses, its routes, and its neighbours — the plumbing. It says nothing about which programs are attached to that plumbing, which port they claimed, or whether the port a client is failing to reach is even open. That's a different question and a different tool.

Sources