NETCONF with Python (ncclient)
Objective
Learn how to use the Python ncclient library to interact with an IOS XE device via NETCONF. You will connect from a management host, retrieve configuration with <get-config>, and perform a simple <edit-config> to change a hostname and verify the change from the device CLI. This matters in production because NETCONF lets automation tools make transactional, structured configuration changes (rather than fragile CLI scripting). In real networks, NETCONF is used for consistent Day-1 and Day-2 operations — for example, safely updating device configuration across many switches/routers without logging into each device manually.
Quick Recap
This lesson continues from the topology used in Lesson 1. No new network devices are added for this lesson; we will use the management-plane link already present between the controller (management PC) and Router1.
ASCII Topology (management-plane focus)
- Note: exact IPs used in this lesson are shown on the interfaces below.
Management PC (controller)
Hostname: controller.lab.nhprep.com
IP: 10.0.0.10/24
(ethernet)
10.0.0.0/24
+---------------+
| Router1 |
| IOS XE |
| Gig0/0: 10.0.0.1/24 |
+---------------+
Device table
| Device | Role | Interface / Address |
|---|---|---|
| Router1 | NETCONF target | GigabitEthernet0/0 — 10.0.0.1 |
| Controller | Management host | eth0 — 10.0.0.10 |
Credentials and general values used in examples
- Username: netconfadmin
- Password: Lab@123
- Domain / organization: lab.nhprep.com / NHPREP
- NETCONF default port: 830 (NETCONF over SSH)
Tip: This lesson assumes Router1 already runs IOS XE with NETCONF support available. The controller runs Python 3 and ncclient.
Key Concepts (theory + how it behaves)
- NETCONF transport and port — NETCONF commonly runs over SSH on TCP port 830. The controller opens an SSH session to the device, negotiates NETCONF as a subsystem, then exchanges XML RPCs. This means firewalls and ACLs must allow TCP/830 between controller and device.
- RPC operations:
and —<get-config>reads a configuration datastore (commonly "running").<edit-config>sends a full or partial configuration to be merged into the target datastore. These are atomic operations from the controller’s perspective. - YANG models and structured data — NETCONF carries configuration as XML (or XML mapped from YANG). Structured config avoids brittle text parsing. In production, you rely on YANG modules the device supports to build correct payloads.
- Session behavior — When the NETCONF session is established over SSH, the device tracks the NETCONF session. Long-running tasks (subscriptions, push telemetry) require leaving the session open; tools like YANG Suite mention "Start Session" to keep it alive. In automation, manage sessions explicitly to avoid premature closure.
- ncclient library role — ncclient implements a NETCONF client in Python. It handles the SSH connection and RPC framing; you provide the payload (XML or filters) and call operations such as get_config() and edit_config().
Step-by-step configuration
Step 1: Prepare Router1 — configure management IP, user, SSH, and enable NETCONF
What we are doing: Configure the router’s management interface, create a local administrative user for NETCONF, enable SSH, and enable the NETCONF server. These are required so the Python ncclient can successfully open an SSH NETCONF session and perform RPCs.
configure terminal
interface GigabitEthernet0/0
ip address 10.0.0.1 255.255.255.0
no shutdown
exit
ip domain-name lab.nhprep.com
username netconfadmin privilege 15 secret Lab@123
crypto key generate rsa modulus 2048
ip ssh version 2
netconf-yang
end
write memory
What just happened:
- The interface configuration assigns the management IP so the controller can reach the device.
username ... secret Lab@123creates an administrative account used for NETCONF authentication.crypto key generate rsaandip ssh version 2ensure the device has SSH keys and will accept SSHv2 connections.netconf-yangenables the NETCONF server (NETCONF over SSH) so the device will accept NETCONF subsystem connections on port 830.
Real-world note: In production, use AAA (RADIUS/TACACS+) for NETCONF authentication and enable certificate-based authentication for stronger security. Local accounts are OK for labs.
Verify:
show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 10.0.0.1 YES manual up up
show running-config | section username
username netconfadmin privilege 15 secret 0 Lab@123
show running-config | include netconf
netconf-yang
Expected outputs above show the interface IP, the username entry, and that netconf-yang is present in running-config. These confirm the device is reachable and NETCONF is enabled.
Step 2: Prepare the controller environment and install ncclient
What we are doing: On the management host we will install Python and the ncclient library so we can run NETCONF operations programmatically. This step matters because ncclient is the library that implements NETCONF RPC framing and SSH transport for Python.
Commands to run on the management host (controller):
sudo apt update
sudo apt install -y python3 python3-venv python3-pip
python3 -m venv ~/netconf-env
source ~/netconf-env/bin/activate
pip install --upgrade pip
pip install ncclient
What just happened:
- A Python virtual environment isolates dependencies.
ncclientis installed; it provides a Manager API for NETCONF actions such as connect(), get_config(), and edit_config().- In production, you would run automation from a centralized controller or CI/CD system rather than a laptop.
Real-world note: In enterprise automation, virtual environments are commonly encapsulated inside CI/CD pipelines or container images so runs are repeatable.
Verify (on controller):
python3 - <<'PY'
from ncclient import manager
print("ncclient version:", manager.__version__ if hasattr(manager, "__version__") else "ncclient available")
PY
Expected output:
ncclient version: <version-number> # shows that ncclient is importable; version varies by install
Step 3: Use ncclient to retrieve the running configuration with
What we are doing: Connect to Router1 using ncclient and perform <get-config> against the running datastore. This demonstrates reading structured configuration over NETCONF and validates connectivity and auth.
Python script (controller):
from ncclient import manager
HOST = "10.0.0.1"
PORT = 830
USER = "netconfadmin"
PASS = "Lab@123"
with manager.connect(host=HOST, port=PORT, username=USER, password=PASS,
hostkey_verify=False, look_for_keys=False, allow_agent=False) as m:
# Retrieve the full running-config
config = m.get_config(source='running').data_xml
print("=== running-config via NETCONF ===")
print(config)
What just happened:
- ncclient established an SSH connection to 10.0.0.1:830 and initiated NETCONF.
get_config(source='running')sends a<get-config>RPC; the device responds with XML containing the running configuration or an appropriate YANG-encoded portion.hostkey_verify=Falseis used here for lab convenience; in production, verify host keys or use certificate-based auth for security.
Real-world note: In production, set
hostkey_verify=Trueand manage SSH host keys centrally (or use certificate auth). Do not disable verification in automated jobs.
Verify (on controller output) The script should print the device configuration in XML. Example snippet (abbreviated here for clarity; the actual output is full XML returned by the device):
=== running-config via NETCONF ===
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<hostname>Router1</hostname>
<interface>
<GigabitEthernet>
<name>0/0</name>
<description>Management</description>
<ip>
<address>
<primary>
<address>10.0.0.1</address>
<mask>255.255.255.0</mask>
</primary>
</address>
</ip>
</GigabitEthernet>
</interface>
</native>
</data>
</rpc-reply>
(Your actual XML will include the full YANG-modeled elements the device exports.)
Step 4: Perform an — change the hostname
What we are doing: Use <edit-config> to change the device hostname to demonstrate a write operation. This shows how automation can perform configuration changes using a transactional RPC that the device applies to the running datastore.
Python script to change hostname:
from ncclient import manager
HOST = "10.0.0.1"
PORT = 830
USER = "netconfadmin"
PASS = "Lab@123"
# XML payload to change hostname - modeled for Cisco native YANG
hostname_payload = """
<config>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<hostname>NETCONF-ROUTER</hostname>
</native>
</config>
"""
with manager.connect(host=HOST, port=PORT, username=USER, password=PASS,
hostkey_verify=False, look_for_keys=False, allow_agent=False) as m:
result = m.edit_config(target='running', config=hostname_payload)
print(result)
What just happened:
edit_configsends an<edit-config>RPC with the XML payload. The device parses the payload according to its supported YANG modules and applies the change to the running configuration.- The operation is transactional at the RPC level — if the payload violates validation rules, the device will return an error and not partially apply the change.
- In production, you should combine validation (
validate="true"where supported) and possiblycandidatedatastores if the device supports them to stage changes.
Real-world note: Use audit trails and change control when automating config changes. NETCONF gives you programmatic error returns — parse them and abort or rollback as needed.
Verify (device CLI):
show running-config | section hostname
hostname NETCONF-ROUTER
Expected output shows the new hostname. Also verify with NETCONF read again:
# quick get to verify via NETCONF
from ncclient import manager
with manager.connect(host="10.0.0.1", port=830, username="netconfadmin", password="Lab@123",
hostkey_verify=False, look_for_keys=False, allow_agent=False) as m:
print(m.get_config(source='running').data_xml)
You should see <hostname>NETCONF-ROUTER</hostname> in the returned XML.
Step 5: Clean up — revert hostname and close session
What we are doing: Revert the hostname to the original value and ensure the NETCONF session is closed cleanly. Cleanup prevents unintended changes from persisting across lessons.
Reversion via CLI (on Router1):
configure terminal
hostname Router1
end
write memory
What just happened:
- The CLI change reverts the hostname; writing memory persists the change to startup-config.
- Closing the NETCONF session is handled automatically by the
withcontext in Python or by explicit close() calls in long-running scripts.
Verify:
show running-config | section hostname
hostname Router1
Expected output shows the hostname restored to Router1.
Verification Checklist
- Check 1: The router accepts NETCONF connections on port 830 — verify by successfully running the ncclient connect() and receiving
<rpc-reply>for<get-config>. - Check 2: Read the running configuration via
<get-config>— verify the printed XML contains expected interface and hostname entries. - Check 3: Write a small change with
<edit-config>(hostname) and verify withshow running-config | section hostnameand subsequent<get-config>retrieving the same hostname in XML.
Common Mistakes
| Symptom | Cause | Fix |
|---|---|---|
| ncclient connection times out | Firewall or ACL blocking TCP/830 between controller and Router1 | Ensure TCP port 830 is allowed; test with telnet 10.0.0.1 830 or nc -vz 10.0.0.1 830 |
| Authentication errors from ncclient | Wrong username/password or hostkey verification issues | Confirm local username and password (netconfadmin / Lab@123). For lab, set hostkey_verify=False; for production use verified host keys or certificates |
| get-config returns empty or minimal XML | The device exports only limited YANG modules or filter used incorrectly | Request broader data (no filter) or confirm which YANG modules the device supports. Use YANG Suite or device docs to determine available models |
| edit-config returns an RPC error | Invalid XML payload or wrong YANG namespace | Validate payload against device YANG models. Use <get-config> output to learn expected structure, and test small edits first |
| Changes not persistent after reboot | Changes applied only to running datastore and not written to startup | After successful edit-config, run write memory via CLI or perform NETCONF write to startup if supported; in practice, combine edit-config with persistent storage steps |
Key Takeaways
- NETCONF runs over SSH (default TCP port 830) and provides structured, transactional config operations such as
<get-config>and<edit-config>. This reduces fragile CLI parsing and makes automation more reliable. - ncclient is a Python client library that handles the NETCONF transport and RPC framing; your scripts supply the XML payloads and parse RPC replies.
- Always manage session security: use SSH host key verification or certificate-based auth in production, and do not leave hostkey_verify disabled.
- Test read operations first (
<get-config>) and validate payloads against device YANG models before performing writes. In production, use staged changes, validation, and rollback strategies to avoid outages.
Warning: In production networks, do not disable host key verification or use plaintext secrets. Use centralized credential stores and certificate-based authentication for NETCONF.