RHEL Intermediate #4: Networking — NetworkManager (nmcli), bonding, teaming

11 min read

The networking concepts we briefly touched during setup in Basics #2 get unpacked in earnest here. The network of RHEL 9 has been organized into NetworkManager alone, and this post looks at how to handle that configuration in actual operations.

The position of this post in the RHEL Intermediate series:

Why NetworkManager became the standard #

Old RHEL wrote network configuration in /etc/sysconfig/network-scripts/ifcfg-* files and applied with service network restart. It worked well for simple roles but was weak at wireless, VPN, dynamic IP, and simultaneous handling of multiple interfaces.

NetworkManager is a tool that integrated all of this into a single daemon. It operates with the same model on desktops, servers, laptops, and containers, changes are reflected immediately (no restart required), and the backend (D-Bus) and client tools (nmcli / nmtui / GUI) are diverse, making automation easy too.

The shape of NetworkManager
   user tools:    nmcli (CLI)   nmtui (TUI)   GNOME (GUI)   D-Bus
                     │             │              │            │
                     └─────────────┴──────┬───────┴────────────┘
                              ┌────────────────────┐
                              │  NetworkManager    │   ← daemon
                              │     (NM)           │
                              └─────────┬──────────┘
                  ┌─────────────────────┼─────────────────────┐
                  ▼                     ▼                     ▼
              [Device]              [Connection]         [Routing/DNS]
              actual NIC            config profile        /etc/resolv.conf
              (enp0s1...)           (Wired-1, vpn-...)    /etc/hosts

Two key concepts:

  • Device — actual NIC. Names like enp0s1, wlan0.
  • Connection — a configuration profile to apply to a Device. You can create multiple connections for one Device and pick one to activate depending on the situation.

Keeping these two concepts distinct is the core of NetworkManager’s model. Always be clear about “which configuration is currently active on this NIC”.

nmcli — daily command group #

nmcli (NetworkManager CLI) is the starting point of nearly all work. It takes the form of attaching commands to three objects (general / device / connection).

current state at a glance
$ nmcli
enp0s1: connected to Wired connection 1
        "Virtio Network Device"
        ethernet (virtio_net), 52:54:00:..., hw, mtu 1500
        ip4 default
        inet4 192.168.64.15/24
        route4 default via 192.168.64.1 metric 100
        inet6 fe80::.../64
        route6 fe80::/64 metric 256

lo: unmanaged

DNS configuration:
        servers: 192.168.64.1
        interfaces: enp0s1

Just typing nmcli shows everything on one screen. In operations, this is the first command you reach for when logging into a machine to check the network state.

Viewing devices #

device command group
$ nmcli device status
DEVICE  TYPE      STATE      CONNECTION
enp0s1  ethernet  connected  Wired connection 1
lo      loopback  unmanaged  --

$ nmcli device show enp0s1     # detail of one device
GENERAL.DEVICE:                         enp0s1
GENERAL.TYPE:                           ethernet
GENERAL.HWADDR:                         52:54:00:...
GENERAL.MTU:                            1500
GENERAL.STATE:                          100 (connected)
...
IP4.ADDRESS[1]:                         192.168.64.15/24
IP4.GATEWAY:                            192.168.64.1
IP4.DNS[1]:                             192.168.64.1

Viewing connections #

connection command group
$ nmcli connection show
NAME                 UUID                   TYPE      DEVICE
Wired connection 1   a1b2c3d4-...           ethernet  enp0s1
my-static            d4e5f6a7-...           ethernet  --

$ nmcli connection show "Wired connection 1"   # detail of one connection
connection.id:                          Wired connection 1
connection.uuid:                        a1b2c3d4-...
connection.type:                        ethernet
ipv4.method:                            auto
ipv4.dns:                               --
...

A connection with an empty DEVICE column (my-static) is a profile that’s only created and not activated.

Static IP configuration — the most frequently encountered task #

When you want to use a fixed IP rather than DHCP.

Creating a new connection #

static IP profile creation
$ sudo nmcli connection add \
    type ethernet \
    con-name "static-enp0s1" \
    ifname enp0s1 \
    ipv4.method manual \
    ipv4.addresses 192.168.64.50/24 \
    ipv4.gateway 192.168.64.1 \
    ipv4.dns "192.168.64.1 8.8.8.8" \
    autoconnect yes

Options explained:

  • con-name — human-recognizable connection name
  • ifname — device name to apply
  • ipv4.method manual — fixed IP (DHCP is auto)
  • ipv4.addresses — IP/prefix form
  • ipv4.gateway — default gateway
  • ipv4.dns — DNS servers (multiple separated by space)
  • autoconnect yes — auto-activate at boot

Modifying an existing connection #

change values of existing connection
$ sudo nmcli connection modify "static-enp0s1" \
    ipv4.addresses 192.168.64.51/24

$ sudo nmcli connection up "static-enp0s1"     # must reactivate to reflect

modify alone doesn’t reflect immediately. Reactivate with up or reconnect the device.

Switching connections #

switch to another connection
$ sudo nmcli connection up "static-enp0s1"
$ sudo nmcli connection down "Wired connection 1"

Even with two connections on the same device, only one is activated at a time. Useful for environments that move frequently (laptop’s office/home/cafe profiles).

Deletion #

connection deletion
$ sudo nmcli connection delete "static-enp0s1"

nmtui — TUI tool #

Before you are fully comfortable with nmcli, nmtui (NetworkManager Text User Interface) is a quick way to get things done.

run
$ sudo nmtui

Move menus with arrow keys, enter static IP / DNS / gateway in forms. It is handy when you SSH into a machine in production and need to make a quick change without memorizing nmcli flags.

/etc/resolv.conf — DNS is managed by NetworkManager #

In the old days you edited /etc/resolv.conf directly, but now NetworkManager manages it automatically. If you edit it directly, it gets overwritten on the next connection change.

DNS configuration is a property of the connection:

DNS add / change
$ sudo nmcli connection modify "static-enp0s1" \
    ipv4.dns "1.1.1.1 8.8.8.8" \
    ipv4.dns-search "example.com"

$ sudo nmcli connection up "static-enp0s1"

To ignore the DNS given by DHCP and use your own DNS:

ignore DHCP DNS
$ sudo nmcli connection modify "Wired connection 1" \
    ipv4.ignore-auto-dns yes \
    ipv4.dns "1.1.1.1 8.8.8.8"

Bonding — two NICs as one block #

When you have two physical NICs, bundling them for fault tolerance or bandwidth aggregation is bonding. Fault tolerance is by far the most common use case.

the shape of bonding
   eth0 ─┐
         ├── bond0 ── 192.168.64.50
   eth1 ─┘
         (if one NIC dies the other takes over)

Modes #

modenameuse
0balance-rrround robin (switch support required)
1active-backupone NIC active, others standby — most common
2balance-xorXOR hash distribution
3broadcastsame packet on all NICs
4802.3ad / LACPstandard link aggregation — switch support required
5balance-tlbsend-only distribution
6balance-albboth send and receive distribution

99% of production setups use mode 1 (active-backup) or mode 4 (LACP). Mode 1 for simple fault tolerance, mode 4 when bandwidth aggregation is needed and the switch supports LACP.

Creating a bond #

active-backup bond
# 1. create bond interface (master)
$ sudo nmcli connection add \
    type bond \
    con-name bond0 \
    ifname bond0 \
    mode active-backup \
    miimon 100 \
    ipv4.method manual \
    ipv4.addresses 192.168.64.50/24 \
    ipv4.gateway 192.168.64.1 \
    ipv4.dns 192.168.64.1

# 2. attach two slaves to bond
$ sudo nmcli connection add \
    type ethernet \
    con-name bond0-slave1 \
    ifname enp0s1 \
    master bond0

$ sudo nmcli connection add \
    type ethernet \
    con-name bond0-slave2 \
    ifname enp0s2 \
    master bond0

# 3. activate
$ sudo nmcli connection up bond0-slave1
$ sudo nmcli connection up bond0-slave2
$ sudo nmcli connection up bond0

miimon 100 is the link monitoring period (ms). It checks NIC state every 100ms and automatically removes a disconnected NIC.

Checking bond state #

bond state
$ cat /proc/net/bonding/bond0
Ethernet Channel Bonding Driver: v...
Bonding Mode: fault-tolerance (active-backup)
Currently Active Slave: enp0s1
MII Status: up
MII Polling Interval (ms): 100

Slave Interface: enp0s1
MII Status: up
Speed: 1000 Mbps
Duplex: full

Slave Interface: enp0s2
MII Status: up
Speed: 1000 Mbps
Duplex: full

Currently Active Slave shows which NIC is active. Unplug the active NIC and the bond immediately fails over to the other — that is bonding’s core value.

Teaming — why was it deprecated #

In the RHEL 7–8 era, there was a similar tool called teaming alongside bonding. Its appeal was that it ran in user space, allowing more flexible policy configuration. But it was deprecated in RHEL 9 and removed in RHEL 10.

The reason is straightforward. Kernel-level bonding is more stable and faster, and its integration with NetworkManager and nmcli is smoother. Teaming’s flexibility never justified the added complexity. If you come across teamd or nmcli connection add type team in older posts, it is time to switch to bonding.

team → bond conversion
# old (deprecated):  nmcli connection add type team ...
# now:               nmcli connection add type bond ...

Bridge — the foundation of virtual machines and containers #

A bridge is used when you want VMs or containers on the same network segment as the host. It ties the host’s physical NIC and virtual NICs together into the same L2 segment.

the shape of bridge
              ┌──── enp0s1 (external)
   ┌──── br0 ─┤
   │          ├──── vnet0 (VM 1)
   │          │
   │          └──── vnet1 (VM 2)
   192.168.64.50/24 (give the bridge an IP)

Creating a bridge #

bridge creation
# 1. bridge interface
$ sudo nmcli connection add \
    type bridge \
    con-name br0 \
    ifname br0 \
    ipv4.method manual \
    ipv4.addresses 192.168.64.50/24 \
    ipv4.gateway 192.168.64.1 \
    ipv4.dns 192.168.64.1

# 2. attach physical NIC to bridge
$ sudo nmcli connection add \
    type ethernet \
    con-name br0-slave1 \
    ifname enp0s1 \
    master br0

# 3. activate
$ sudo nmcli connection up br0

With this, enp0s1 loses its IP, and br0 takes over that IP. When creating a VM, connect the network to br0 and it can communicate directly with other machines on the same LAN.

Trap — if you put the NIC the host is connected to via SSH directly into the bridge, the connection drops temporarily. Work in a state where console access is available, or apply at once via an automation script.

VLAN #

You can create virtual interfaces by attaching multiple VLAN tags to one NIC.

VLAN creation
$ sudo nmcli connection add \
    type vlan \
    con-name vlan100 \
    ifname enp0s1.100 \
    dev enp0s1 \
    id 100 \
    ipv4.method manual \
    ipv4.addresses 10.0.100.50/24

id 100 is the 802.1Q VLAN tag. The resulting interface name is usually in the form <base>.<id> (enp0s1.100).

Diagnostic tools #

ip command #

ip is the most direct command. It reads the kernel’s network state directly, bypassing NetworkManager.

ip frequently used patterns
$ ip -4 addr show              # IPv4 addresses
$ ip -6 addr show              # IPv6
$ ip route                     # routing table
$ ip neigh                     # ARP table
$ ip link show                 # link state (up/down)

Connectivity #

ping / traceroute / mtr
$ ping -c 3 8.8.8.8
$ traceroute google.com
$ mtr -n google.com           # ping + traceroute integrated (interactive)

Ports / sockets #

ss
$ ss -tlnp                     # TCP listening + process
$ ss -tunap                    # TCP+UDP all sockets + process
$ ss -s                        # socket stats summary

netstat is deprecated. If you are just starting out, use ss exclusively.

DNS #

DNS lookup
$ dig example.com              # DNS response detail
$ dig +short example.com       # IP only
$ host example.com             # short response
$ nslookup example.com         # old tool, for compatibility

In operations, dig is the standard. +short is frequently used.

AlmaLinux / Rocky differences #

All commands in this post work as is. NetworkManager / nmcli / bonding / bridge / VLAN all bring the RHEL packages as is.

Frequently encountered traps #

“Grabbed a static IP but can’t communicate externally” #

99% of the time it’s because the gateway or DNS was missed. Check ipv4.gateway and ipv4.dns with nmcli connection show <name>.

“Changed DNS but /etc/resolv.conf stays the same” #

The file was edited directly instead of updating the connection property. NetworkManager overwrites it on the next change. Always use nmcli connection modify ... ipv4.dns ... instead.

“Modified but no change” #

An activated connection requires reapply with up after modify to be reflected. Or reconnect the device.

“bond / bridge doesn’t come up after boot” #

Check that the slave connections have connection.autoconnect set to yes. NetworkManager brings up the master first and then attaches the slaves, but slaves that are not set to autoconnect will never attach.

“Two connections collide” #

If two connections exist on the same device, which one activates at boot is unpredictable. Delete unused connections immediately or set connection.autoconnect no on the ones you want dormant.

Frequently used commands at a glance #

CommandWhat it does
nmclioverall state at a glance
nmcli device statusdevice list
nmcli device show <dev>device detail
nmcli connection showconnection list
nmcli connection show <name>connection detail
nmcli connection add type ethernet ...add new connection
nmcli connection modify <name> <key> <value>modify property
nmcli connection up/down <name>activate / deactivate
nmcli connection delete <name>delete
nmcli connection reloadre-read disk changes into NM
nmtuiTUI
ip -4 addr show / ip route / ip linkkernel state
ss -tlnplistening ports + process
dig +short <host>DNS lookup

Wrapping up #

The flows organized in this post:

  • The network standard for RHEL 9 is the single tool NetworkManager. ifcfg / network service is gone.
  • The core model is Device (NIC) + Connection (config profile). Multiple connections per device are possible.
  • nmcli is the daily command. The first line is always nmcli to see at a glance.
  • Static IP pattern: nmcli connection add type ethernet ... ipv4.method manual ipv4.addresses ... gateway ... dns ....
  • DNS is a property of the connection — direct editing of /etc/resolv.conf is forbidden.
  • 99% of bonding is mode 1 (active-backup), or mode 4 in environments where LACP is possible. Teaming is deprecated.
  • Bridge is the foundation for placing VMs / containers on the same LAN as the host.
  • For diagnostics, ip / ss / dig are the standard (netstat is deprecated).

Next — log management #

Once the network is configured, everything that happens on top of it eventually ends up in logs. The next post looks at how to handle them.

In #5 Log management — journald, rsyslog, log rotation we’ll organize from an operational perspective: journald’s retention policy and disk usage control, rsyslog that’s the old standard but still alive and remote log collection, and logrotate that automatically rotates all log files by certain sizes / periods.

X