Choosing between NETCONF, RESTCONF, and gNMI¶
The RESTCONF article ended on a rule of thumb: reach for RESTCONF for quick RESTful access, and NETCONF when you need transactions. This article unpacks that choice. All three interfaces — NETCONF, RESTCONF, and gNMI — drive the same YANG-modeled data; what differs is the transport, the transactional guarantees, and what each is genuinely good at. Pick the wrong one and you either fight the tooling or discover, mid-change, that the safety net you assumed isn't there.
One data model, three front doors¶
YANG models a device's configuration and operational data as containers, lists, and leaves in a
vendor-neutral way. Every interface here is just a different way to read and write that model, so the data
you manipulate is identical across all three — an ietf-interfaces interface is the same object whether
you reach it over SSH, HTTPS, or gRPC. That means the decision is never about what you can configure; it
is about how the change is delivered and what happens when something goes wrong halfway through.
NETCONF: transactions and datastores¶
NETCONF (RFC 6241) is the oldest and richest of the three.
It runs over SSH on TCP port 830 (RFC 6242), encodes
everything as XML, and models operations as remote procedure calls: get, get-config, edit-config,
copy-config, delete-config, lock, unlock, and close-session.
Its defining feature is the datastore model. The conventional datastores are running (the active
config), candidate (a scratch copy you edit freely), and startup (what survives a reboot). On a device
that advertises the :candidate capability you build up a batch of edits against candidate, optionally
validate it, then commit — and the change promotes to running all at once, or not at all. Combined
with lock, which stops anyone else from editing concurrently, this gives you genuine all-or-nothing
transactions. A :confirmed-commit goes further: the change rolls back automatically unless you send a
confirming commit within a timeout, so a config that cuts off your own management session undoes itself.
RFC 8342 (the Network Management Datastore Architecture, or NMDA) later added the intended datastore (validated config that should be in effect) and the operational datastore (the complete running state, including values the system applied on its own) — cleanly separating "what I asked for" from "what is actually true."
RESTCONF: the HTTP subset¶
RESTCONF (RFC 8040) was standardized to expose that same
YANG data over plain HTTPS with JSON, so any tool that makes a web request — curl, Postman, Python
requests — can drive a device. The trade is deliberate: RESTCONF is stateless and per-request. There
is no candidate datastore, no locking, no multi-step commit; each request stands alone. That is exactly
what makes it approachable, and exactly why it is the wrong tool when a change must land atomically across
many objects. (See Network Automation with RESTCONF for the URI
structure and the PUT-vs-PATCH distinction.)
gNMI: gRPC and streaming telemetry¶
gNMI (the gRPC Network Management Interface,
from OpenConfig) is the newest interface. It is built on gRPC — which runs over HTTP/2 — with all messages
defined as Protocol Buffers (proto3), and defines four RPCs: Capabilities, Get, Set, and
Subscribe. A single Set carries delete, replace, and update operations applied in that order and
committed as one unit.
Where gNMI pulls ahead is Subscribe: a bidirectional stream that pushes operational state as it changes,
which is the foundation of model-driven telemetry (see Model-Driven Telemetry).
For configuration it is capable, but it has no candidate/commit/lock model like NETCONF's — its atomicity
is per-SetRequest, not a durable multi-step transaction. There is also no single mandated TCP port;
9339 is common, but check your platform.
Side by side¶
| Aspect | NETCONF | RESTCONF | gNMI |
|---|---|---|---|
| Standard | RFC 6241 | RFC 8040 | OpenConfig spec |
| Transport | SSH (TCP 830) | HTTP(S) | gRPC / HTTP/2 |
| Encoding | XML | JSON or XML | Protobuf (proto3) |
| Transactions | Candidate + commit, locking, confirmed-commit | Per-request | Per-Set |
| Streaming telemetry | Limited | No | Yes (Subscribe) |
| Typical client | ncclient |
curl, requests |
gnmic, pygnmi |
| Best for | Careful transactional change | Quick RESTful access | Telemetry + modern config |
Choosing in practice¶
A few decisions resolve most cases:
- A multi-object change must be all-or-nothing (new VLAN and the interface that uses it, or nothing)
→ NETCONF, using a candidate commit so a partial failure leaves
runninguntouched. - A quick read, a one-field update, or a script a teammate can run from
curl→ RESTCONF. Its simplicity is the feature. - You want to stream interface counters or BGP state instead of polling → gNMI
Subscribe. - You are writing a Terraform provider or calling an existing REST toolchain → it is almost certainly RESTCONF under the hood already, so stay there.
When in doubt, let the risk of the change decide. Low-risk reads and single edits belong on the simplest interface that works; risky, multi-step configuration belongs on the one that can roll back cleanly.
A NETCONF transaction in Python¶
This is the capability RESTCONF cannot give you: lock the candidate, stage edits, and commit atomically. If
anything fails, running is never touched. Read the password from the environment, never the source.
# netconf_transaction.py — stage edits on candidate, then commit as one unit
import os
from ncclient import manager
PASSWORD = os.environ["NET_PASSWORD"]
CONFIG = """
<config>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<vlan>
<vlan-list><id>30</id><name>GUEST</name></vlan-list>
</vlan>
</native>
</config>"""
with manager.connect(host="192.0.2.11", port=830, username="automation",
password=PASSWORD, hostkey_verify=False,
device_params={"name": "iosxe"}) as m:
with m.locked(target="candidate"): # no concurrent edits while we work
m.discard_changes() # start from a clean candidate
m.edit_config(target="candidate", config=CONFIG)
m.validate(source="candidate") # reject bad data before it reaches running
m.commit() # all-or-nothing promotion to running
For a change that could sever your own session, add a safety net with a confirmed commit — the device reverts unless you confirm in time:
m.commit(confirmed=True, timeout="120") # auto-rollback after 120s
# ... verify reachability, then confirm ...
m.commit() # make it permanent
Key takeaways¶
- NETCONF, RESTCONF, and gNMI all read and write the same YANG models — choose on transport and transactional guarantees, not on what can be configured.
- NETCONF (RFC 6241, SSH/830, XML) owns real transactions: candidate datastore, locking,
commit, and confirmed-commit auto-rollback. Reach for it when a change must be all-or-nothing. - RESTCONF (RFC 8040, HTTPS/JSON) is stateless and per-request — simplest to call, but no locking or multi-step commit.
- gNMI (gRPC/proto3) shines at streaming telemetry via
Subscribe; itsSetis atomic per request but it lacks NETCONF's durable transaction model. - NMDA (RFC 8342) adds the intended and operational datastores, separating desired config from observed state.
- Let the risk of the change pick the interface: simplest tool for low-risk reads and edits, transactional NETCONF for anything that must roll back cleanly.
Sources: RFC 6241 — NETCONF Protocol, RFC 6242 — Using the NETCONF Protocol over SSH, RFC 8040 — RESTCONF Protocol, RFC 8342 — Network Management Datastore Architecture, OpenConfig gNMI specification, ncclient documentation.