iproute2 is the standard set of Linux networking tools. Its ip command replaces ifconfig, route and arp, ss replaces netstat, and tc controls how packets are queued and shaped. The legacy net-tools commands are not even installed on a fresh Ubuntu 24.04 server, and they cannot show features such as multiple routing tables.

In this tutorial you will inspect the network configuration of an Ubuntu 24.04 server, then practice adding addresses, routes and policy routing rules on a dummy interface so you never risk your SSH connection. You will also create a VLAN interface, shape traffic with tc, inspect sockets with ss, and finally make a change persistent with Netplan, because everything done with ip is lost at reboot.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS. iproute2 is installed by default.
  • A non-root user with sudo privileges.
  • Access to the server console from your provider, as a safety net for Steps 7 and 9, where you change the real network configuration.

The examples use eth0 as the main interface. Find yours with:

ip route show default
default via 203.0.113.1 dev eth0 proto static

The interface name follows dev. Replace eth0 with it wherever it appears.

Step 1 - Reading the current configuration

The ip command is organized in objects: link (interfaces), address, route, rule and neighbour, among others. Each object accepts show, add and del, and every object and command can be abbreviated (ip a, ip r). The -brief (-br) option prints one line per interface, which is the fastest overview:

ip -brief link
ip -brief address
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             bc:24:11:8a:1c:2d <BROADCAST,MULTICAST,UP,LOWER_UP>
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             203.0.113.21/24 2001:db8:10::21/64 fe80::be24:11ff:fe8a:1c2d/64

Show the main routing table and the IPv6 routes:

ip route
ip -6 route

To see which route and source address the kernel would use for a given destination, ask it directly. This is the most useful command when debugging routing:

ip route get 1.1.1.1
1.1.1.1 via 203.0.113.1 dev eth0 src 203.0.113.21 uid 1000
    cache

The neighbour table shows the MAC addresses resolved with ARP (IPv4) or NDP (IPv6):

ip neighbour

Add -s for statistics, for example ip -s link show eth0 to see received and transmitted packets, errors and drops per interface, and -j -p for pretty-printed JSON output that scripts can parse with jq.

Step 2 - Creating a practice interface

Changing addresses or routes on eth0 over SSH can disconnect you. A dummy interface behaves like a real one for addressing and routing, but no traffic leaves the server through it. Create one and bring it up:

sudo ip link add dummy0 type dummy
sudo ip link set dummy0 up
ip -brief link show dummy0
dummy0           UNKNOWN        5e:1a:73:0f:9c:42 <BROADCAST,NOARP,UP,LOWER_UP>

Link properties such as the MTU are changed with ip link set:

sudo ip link set dummy0 mtu 1400
ip link show dummy0 | head -n 1
5: dummy0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1400 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000

Step 3 - Managing IP addresses

An interface can have any number of IPv4 and IPv6 addresses. Add two IPv4 addresses and one IPv6 address to dummy0. The examples use private and documentation ranges:

sudo ip address add 10.10.10.1/24 dev dummy0
sudo ip address add 10.10.10.2/24 dev dummy0
sudo ip address add fd00:10::1/64 dev dummy0
ip -brief address show dummy0
dummy0           UNKNOWN        10.10.10.1/24 10.10.10.2/24 fd00:10::1/64 fe80::5c1a:73ff:fe0f:9c42/64

The first address added in a subnet is the primary one and the kernel uses it as the default source address. Later addresses in the same subnet are secondary. You can see this in the detailed view:

ip address show dummy0
    inet 10.10.10.1/24 scope global dummy0
       valid_lft forever preferred_lft forever
    inet 10.10.10.2/24 scope global secondary dummy0
       valid_lft forever preferred_lft forever

Remove an address with del, using the same prefix length:

sudo ip address del 10.10.10.2/24 dev dummy0

The kernel adds a route for the subnet of every address automatically. Confirm it:

ip route show dev dummy0
10.10.10.0/24 proto kernel scope link src 10.10.10.1

Step 4 - Adding static routes

A static route tells the kernel to send traffic for a network through a specific gateway or interface. Add a route for 192.168.50.0/24 via a gateway on the dummy0 subnet:

sudo ip route add 192.168.50.0/24 via 10.10.10.254 dev dummy0
ip route get 192.168.50.10
192.168.50.10 via 10.10.10.254 dev dummy0 src 10.10.10.1 uid 1000
    cache

ip route add fails if an identical route already exists, while ip route replace creates or overwrites it, which makes it the safer choice in scripts. For example, change the route to prefer a different source address and a lower metric:

sudo ip route replace 192.168.50.0/24 via 10.10.10.254 dev dummy0 src 10.10.10.1 metric 50

Routes to networks that should never be reachable can be rejected immediately instead of timing out:

sudo ip route add unreachable 192.168.99.0/24
ip route get 192.168.99.1
RTNETLINK answers: No route to host

Delete the routes when you are done testing:

sudo ip route del 192.168.50.0/24
sudo ip route del unreachable 192.168.99.0/24

Step 5 - Routing by source with policy routing

The normal routing table only looks at the destination. Policy routing chooses a routing table based on other criteria, most often the source address. A typical use is a server with two uplinks or an additional IP on a second interface, where replies must leave through the interface that received the request.

List the current rules. The kernel checks them in order of priority, and the main table is where ip route adds routes by default:

ip rule
0:	from all lookup local
32766:	from all lookup main
32767:	from all lookup default

Create a separate routing table, identified by the number 100, with a default route through dummy0:

sudo ip route add default dev dummy0 table 100
ip route show table 100
default dev dummy0 scope link

Add a rule so that traffic with the source address 10.10.10.1 uses table 100, with priority 1000:

sudo ip rule add from 10.10.10.1 table 100 priority 1000

Compare the route chosen for the same destination with and without that source address:

ip route get 1.1.1.1
ip route get 1.1.1.1 from 10.10.10.1
1.1.1.1 via 203.0.113.1 dev eth0 src 203.0.113.21 uid 1000
    cache
1.1.1.1 from 10.10.10.1 dev dummy0 table 100 uid 1000
    cache

Traffic from 10.10.10.1 now goes through dummy0 while everything else keeps using eth0. In a real setup, table 100 would contain a default via route to the gateway of the second network. Remove the rule and the table:

sudo ip rule del from 10.10.10.1 table 100 priority 1000
sudo ip route flush table 100

Step 6 - Creating a VLAN interface

A VLAN interface tags outgoing frames with an 802.1Q VLAN ID and receives only frames with that tag. It is used when the switch port carries several networks. Create VLAN 20 on top of dummy0 (on a real server you would use the physical interface connected to a trunk port):

sudo ip link add link dummy0 name dummy0.20 type vlan id 20
sudo ip address add 172.16.20.1/24 dev dummy0.20
sudo ip link set dummy0.20 up
ip -details link show dummy0.20
7: dummy0.20@dummy0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1400 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether 5e:1a:73:0f:9c:42 brd ff:ff:ff:ff:ff:ff promiscuity 0 allmulti 0 minmtu 0 maxmtu 65535
    vlan protocol 802.1Q id 20 <REORDER_HDR> ...

The line vlan protocol 802.1Q id 20 confirms the tag. Remove the VLAN interface and then the dummy interface. Deleting an interface also removes its addresses and routes:

sudo ip link del dummy0.20
sudo ip link del dummy0

Step 7 - Shaping traffic with tc

tc manages queueing disciplines (qdiscs), which decide how packets waiting to be sent are ordered, delayed or dropped. See the current qdisc of eth0:

tc qdisc show dev eth0
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64

A Token Bucket Filter (tbf) caps the outgoing rate of an interface, which is useful to test how an application behaves on a slow link or to keep a backup job from saturating the uplink. Limit eth0 to 10 Mbit/s:

sudo tc qdisc replace dev eth0 root tbf rate 10mbit burst 32kbit latency 400ms
  • rate is the sustained limit.
  • burst is how much data can be sent at once above the rate; it must be at least rate / HZ (10 Mbit/s / 250 = 5 KB) or the configured rate cannot be reached.
  • latency is the maximum time a packet can wait in the queue before it is dropped.

Measure the result with a download from the server, for example with iperf3 against another machine, and read the qdisc statistics, which show the bytes sent and packets dropped:

tc -s qdisc show dev eth0

To simulate a distant or unreliable network instead, the netem qdisc adds delay and loss:

sudo tc qdisc replace dev eth0 root netem delay 100ms loss 1%
ping -c 3 1.1.1.1

The ping times increase by about 100 ms. Restore the default qdisc by deleting the root qdisc:

sudo tc qdisc del dev eth0 root
tc qdisc show dev eth0

The kernel reinstalls the default fq_codel qdisc automatically.

Step 8 - Inspecting sockets with ss

ss reads socket information directly from the kernel and is much faster than netstat on busy servers. List listening TCP and UDP sockets with numeric ports and the owning process:

sudo ss -tulpn
Netid State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp   UNCONN 0      0         127.0.0.54:53        0.0.0.0:*     users:(("systemd-resolve",pid=612,fd=16))
tcp   LISTEN 0      4096         0.0.0.0:22        0.0.0.0:*     users:(("sshd",pid=1021,fd=3))

Filters select connections by state, port or address. For example, show established connections to the SSH port:

ss -tn state established '( sport = :22 )'

Add -i to show TCP internals of each connection, such as round-trip time, congestion window and retransmissions, and use ss -s for a summary of all sockets by state.

Step 9 - Making changes persistent with Netplan

Everything configured with ip and tc lives only in the running kernel and is lost at reboot. On Ubuntu 24.04, the persistent network configuration is written in YAML under /etc/netplan/ and applied by Netplan through systemd-networkd.

View the current configuration. On cloud servers it is usually generated by cloud-init:

sudo netplan get

Instead of editing the generated file, which cloud-init may overwrite, add your changes in a separate file. Netplan merges all files in lexical order. As an example, add a secondary address and a static route to eth0. Replace your_extra_ip, the destination network and your_gateway_ip with your own values:

sudo nano /etc/netplan/60-extra.yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - your_extra_ip/24
      routes:
        - to: 192.168.50.0/24
          via: your_gateway_ip

Netplan refuses files that other users can read, so restrict the permissions:

sudo chmod 600 /etc/netplan/60-extra.yaml

Apply the configuration with netplan try. It applies the change and rolls it back automatically after 120 seconds unless you confirm it, which protects you from locking yourself out:

sudo netplan try
Do you want to keep these settings?

Press ENTER before the timeout to accept the new configuration

Changes will revert in 118 seconds

Open a new SSH session to confirm that you still have access, then press ENTER in the first one. Check the result with the same commands as before:

ip -brief address show eth0
ip route show 192.168.50.0/24

Troubleshooting

RTNETLINK answers: File exists. An identical address or route already exists. Use ip route replace, or delete the existing entry first.

Error: Nexthop has invalid gateway. The gateway is not reachable through a directly connected subnet on that interface. Add an address in the gateway's subnet first, or check for typos in the gateway address.

Error: Specified qdisc kind is unknown. The kernel module for that qdisc is not available. Ubuntu's minimal virtual machine kernels ship some qdiscs, such as netem, in a separate package. Install it and try again:

sudo apt install linux-modules-extra-$(uname -r)

Policy routing rules have no effect. Check the rule order with ip rule (lower priority numbers are evaluated first) and test the decision with ip route get <destination> from <source>. Also make sure the reply traffic actually uses that source address.

netplan try warns about permissions or fails to parse the file. Fix the indentation (spaces only, no tabs), set permissions to 600, and validate the configuration with sudo netplan generate before trying again.

Conclusion

You have used ip to inspect and change interfaces, addresses, routes and policy routing rules, created a VLAN interface, shaped and delayed traffic with tc, inspected sockets with ss, and made a change persistent with Netplan. Practicing on a dummy interface kept your SSH session safe while you learned each command.

As next steps, isolate network stacks with ip netns to learn how containers are wired, set up a WireGuard tunnel and route a private subnet through it, and use ip -j with jq to feed interface data into your monitoring scripts.