If you learned networking on Cisco gear and then inherited a fleet of Linux boxes, the ip command is the single tool that closes the gap. It is the Linux equivalent of show ip interface brief, show ip route, show arp and half of interface configuration mode, all behind one binary. It is also the tool that most engineers use at about ten percent of its capability, because they learned three invocations years ago and never went back.
This guide is the full pass. It covers the grammar that makes ip predictable, the four objects you will use every day (address, link, route, neigh), the output modifiers that turn a wall of text into something readable, how to make changes and why none of them survive a reboot, and the interface types you can create out of thin air. Every command below was run on a Debian 13 host wired into a Cisco Modeling Labs topology, so the output is what you will see, not what the man page implies. It is the anchor article for the Linux networking commands cluster.
The tool you are replacing
ifconfig, route and arp come from the net-tools package, which stopped being actively developed around 2001. It still works, and Debian still ships version 2.10, but it talks to the kernel through an interface that the kernel stopped extending a long time ago. Anything added to Linux networking since then is either invisible to it or displayed wrong.
ip comes from iproute2, which talks to the kernel over netlink. Netlink is the modern, structured, still-evolving interface, which is why ip can show you address lifetimes, policy routing tables, neighbor states and VRFs while ifconfig cannot. On this host:
j@llmbits:~$ ip -V
ip utility, iproute2-6.15.0, libbpf 1.5.0The full argument for the switch, with the legacy commands and their replacements side by side, is in ifconfig, route and netstat: mapping legacy net-tools to iproute2. This article assumes you have already accepted the premise.
The grammar: object, then command, then arguments
ip is not one command. It is a dispatcher for about thirty subsystems, and every invocation has the same shape:
ip [OPTIONS] OBJECT { COMMAND | help }Ask it what the objects are and it tells you:
j@llmbits:~$ ip help
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
ip [ -force ] -batch filename
where OBJECT := { address | addrlabel | fou | help | ila | ioam | l2tp | link |
macsec | maddress | monitor | mptcp | mroute | mrule |
neighbor | neighbour | netconf | netns | nexthop | ntable |
ntbl | route | rule | sr | stats | tap | tcpmetrics |
token | tunnel | tuntap | vrf | xfrm }
OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
-h[uman-readable] | -iec | -j[son] | -p[retty] |
-f[amily] { inet | inet6 | mpls | bridge | link } |
-4 | -6 | -M | -B | -0 |
-l[oops] { maximum-addr-flush-attempts } | -echo | -br[ief] |
-o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
-rc[vbuf] [size] | -n[etns] name | -N[umeric] | -a[ll] |Two conveniences follow from this. First, every object accepts help, so ip route help and ip neigh help give you a syntax summary without leaving the shell. Second, objects and commands can be abbreviated to any unambiguous prefix. ip a is ip address, ip r is ip route, ip n is ip neighbour, and ip a s is ip address show. Type the short form interactively and the long form in scripts, because a future object starting with the same letter can make an abbreviation ambiguous and break your script years later.
The command defaults to show (or list) when you leave it off, which is why ip addr and ip addr show do the same thing.
ip address: what is actually configured
This is the one you will type most. With no arguments it dumps every address on every interface:
j@llmbits:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:0c:29:b1:cc:3d brd ff:ff:ff:ff:ff:ff
altname enp11s0
altname enx000c29b1cc3d
inet 192.168.88.156/24 brd 192.168.88.255 scope global dynamic noprefixroute ens192
valid_lft 6409sec preferred_lft 6409sec
inet6 fd64:f725:df42:4f01:8498:4c62:15f0:8139/64 scope global temporary dynamic
valid_lft 1775sec preferred_lft 1775sec
inet6 fd64:f725:df42:4f01:20c:29ff:feb1:cc3d/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 1775sec preferred_lft 1775sec
inet6 fe80::20c:29ff:feb1:cc3d/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
altname enp19s0
altname enx000c29b1cc47
inet 10.77.0.100/24 scope global ens224
valid_lft forever preferred_lft foreverThere is more in there than most people read. Working left to right on the ens192 block:
The flags in angle brackets are the interface's administrative and operational state. UP means the admin brought it up. LOWER_UP means the driver sees carrier. An interface that is UP without LOWER_UP is the Linux equivalent of "up / down" on a Cisco box: you configured it, the cable is not there. That distinction is the first thing to check and the easiest one to miss.
qdisc mq is the queueing discipline, in this case multiqueue. state UP is the operational state, and UNKNOWN on the loopback is normal rather than broken (loopback has no carrier concept).
The altname lines are alternative interface names the kernel will also answer to. ip link show enp19s0 works exactly as well as ip link show ens224 here. If you have ever written a script against a predictable-name scheme and had it break on a different distro, altnames are the fix.
valid_lft and preferred_lft are the address lifetimes. On the DHCP-managed interface they count down; the moment they hit zero without a renewal the address goes away. On the statically configured interface they read forever. ifconfig shows you neither, which is why a host that is about to lose its address looks identical to one that is not.
scope matters more than it looks. global is routable anywhere, link is valid only on this segment (every IPv6 fe80:: address), and host never leaves the machine. dynamic means DHCP or SLAAC put it there. noprefixroute means the kernel did not automatically add a connected route for it, which NetworkManager sets so it can manage routes itself.
Filter that dump down with the standard modifiers:
j@llmbits:~$ ip -br addr show
lo UNKNOWN 127.0.0.1/8 ::1/128
ens192 UP 192.168.88.156/24 fd64:f725:df42:4f01:8498:4c62:15f0:8139/64 fd64:f725:df42:4f01:20c:29ff:feb1:cc3d/64 fe80::20c:29ff:feb1:cc3d/64
ens224 UP 10.77.0.100/24-br (brief) is the closest thing Linux has to show ip interface brief, and it is the command to reach for when someone asks "what is this box's address" over the phone. Add -4 to drop the IPv6 noise and a device name to narrow it further:
j@llmbits:~$ ip -4 addr show dev ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
altname enp19s0
altname enx000c29b1cc47
inet 10.77.0.100/24 scope global ens224
valid_lft forever preferred_lft foreverip link: the layer 2 view
ip link is the same interfaces with the addresses stripped out. It is where MAC, MTU, queue length and driver state live.
j@llmbits:~$ ip link show dev ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
altname enp19s0
altname enx000c29b1cc47Add -s and you get counters, which is the Linux answer to show interfaces:
j@llmbits:~$ ip -s link show dev ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped missed mcast
250454 3685 0 0 0 0
TX: bytes packets errors dropped carrier collsns
9332264 3388 0 0 0 0
altname enp19s0
altname enx000c29b1cc47Double it up as -s -s and the error columns expand into the breakdown that tells you which layer is unhappy:
j@llmbits:~$ ip -s -s link show ens224
RX errors: length crc frame fifo overrun
0 0 0 0 0
TX: bytes packets errors dropped carrier collsns
2030 27 0 0 0 0
TX errors: aborted fifo window heartbt transns
0 0 0 0 5CRC errors point at cabling or a duplex mismatch. FIFO and overrun point at the host not draining the ring fast enough. transns counts carrier transitions, so a number that climbs while you watch is a link flapping. For the layer below this (actual speed, duplex, offloads and driver statistics), you need ethtool, because the kernel's generic counters stop where the NIC driver begins.
ip route: where packets go
j@llmbits:~$ ip route show
default via 192.168.88.1 dev ens192 proto dhcp src 192.168.88.156 metric 101
10.77.0.0/24 dev ens224 proto kernel scope link src 10.77.0.100
10.77.0.0/16 via 10.77.0.1 dev ens224
192.168.88.0/24 dev ens192 proto kernel scope link src 192.168.88.156 metric 101Four routes, and each field earns its place. proto records who installed the route: kernel for the connected routes the kernel derived from the interface addresses, dhcp for the default route a lease supplied, static for one you typed, and bird or bgp if a routing daemon is running. src is the source address the kernel will use for locally originated traffic on that route, which is the setting that decides which IP your monitoring traffic appears to come from on a multihomed box. metric breaks ties between routes of equal prefix length, lower wins.
The single most useful routing command is not show, it is get. It asks the kernel to perform an actual lookup and report the decision:
j@llmbits:~$ ip route get 10.77.3.10
10.77.3.10 via 10.77.0.1 dev ens224 src 10.77.0.100 uid 1000
cache
j@llmbits:~$ ip route get 8.8.8.8
8.8.8.8 via 192.168.88.1 dev ens192 src 192.168.88.156 uid 1000
cacheThis is the equivalent of show ip route 10.77.3.10 on IOS, and it settles arguments. It applies longest-prefix match, policy rules and source selection, then tells you the exit interface, next hop and source address it would use. When someone insists traffic "should" be going out the other interface, this is the command that ends the conversation. Routing tables, metrics, multiple tables and policy rules get the full treatment in ip route: managing the Linux routing table.
ip neigh: the ARP table, with states
j@llmbits:~$ ip neigh show
192.168.88.124 dev ens192 lladdr 34:9f:7b:7f:ca:ee STALE
10.77.0.1 dev ens224 lladdr aa:bb:cc:00:04:00 STALE
192.168.88.1 dev ens192 lladdr 98:ba:5f:11:1b:3a REACHABLE
192.168.88.125 dev ens192 lladdr 58:ef:68:e7:26:1a REACHABLE
fe80::18d8:e4e1:ba78:179e dev ens192 lladdr 1c:b3:c9:02:f4:2f router STALE
fe80::1072:e4df:6063:2046 dev ens192 lladdr c4:f7:c1:09:40:9d router STALE
fe80::c1f:b024:94ab:44c3 dev ens192 lladdr f4:34:f0:69:8a:76 router STALETwo things here that arp -n cannot show you. IPv4 ARP and IPv6 neighbor discovery are the same table, so both appear in one listing. And each entry carries a state: REACHABLE means the kernel has confirmed this neighbor within the last thirty seconds or so, STALE means the mapping is cached but unverified. arp flattens both to the flag C, which hides exactly the information you want during an incident. The full state machine, walked live, is in ARP on Linux: ip neigh, arp and neighbor states.
The output modifiers that make ip usable
These are global options, so they go before the object and work across most of them.
-br briefshow ip interface brief. Works with addr, link and neigh.-s statistics-s -s) for the per-error-type breakdown and neighbor probe counts.-4 / -6-j -p JSONip into a script instead of parsing text with awk.-d details-o onelinegrep behave, since a match no longer loses its continuation lines.The JSON option deserves a demonstration, because it is the difference between a fragile script and a reliable one:
j@llmbits:~$ ip -j -p addr show dev ens224
[ {
"ifindex": 3,
"ifname": "ens224",
"flags": [ "BROADCAST","MULTICAST","UP","LOWER_UP" ],
"mtu": 1500,
"qdisc": "mq",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "00:0c:29:b1:cc:47",
"broadcast": "ff:ff:ff:ff:ff:ff",
"altnames": [ "enp19s0","enx000c29b1cc47" ],
"addr_info": [ {
"family": "inet",
"local": "10.77.0.100",
"prefixlen": 24,
"scope": "global",
"label": "ens224",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
} ]
} ]Piping that into jq -r '.[0].addr_info[0].local' will keep working after an iproute2 upgrade changes the text layout. An awk '{print $2}' against the human output will not. If you are already doing structured collection with the tools in the network automation cluster, ip -j is how Linux hosts join that pipeline.
Making changes, and why they vanish
Everything so far was read-only. The write side follows the same grammar, with add, del, change, replace and set replacing show. Adding a second address to an interface:
j@llmbits:~$ sudo ip addr add 10.77.0.200/24 dev ens224 label ens224:1
j@llmbits:~$ ip addr show dev ens224 | grep inet
inet 10.77.0.100/24 scope global ens224
inet 10.77.0.200/24 scope global secondary ens224:1Note secondary. The first global address in a subnet on an interface is primary and the rest are secondary, exactly as on IOS. The difference from ifconfig is that Linux has always supported multiple addresses per interface natively; the ens224:1 label is a compatibility shim so that ifconfig can see the address at all. You do not need it unless something old is reading the interface list.
Removing it takes the same prefix length you added:
j@llmbits:~$ sudo ip addr del 10.77.0.200/24 dev ens224Changing an MTU shows the write path on ip link, and immediately shows why you should be careful:
j@llmbits:~$ sudo ip link set dev ens224 mtu 1400
j@llmbits:~$ ip link show ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
j@llmbits:~$ ping -c 2 -M do -s 1400 10.77.3.10
--- 10.77.3.10 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1018msA 1400 byte payload with the do-not-fragment bit set no longer fits in a 1400 byte MTU once headers are added, and the kernel refuses to send it. That is the correct behavior and it is worth having seen deliberately before you meet it accidentally across a tunnel. The ping MTU and DF bit article takes that further.
Now the part that catches people. None of these changes persist. ip writes to the running kernel and nothing else. Reboot the host and every address, route and MTU you set by hand is gone, back to whatever the configuration system says. Depending on the distribution that system is NetworkManager, systemd-networkd, or /etc/network/interfaces. On this host it is NetworkManager, which is why nmcli is the tool that makes a change survive. Use ip to test and to troubleshoot, then write the working configuration into whatever owns the interface.
Creating interfaces out of nothing
ip link add creates virtual interfaces, and this is where ip stops having a net-tools equivalent at all. A dot1q subinterface, in one command:
j@llmbits:~$ sudo ip link add link ens224 name ens224.50 type vlan id 50
j@llmbits:~$ sudo ip link set ens224.50 up
j@llmbits:~$ ip -d link show ens224.50
5: ens224.50@ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff promiscuity 0 allmulti 0 minmtu 0 maxmtu 65535
vlan protocol 802.1Q id 50 <REORDER_HDR> addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535The @ens224 suffix names the parent device, and -d is what reveals vlan protocol 802.1Q id 50. Without -d you would have no way to tell which VLAN this interface is tagging. It inherits the parent's MAC, exactly like router-on-a-stick subinterfaces on Cisco gear, which is worth remembering when you are reading a VLAN trunk from the host side.
Delete it with sudo ip link del ens224.50. The same add ... type pattern builds bridges (type bridge), bonds (type bond), veth pairs for containers (type veth), dummy interfaces for testing (type dummy), and GRE tunnels. Any of them can exist a second after you decide you want one, with no reboot and no hardware.
The object map
You will not use most of the thirty objects. These are the ones worth knowing by name.
ip address (ip a)IP addresses on interfaces, with scope and lifetimes
ip link (ip l)Layer 2: MAC, MTU, state, counters, and creating virtual devices
ip route (ip r)The routing table, plus ip route get for a real lookup
ip neigh (ip n)ARP and IPv6 neighbor discovery, with per-entry state
ip rulePolicy routing: which table gets consulted, and in what order
ip netnsNetwork namespaces: separate routing and interface stacks on one kernel
ip vrfVRF membership, the same concept you already know from IOS
ip monitorWatch netlink events live: addresses, routes and neighbors as they change
Proving it end to end
Reading configuration is half the job. The other half is confirming the path exists. From this host, through the lab's edge router, across two more hops to a server four hops away:
j@llmbits:~$ traceroute -n 10.77.3.10
traceroute to 10.77.3.10 (10.77.3.10), 30 hops max, 60 byte packets
1 10.77.0.1 4.452 ms 4.379 ms 4.437 ms
2 10.77.12.2 4.686 ms 6.932 ms 6.960 ms
3 10.77.23.3 7.029 ms 7.022 ms 8.543 ms
4 10.77.3.10 8.506 ms 10.428 ms 10.354 msAnd the router at hop one, seeing the same host from the other side:
R1#show ip arp
Protocol Address Age (min) Hardware Addr Type Interface
Internet 10.77.0.1 - aabb.cc00.0400 ARPA Ethernet0/0
Internet 10.77.0.100 0 000c.29b1.cc47 ARPA Ethernet0/0
Internet 10.77.12.1 - aabb.cc00.0410 ARPA Ethernet0/1
Internet 10.77.12.2 4 aabb.cc00.0300 ARPA Ethernet0/1000c.29b1.cc47 on the router is 00:0c:29:b1:cc:47 on the host. Same MAC, two vendors' formatting conventions, one adjacency. That is the entire trick to working across both worlds: the protocols are identical, only the presentation changed.
FAQ
Is ifconfig actually deprecated?
Upstream net-tools has had no meaningful development for two decades, and several distributions stopped installing it by default. It is not removed and it is not going to stop working, but it cannot show you address lifetimes, policy routing, neighbor states or VRFs, because the kernel interface it uses was never extended to carry them. Treat it as read-only muscle memory for hosts where it happens to be installed, and configure with ip.
Why do my ip command changes disappear after a reboot?
Because they were only ever in the running kernel. ip has no configuration file and writes to no persistent store. Whatever manages your interfaces at boot (NetworkManager, systemd-networkd, /etc/network/interfaces, netplan) reapplies its own view of the world and yours is gone. That is a feature when you are testing, since a reboot is a guaranteed rollback.
What is the difference between ip route show and ip route get?
show prints the table. get performs a lookup for one destination and reports what the kernel decided, including policy rules, longest-prefix match and source address selection. When the table is complex, get is the only one that answers the question you are actually asking.
Can I use ip inside scripts safely?
Yes, with two rules. Use -j for JSON output and parse it with jq rather than scraping the human-readable text, and spell out full object and command names instead of abbreviations. Text layout and abbreviation resolution can both change between releases; the JSON schema and the long names are stable.
Does ip work the same on every distribution?
The command grammar and output format come from iproute2, so yes, allowing for version differences in which fields exist. What changes between distributions is what manages the interfaces at boot and therefore what you have to edit to make a change stick. The read commands transfer everywhere; the persistence step does not.
Key takeaways
ipfollows one grammar:ip [OPTIONS] OBJECT COMMAND, withshowas the default command and any unambiguous abbreviation accepted.- Four objects cover nearly everything:
addressfor what is configured,linkfor layer 2 and counters,routefor forwarding,neighfor ARP and neighbor discovery. UPandLOWER_UPare different things. Admin up without carrier is the Linux version of "up / down".ip -br addris yourshow ip interface brief;ip route getis yourshow ip route x.x.x.xand it ends arguments about which interface traffic takes.-s -sexpands the error counters into the per-cause breakdown that tells you whether to suspect cabling, duplex or the host itself.-j -pgives you JSON. Parse that in scripts, never the human output.- Nothing
ipdoes persists across a reboot. Test withip, then write the result into NetworkManager,systemd-networkdor your distribution's config. ip link addcreates VLANs, bridges, bonds, veth pairs and tunnels on demand, whichnet-toolscannot do at all.
Next in the cluster, go deeper on the routing table with ip route: managing the Linux routing table, or on the neighbor table with ARP on Linux. If you are still translating old habits, the net-tools to iproute2 map is the fastest way across. Everything in this cluster is indexed on the Linux networking commands guide.