Stop Hand-Editing ifcfg Files: Declarative Networking with RHEL 9 System Roles
Manual network edits on RHEL cause drift and outages. RHEL 9 System Roles give you a Red Hat-supported, declarative Ansible path to consistent bonds, VLANs, and interfaces — with a worked bonding example.
25 Jan 2026, 17:16 UTC

Every RHEL shop has that one server where the network config drifted years ago. Someone edited an ifcfg file by hand, someone else ran nmcli against it later, and now nobody is sure which settings are authoritative. When you have two hosts this is annoying; when you have two hundred, it's an outage waiting for a change window.
The supported way out of this on Red Hat Enterprise Linux 9 is the RHEL System Roles — a set of Ansible content shipped and supported by Red Hat itself. The network role in particular lets you describe interfaces, bonds, bridges, and VLANs declaratively, push them from a playbook, and get idempotent, repeatable results instead of artisanal per-host configuration.
What System Roles actually are
System Roles are ordinary Ansible roles, but they live in the rhel-system-roles package in the RHEL BaseOS repository and are covered by your RHEL subscription. That last part matters: if the role misbehaves on a supported configuration, you can open a case with Red Hat — something you can't do with a random Galaxy role.
Under the hood, the network role talks to NetworkManager, which is the default and recommended network stack on RHEL 9 (the legacy network-scripts package is gone). The role builds NetworkManager connection profiles from your variables and applies them. Because it's declarative, re-running the playbook converges the host back to the described state rather than stacking changes.
Prerequisites and setup
You need a control node with Ansible 2.9 or newer, and managed hosts running RHEL 9 with an active subscription. On the control node (root or sudo required):
subscription-manager repos --enable=rhel-9-for-x86_64-baseos-rpms
dnf install -y rhel-system-roles ansible-coreCheck that the role is visible before writing any YAML:
ansible-doc rhel-system-roles.network | headIf ansible-doc can't find the role, the package either isn't installed or your roles_path doesn't include /usr/share/ansible/roles (the package's default install location). Fix that first; everything else depends on it.
Worked example: an active-backup bond
A common real task: bond two Ethernet interfaces (ens1f0 and ens1f1) into bond0 in active-backup mode with a static address. Here's a complete playbook you run from the control node against a test host:
---
- name: Configure bonded interface
hosts: testhost
become: true
vars:
network_connections:
- name: bond0
type: bond
interface_name: bond0
state: up
bond:
mode: active-backup
ip:
address:
- "192.0.2.10/24"
gateway4: 192.0.2.1
dns:
- 192.0.2.53
- name: bond0-port1
type: ethernet
interface_name: ens1f0
controller: bond0
state: up
- name: bond0-port2
type: ethernet
interface_name: ens1f1
controller: bond0
state: up
roles:
- rhel-system-roles.networkRun it with ansible-playbook -i inventory bond.yml. Note the structure: the bond connection is defined first, then each Ethernet port references it via controller. Placeholders to replace: interface names (check yours with nmcli device status), the address, gateway, and DNS server.
Risk to know before you run: if you're connected to the test host over one of the interfaces you're enslaving, you can cut your own SSH session. Do this from the console, a BMC/iDRAC/iLO session, or an out-of-band management interface the first time.
Verifying the result
Don't trust a green playbook recap — check the host directly. On the managed host:
nmcli connection show --active
nmcli device show bond0
cat /proc/net/bonding/bond0
ip addr show bond0You're looking for three things: bond0 and both ports listed as active connections, the bonding file showing active-backup mode with both slaves up and MII status OK, and the expected address on bond0. A real failover check — pull the active link and confirm traffic continues on the backup — is worth doing in a lab before this touches production.
Where the role falls short
The network role covers the common 90% well: Ethernet, bonds, bridges, VLANs, static and DHCP addressing, DNS. It does not cover everything. Exotic driver-specific options, custom kernel module parameters, and some advanced offload or ring-buffer tuning still need nmcli commands, udev rules, or modprobe configuration outside the role. The role also assumes NetworkManager; if you have hosts pinned to some other stack, this isn't your tool.
The authoritative list of supported parameters is on your own system: ansible-doc rhel-system-roles.network, plus the README shipped under /usr/share/doc/rhel-system-roles/. Check those against your exact RHEL 9 minor release before assuming a parameter exists — the role evolves between releases.
Try it this week
Pick one non-production host, describe one interface or bond with the role, and run the playbook twice. The second run reporting no changes is the whole point: your network config is now code, reviewable in git and reproducible on the next rebuild. Once you trust it, extend the same pattern across the fleet — and retire the hand-edited ifcfg archaeology for good.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.