{
"meta": {
"title": "Persistent Interface Names | ProxMenux Documentation",
"description": "Pin Proxmox interface names (eno1, enp3s0, …) to MAC addresses via systemd .link files so names survive PCI re-enumeration, kernel upgrades and adding / removing other NICs.",
"ogTitle": "Persistent Interface Names | ProxMenux Documentation",
"ogDescription": "Stop Proxmox interface names from changing when you alter hardware. Uses systemd .link files keyed on MAC address."
},
"header": {
"title": "Persistent interface names",
"description": "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.",
"section": "Network"
},
"intro": {
"title": "What this does",
"body": "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."
},
"problem": {
"heading": "The problem this solves",
"intro": "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:",
"items": [
"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."
],
"outro": "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."
},
"howWorks": {
"heading": "How it works",
"arrowLabel": "per NIC",
"nodes": {
"detectLabel": "Detect physical NICs",
"detectDetail": "ls /sys/class/net/\nfilter out vmbr / bond /\ndocker / veth / wireguard …",
"readLabel": "Read each MAC",
"readDetail": "cat /sys/class/net/\n '<'iface'>'/address",
"writeLabel": "Write .link file",
"writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link"
},
"minimalIntro": "Each generated file is intentionally minimal:",
"minimalOutro": "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."
},
"scope": {
"heading": "What gets written, what gets skipped",
"headerType": "Type",
"headerBehaviour": "Behaviour",
"headerWhy": "Why",
"rows": [
{
"type": "Onboard / PCIe NIC",
"behaviour": ".link file written",
"why": "Backed by a real PCI device — the case the tool is for"
},
{
"type": "Wi-Fi (phy80211)",
"behaviour": ".link file written",
"why": "Has a real MAC and benefits from name stability"
},
{
"type": "Bridges (vmbrX)",
"behaviour": "Skipped",
"why": "Virtual; name comes from /etc/network/interfaces"
},
{
"type": "Bonds (bondX)",
"behaviour": "Skipped",
"why": "Virtual; bond name is set by ifupdown"
},
{
"type": "veth / docker0 / br-XXXX",
"behaviour": "Skipped",
"why": "Created on demand by Docker / LXC — not persistent hardware"
},
{
"type": "tap / fwpr / fwln",
"behaviour": "Skipped",
"why": "Created on demand by Proxmox per VM/CT"
},
{
"type": "WireGuard / Cilium / Tailscale",
"behaviour": "Skipped",
"why": "Software interfaces managed by their own daemons"
}
]
},
"safety": {
"heading": "Safety net: previous .link files are backed up",
"intro": "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:",
"outro": "To roll back: copy the files back from the backup directory and reboot.",
"rebootTitle": "Takes effect on next reboot, not immediately",
"rebootBody": "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."
},
"afterReboot": {
"heading": "After the reboot",
"intro": "Once the names are pinned, the workflow for future hardware changes is simple:",
"items": [
"Power off, change hardware (add card, move slot, …), boot.",
"Each NIC keeps its previous name because its MAC matches a .link file.",
"If a NIC is replaced (different MAC), it gets a default kernel name (enp<bus>s<slot>); re-run this menu to add a .link entry for the new card's MAC."
]
},
"troubleshoot": {
"heading": "Troubleshooting",
"emptyTitle": "\"No physical interfaces found\" after running the tool",
"emptyBody": "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.",
"noChangeTitle": "After the reboot, names did not change as expected",
"noChangeBody": "Check that the file is present and well-formed: cat /etc/systemd/network/10-<iface>.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.",
"undoTitle": "I want to undo persistent naming",
"undoBody": "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": {
"heading": "Related",
"items": [
{
"label": "Bridge analysis & guided repair",
"href": "/docs/network/bridge-analysis",
"tailRich": " — the recovery path when names have already shifted."
},
{
"label": "Config analysis & guided cleanup",
"href": "/docs/network/config-analysis",
"tail": " — to remove orphan blocks left behind by name shifts."
},
{
"label": "Diagnostics",
"href": "/docs/network/diagnostics",
"tailRich": " — confirm the new names with Show Routing Table after reboot."
}
]
}
}