Persistent interface names

Network~4 minView script

Generates a systemd .link file per physical NIC that pins the kernel-assigned name to the card's MAC address. Once applied (after the next reboot), interface names stop drifting when you add, remove or move PCIe cards.

What this does

For every physical NIC on the host, writes a small file under /etc/systemd/network/10-<iface>.link that says: "the device with this MAC must always be called this name". systemd-udevd applies the rule at every boot, before ifupdown reads /etc/network/interfaces.

The problem this solves

Linux derives default interface names from PCI topology — eno1 = onboard, enp3s0 = the card in PCI bus 3, slot 0, etc. The naming scheme is deterministic given the same hardware layout. Change the layout and names shift:

  • Add a GPU in front of an existing NIC → the NIC bus number can change → name changes.
  • Move a card to a different PCIe slot → name changes.
  • BIOS / UEFI update that re-enumerates devices → names can change.
  • Replace a faulty card with the same model in a different slot → name changes.

After such a change, /etc/network/interfaces still references the old name; the bridge fails to come up; the host loses network. Setting up persistent names prevents this scenario from happening again.

How it works

Detect physical NICs
ls /sys/class/net/ filter out vmbr / bond / docker / veth / wireguard …
per NIC
Read each MAC
cat /sys/class/net/ '<'iface'>'/address
per NIC
Write .link file
/etc/systemd/network/ 10-'<'iface'>'.link

Each generated file is intentionally minimal:

# /etc/systemd/network/10-eno1.link
[Match]
MACAddress=aa:bb:cc:dd:ee:ff

[Link]
Name=eno1

At boot, systemd-udevd matches the device by MAC and assigns the requested name before any other component (ifupdown, kernel default naming) gets to it. The file prefix 10- ensures these rules load early, ahead of the default 99-default.link.

What gets written, what gets skipped

TypeBehaviourWhy
Onboard / PCIe NIC.link file writtenBacked by a real PCI device — the case the tool is for
Wi-Fi (phy80211).link file writtenHas a real MAC and benefits from name stability
Bridges (vmbrX)SkippedVirtual; name comes from /etc/network/interfaces
Bonds (bondX)SkippedVirtual; bond name is set by ifupdown
veth / docker0 / br-XXXXSkippedCreated on demand by Docker / LXC — not persistent hardware
tap / fwpr / fwlnSkippedCreated on demand by Proxmox per VM/CT
WireGuard / Cilium / TailscaleSkippedSoftware interfaces managed by their own daemons

Safety net: previous .link files are backed up

If /etc/systemd/network/ already contains .link files (from a previous run or other tooling), they are copied to a timestamped backup directory before the new ones are generated:

/etc/systemd/network/backup-20260426-143012/
├── 10-eno1.link    (previous version)
└── 10-enp3s0.link  (previous version)

To roll back: copy the files back from the backup directory and reboot.

Takes effect on next reboot, not immediately

Changes to .link files only apply at boot, when udev re-enumerates devices. The tool reports "Changes will apply after reboot" for this reason. Renaming an interface live is risky and not attempted: an active vmbr0 with members would have to be reconfigured atomically, which is why the operation is deferred to the next clean boot.

After the reboot

Once the names are pinned, the workflow for future hardware changes is simple:

  1. Power off, change hardware (add card, move slot, …), boot.
  2. Each NIC keeps its previous name because its MAC matches a .link file.
  3. If a NIC is replaced (different MAC), it gets a default kernel name (enp&lt;bus&gt;s&lt;slot&gt;); re-run this menu to add a .link entry for the new card's MAC.

Troubleshooting

"No physical interfaces found" after running the tool

The host has no PCI / phy80211-backed interfaces visible to the kernel. Confirm with ls -l /sys/class/net/ — every entry should have either a device symlink (PCI) or a phy80211 entry (Wi-Fi). If both are missing for what should be a real NIC, the driver is not loaded.

After the reboot, names did not change as expected

Check that the file is present and well-formed: cat /etc/systemd/network/10-&lt;iface&gt;.link. Then check udev logs: journalctl -u systemd-udevd -b | grep -i link. A common cause is a stale net.ifnames=0 kernel parameter that disables predictable naming entirely — remove it from /etc/default/grub, run update-grub, reboot.

I want to undo persistent naming

Either delete the .link files (rm /etc/systemd/network/10-*.link) or restore from the backup directory generated on the previous run. Reboot to apply.

Related