nmcli and nmtui: Managing Connections from the CLI

Devices against connection profiles, the activation step everybody forgets, and how to make a configuration survive a reboot.

Dark terminal graphic reading nmcli and nmtui, showing real NetworkManager device status and activation output

Everything you do with ip disappears when the host reboots. That is by design, and it is fine while you are troubleshooting. It stops being fine the moment you need a change to still be there tomorrow, and on most modern Linux distributions the thing that decides what is there tomorrow is NetworkManager. nmcli is how you drive it from a terminal, without a desktop and without editing files by hand.

Network engineers tend to skip it, on the reasonable assumption that a laptop wifi manager has nothing to say about servers. That assumption is a decade out of date. NetworkManager is the default on RHEL, Fedora, CentOS Stream, Rocky, Alma and most Debian and Ubuntu installs with a desktop, it manages bonds, bridges, VLANs, VRFs and teams, and it is what the vendor documentation assumes you are using.

This article covers the model (devices against connection profiles), reading state, creating and modifying profiles, the activation step everybody forgets, where the configuration lives on disk, and how NetworkManager coexists with an interface you configured by hand. Every command was run on a Debian 13 host with two NICs: one NetworkManager-managed, one deliberately left to iproute2 and bridged into a Cisco Modeling Labs topology. It is part of the Linux networking commands cluster.

The model: devices and connections are different things

This is the concept that makes the rest of nmcli obvious, and getting it wrong is why the tool feels arbitrary at first.

A device is a network interface: ens192, eth0, bond0. It exists because hardware or the kernel says so.

A connection is a saved configuration profile: an address, a gateway, DNS servers, routes, a method (DHCP or manual). It is a file on disk with a UUID. A profile is not a device, and it is not applied until you activate it.

One device can have many profiles (an office profile and a datacenter profile for the same NIC) and only one can be active at a time. A profile can also exist without a device present, which is how you stage a configuration for a NIC that is not installed yet. If you have worked with Cisco, the profile is the configuration and the device is the interface, with the difference that Linux keeps several configurations on the shelf and lets you swap between them.

j@llmbits:~$ nmcli --version
nmcli tool, version 1.52.1

Reading state

Three commands cover almost all read-only work. Start at the top:

j@llmbits:~$ nmcli general status
STATE                  CONNECTIVITY  WIFI-HW  WIFI     WWAN-HW  WWAN     METERED
connected (site only)  limited       missing  enabled  missing  enabled  no (guessed)

connected (site only) and CONNECTIVITY limited mean NetworkManager reached the local network but its connectivity check did not get the expected response from the internet. On a server behind a proxy or with outbound HTTP filtered, that is normal and not a fault. It is worth knowing what it means before somebody escalates it.

j@llmbits:~$ nmcli device status
DEVICE  TYPE      STATE                   CONNECTION
ens192  ethernet  connected               Wired connection 1
lo      loopback  connected (externally)  lo
ens224  ethernet  connected (externally)  ens224

This is the closest thing to show ip interface brief that NetworkManager offers, and the STATE column is where the interesting information is.

connected on ens192 means NetworkManager activated the profile Wired connection 1 on that device and owns its configuration.

connected (externally) on ens224 means something else configured this interface and NetworkManager noticed. In this case that something is ip addr add, run by hand to put the host on the lab network. NetworkManager generated a placeholder profile so the device appears in its inventory, but it did not apply anything and it will not fight you for it. That distinction is worth being able to read at a glance: connected means NetworkManager is in charge, connected (externally) means it is watching.

j@llmbits:~$ nmcli connection show
NAME                UUID                                  TYPE      DEVICE
Wired connection 1  e532342d-ad22-417f-95c6-ae5e0fcfe2cd  ethernet  ens192
lo                  51dff46e-074d-4c29-9b02-88961476bbf0  loopback  lo
ens224              2844233d-cb9c-4204-91fd-ecbd084f1825  ethernet  ens224

Profiles, with the device each is currently active on. A blank DEVICE column means the profile exists but is not active.

For the full picture on one interface:

j@llmbits:~$ nmcli device show ens192
GENERAL.DEVICE:                         ens192
GENERAL.TYPE:                           ethernet
GENERAL.HWADDR:                         00:0C:29:B1:CC:3D
GENERAL.MTU:                            1500
GENERAL.STATE:                          100 (connected)
GENERAL.CONNECTION:                     Wired connection 1
GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/ActiveConnection/4
WIRED-PROPERTIES.CARRIER:               on
IP4.ADDRESS[1]:                         192.168.88.156/24
IP4.GATEWAY:                            192.168.88.1
IP4.ROUTE[1]:                           dst = 192.168.88.0/24, nh = 0.0.0.0, mt = 101
IP4.ROUTE[2]:                           dst = 0.0.0.0/0, nh = 192.168.88.1, mt = 101
IP4.DNS[1]:                             45.90.28.181
IP4.DNS[2]:                             45.90.30.181
IP6.ADDRESS[1]:                         fd64:f725:df42:4f01:8498:4c62:15f0:8139/64
IP6.ADDRESS[2]:                         fd64:f725:df42:4f01:20c:29ff:feb1:cc3d/64
IP6.ADDRESS[3]:                         fe80::20c:29ff:feb1:cc3d/64
IP6.GATEWAY:                            --
IP6.ROUTE[1]:                           dst = fe80::/64, nh = ::, mt = 1024
IP6.ROUTE[2]:                           dst = fd27:5923:50b3::/64, nh = fe80::18d8:e4e1:ba78:179e, mt = 101
IP6.ROUTE[3]:                           dst = fd64:f725:df42:4f01::/64, nh = ::, mt = 101
IP6.ROUTE[4]:                           dst = fd9b:7bc4:97f1::/64, nh = fe80::c1f:b024:94ab:44c3, mt = 106
IP6.ROUTE[5]:                           dst = fd9b:7bc4:97f1::/64, nh = fe80::1072:e4df:6063:2046, mt = 106

Address, gateway, routes and DNS on one screen. That last part is the genuine advantage over ip: DNS servers are not kernel state, so ip cannot show them and you would otherwise be reading /etc/resolv.conf and guessing which of several things wrote it.

Compare the externally configured interface:

j@llmbits:~$ nmcli device show ens224
GENERAL.DEVICE:                         ens224
GENERAL.TYPE:                           ethernet
GENERAL.HWADDR:                         00:0C:29:B1:CC:47
GENERAL.MTU:                            1500
GENERAL.STATE:                          100 (connected (externally))
GENERAL.CONNECTION:                     ens224
GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/ActiveConnection/5
WIRED-PROPERTIES.CARRIER:               on
IP4.ADDRESS[1]:                         10.77.0.100/24
IP4.GATEWAY:                            --
IP4.ROUTE[1]:                           dst = 10.77.0.0/24, nh = 0.0.0.0, mt = 0
IP4.ROUTE[2]:                           dst = 10.77.0.0/16, nh = 10.77.0.1, mt = 0
IP6.GATEWAY:                            --

NetworkManager is reporting the address and both routes accurately, including the 10.77.0.0/16 summary added with ip route, while owning none of it. No gateway, because none was configured. This is a read-only reflection of kernel state, and it is exactly how you want it to behave on an interface you are managing yourself.

Output that scripts can use

Two flags turn nmcli from a display tool into something you can build on.

j@llmbits:~$ nmcli -f GENERAL.DEVICE,GENERAL.STATE,IP4.ADDRESS,IP4.GATEWAY device show ens192
GENERAL.DEVICE:                         ens192
GENERAL.STATE:                          100 (connected)
IP4.ADDRESS[1]:                         192.168.88.156/24
IP4.GATEWAY:                            192.168.88.1

j@llmbits:~$ nmcli -t -f NAME,UUID,TYPE,DEVICE connection show
Wired connection 1:e532342d-ad22-417f-95c6-ae5e0fcfe2cd:802-3-ethernet:ens192
lo:51dff46e-074d-4c29-9b02-88961476bbf0:loopback:lo
ens224:2844233d-cb9c-4204-91fd-ecbd084f1825:802-3-ethernet:ens224

-f selects fields, -t makes the output terse and colon-separated. Together they give you something cut or awk can consume reliably. There is also -g, which is -t plus -f with the field names stripped, for when you want a bare value and nothing else. That combination is how you would fold host configuration into the kind of collection the network automation cluster covers.

Creating a profile

The write side. Everything here uses a throwaway dummy device so nothing on a real NIC moves, which is also a genuinely useful pattern for testing profile syntax before you point it at production.

j@llmbits:~$ sudo nmcli connection add type dummy ifname dummy0 con-name lab-dummy \
              ipv4.method manual ipv4.addresses 10.88.88.1/24 ipv6.method disabled
Connection 'lab-dummy' (25a72cf7-1e7b-42c9-80df-b74e2abc7f1c) successfully added.

Four things in that one command. type is the connection type (ethernet, bond, bridge, vlan, vrf, wifi, dummy). ifname is the device it binds to. con-name is the profile name, and setting it explicitly saves you from living with Wired connection 2 forever. ipv4.method manual is the one people miss: without it the profile defaults to auto and your static address is quietly ignored while the interface waits for a DHCP server that is not there.

j@llmbits:~$ nmcli connection show
NAME                UUID                                  TYPE      DEVICE
Wired connection 1  e532342d-ad22-417f-95c6-ae5e0fcfe2cd  ethernet  ens192
lab-dummy           25a72cf7-1e7b-42c9-80df-b74e2abc7f1c  dummy     dummy0
lo                  51dff46e-074d-4c29-9b02-88961476bbf0  loopback  lo
ens224              2844233d-cb9c-4204-91fd-ecbd084f1825  ethernet  ens224

j@llmbits:~$ ip -br addr show dummy0
dummy0           UNKNOWN        10.88.88.1/24

The profile was created and activated in one step, because connection add brings up a new profile by default when its device is available. The address is live in the kernel, which ip confirms.

Inspecting a profile

nmcli connection show <name> prints every property, which runs to a couple of hundred lines. Filter it:

j@llmbits:~$ nmcli -f ipv4 connection show lab-dummy
ipv4.method:                            manual
ipv4.dns:                               --
ipv4.dns-search:                        --
ipv4.dns-priority:                      0
ipv4.addresses:                         10.88.88.1/24
ipv4.gateway:                           --
ipv4.routes:                            --
ipv4.route-metric:                      -1
ipv4.route-table:                       0 (unspec)
ipv4.routing-rules:                     --
ipv4.ignore-auto-routes:                no
ipv4.ignore-auto-dns:                   no
ipv4.dhcp-client-id:                    --
ipv4.dhcp-timeout:                      0 (default)
ipv4.dhcp-hostname:                     --
ipv4.never-default:                     no
ipv4.may-fail:                          yes
ipv4.dad-timeout:                       -1 (default)
ipv4.link-local:                        0 (default)

Several of these are worth knowing by name.

ipv4.routes and ipv4.routing-rules are how a static route or a policy rule becomes permanent, which is the persistence answer for everything in the ip route article. The syntax is nmcli connection modify <name> +ipv4.routes "10.99.99.0/24 10.77.0.1".

ipv4.never-default stops a profile from installing a default route, which is what you want on a second NIC that should carry only its own subnet.

ipv4.ignore-auto-dns keeps a DHCP server from overwriting your resolvers while still taking the address from it.

ipv4.may-fail yes means the connection activates even if IPv4 fails. On a dual-stack host where IPv6 works and IPv4 does not, that is why the interface came up looking healthy.

ipv4.route-metric -1 means "use the default", which for wired ethernet is 100.

Modify, then activate

This is the mistake, and it is worth stating on its own line. nmcli connection modify changes the saved profile. It does not change the running interface.

j@llmbits:~$ sudo nmcli connection modify lab-dummy +ipv4.addresses 10.88.89.1/24 ipv4.route-metric 300
j@llmbits:~$ sudo nmcli connection up lab-dummy
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/7)

j@llmbits:~$ ip -br addr show dummy0
dummy0           UNKNOWN        10.88.88.1/24 10.88.89.1/24

Without the connection up, the second address exists on disk and nowhere else, and ip addr shows one address while nmcli connection show shows two. That mismatch is the source of a great deal of confusion, and the fix is always the same: reactivate the profile.

The + in +ipv4.addresses appends. Without it you replace the whole list, which is how people accidentally remove the address they were connected over. On a remote host, use + deliberately and check the profile before you activate.

j@llmbits:~$ nmcli -g ipv4.addresses,ipv4.method,ipv4.route-metric connection show lab-dummy
10.88.88.1/24, 10.88.89.1/24
manual
300

Where it lives on disk

j@llmbits:~$ sudo ls -l /etc/NetworkManager/system-connections/
total 8
-rw------- 1 root root 271 Aug 16 16:48 lab-dummy.nmconnection
-rw------- 1 root root 170 May 15 16:31 Wired connection 1

One file per profile, mode 0600 because profiles can contain wifi keys and 802.1X credentials. The format is INI-style keyfile. You can edit these directly and run nmcli connection reload, and for configuration management that is often the cleaner path: template the file, reload, activate. On older RHEL-family systems you may still find ifcfg- files under /etc/sysconfig/network-scripts/, which NetworkManager reads through a compatibility plugin; new profiles go to the keyfile directory.

Note the timestamps. The management NIC's profile was written in May and has not been touched since, which is what a stable production profile looks like.

Cleanup, and the interface that was never touched

j@llmbits:~$ sudo nmcli connection down lab-dummy
Connection 'lab-dummy' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/7)
j@llmbits:~$ sudo nmcli connection delete lab-dummy
Connection 'lab-dummy' (25a72cf7-1e7b-42c9-80df-b74e2abc7f1c) successfully deleted.

j@llmbits:~$ nmcli device status
DEVICE  TYPE      STATE                   CONNECTION
ens192  ethernet  connected               Wired connection 1
lo      loopback  connected (externally)  lo
ens224  ethernet  connected (externally)  ens224

j@llmbits:~$ ip -br addr show ens224
ens224           UP             10.77.0.100/24

down deactivates but keeps the profile; delete removes it from disk. And the lab interface is exactly where it was, still carrying the address ip put on it, still untouched by any of this.

Making NetworkManager leave an interface alone

Everything above assumed a NIC you want NetworkManager to own. Sometimes you have one you do not: a lab bridge, a NIC handed to a container runtime, an interface a routing daemon manages. The failure mode is NetworkManager helpfully applying a DHCP-by-default profile to it, or dropping the address you configured, at a moment you were not expecting.

Three ways to prevent it, in increasing order of permanence:

Keeping an interface out of NetworkManager's hands
nmcli device set ens224 managed noImmediate and runtime only. Resets when NetworkManager restarts.
A file in /etc/NetworkManager/conf.d/An [keyfile] unmanaged-devices=interface-name:ens224 stanza. Permanent and the right answer for a server.
NM_CONTROLLED=noThe old RHEL ifcfg- way. Still honored on systems using the compatibility plugin.

The interface in this article was left as connected (externally) rather than fully unmanaged, which is the lighter-touch option: NetworkManager reports on it but applies nothing to it. That is fine for a lab and acceptable in production as long as no profile exists that could claim the device.

nmtui, for when you would rather not type

nmtui is the curses front end to the same daemon. Run it with no arguments and you get three menu items: edit a connection, activate a connection, set the system hostname. It writes the same profiles to the same directory, so anything you do there is visible to nmcli immediately.

It earns its place in one situation in particular: reconfiguring the interface you are connected over. nmtui makes the whole change in the editor and applies it in one action when you confirm, so there is no window where the profile is half-edited and your session is on the wrong side of it. Over a serial console or IPMI that is a real advantage. For anything repeatable, or anything you want in version control, nmcli is the tool.

FAQ

Why did my nmcli change not take effect?

Because connection modify writes the profile and nothing else. Run nmcli connection up <name> to reactivate it. Until you do, ip addr and nmcli connection show will disagree, which is the tell.

What does "connected (externally)" mean?

The device has an address that NetworkManager did not apply. Something else configured it: ip addr add, a container runtime, a routing daemon. NetworkManager creates a placeholder profile so the device appears in its inventory but does not manage it. On a lab or a manually configured interface this is the expected state.

Can I use ip and nmcli on the same host?

Yes, and this article is the demonstration. Use ip to read state and to test changes, and nmcli to persist them. What you should not do is make a change with ip on an interface NetworkManager actively manages and expect it to survive, because the next profile activation will overwrite it.

How do I add a permanent static route with nmcli?

sudo nmcli connection modify <name> +ipv4.routes "10.99.99.0/24 10.77.0.1", then sudo nmcli connection up <name>. The + appends rather than replacing the existing list. Policy rules go in ipv4.routing-rules the same way.

Why is my static address being ignored?

Almost always because ipv4.method is still auto. Setting ipv4.addresses without setting ipv4.method manual leaves the profile in DHCP mode and your address unused. Check with nmcli -g ipv4.method connection show <name>.

Key takeaways

  • A device is an interface; a connection is a saved profile. One device can have many profiles and only one is active at a time.
  • nmcli connection modify edits the profile on disk. Nothing changes on the wire until nmcli connection up.
  • Set ipv4.method manual when configuring a static address, or the profile stays in DHCP mode and ignores it.
  • Use + to append to list properties like ipv4.addresses and ipv4.routes. Without it you replace the whole list, which can drop the address you are connected over.
  • connected means NetworkManager owns the interface. connected (externally) means something else configured it and NetworkManager is only reporting.
  • nmcli device show is the one place that shows address, gateway, routes and DNS together, because DNS is not kernel state and ip cannot see it.
  • Profiles live in /etc/NetworkManager/system-connections/ as 0600 keyfiles. Edit them directly plus nmcli connection reload if that suits your tooling better.
  • Keep NetworkManager off an interface you manage yourself with an unmanaged-devices stanza in /etc/NetworkManager/conf.d/.

That completes the iproute2 core. Go back to the Linux ip command guide for the runtime side of everything you just persisted, or to ethtool for the driver layer below it. Everything in this cluster is indexed on the Linux networking commands guide.

Read next