Skip to content

Cisco IOS / IOS-XE

This page covers administrative access — engineers logging in to the CLI — on Cisco IOS and IOS-XE. It is the device-side counterpart to TACACS+ and RADIUS.

For endpoint access control (802.1X and MAB) on the same platforms, see Build an 802.1X lab, which walks the switch side end to end. The NAC section below records what differs between software trains, and what Taranac sends to a Cisco device.

PlatformSoftwareWhat was verified
Cisco IOSv (vios_l2)15.2TACACS+ login, enable, command authorization and accounting at levels 0/1/15; RADIUS administrative login with exec authorization and accounting
Cisco IOL (X86_64BI_LINUX_L2)17.12.1 (Dublin)TACACS+ login, enable, command authorization and accounting at levels 0/1/15

Both are virtual platforms. The AAA configuration below is not platform-specific — it is the same on Catalyst, ISR and Nexus-adjacent IOS-XE gear — but output formats differ slightly between trains, and those differences are called out where they matter.

The good news for anyone running a mixed estate: the TACACS+ device-administration configuration is identical on 15.x and 17.x. The named tacacs server block replaced the legacy tacacs-server host global on 15.x already, so one template covers both.

This is the standard shape: Taranac authenticates the login, decides the privilege level, authorizes every command, and records everything.

aaa new-model
!
! ── The server and its group ────────────────────────────────────
tacacs server TARANAC-1
address ipv4 10.0.0.20
key <TACACS_SECRET>
timeout 30
!
aaa group server tacacs+ TARANAC
server name TARANAC-1
!
! ── Authentication ──────────────────────────────────────────────
aaa authentication login default group TARANAC local
aaa authentication login CONSOLE local
aaa authentication enable default group TARANAC enable
!
! ── Authorization ───────────────────────────────────────────────
aaa authorization config-commands
aaa authorization exec default group TARANAC local
aaa authorization commands 0 default group TARANAC local
aaa authorization commands 1 default group TARANAC local
aaa authorization commands 15 default group TARANAC local
!
! ── Accounting ──────────────────────────────────────────────────
aaa accounting exec default start-stop group TARANAC
aaa accounting commands 0 default start-stop group TARANAC
aaa accounting commands 1 default start-stop group TARANAC
aaa accounting commands 15 default start-stop group TARANAC
!
! ── Source the packets from the registered address ──────────────
ip tacacs source-interface Vlan1
!
line con 0
login authentication CONSOLE
privilege level 15
line vty 0 4
login authentication default
transport input ssh

Do not save it yet. Apply it to the running configuration, then verify a fresh login before write memory. While the startup configuration is still clean, a console reload undoes a lockout in one step. Once a new session logs in and lands where it should, save it: a switch that reloads with half of this missing fails in ways that look nothing like a missing configuration.

This is the single most misunderstood part of Cisco AAA, and it fails silently in the permissive direction, which is the worst way for a security control to fail.

aaa authorization commands and aaa accounting commands take one privilege level per line. There is no all keyword, no range, and no wildcard. IOS will not warn you about levels you did not list — it simply never sends those commands to Taranac.

Levels 0, 1 and 15 cover the stock IOS command set and are what the example configures:

LevelWhat lives there
0enable, disable, exit, help, logout — five commands, easy to forget, and enable is not a command you want unaudited
1User EXEC — the read-only show commands an operator has by default
15Privileged EXEC and configuration mode — everything else

If you use custom privilege levels, you must add a line for each one. Any deployment that has run privilege exec level 7 … to build a tiered operator role needs aaa authorization commands 7 and aaa accounting commands 7 as well. Otherwise the tier you carefully built is the one tier nobody is checking. The rule is simply: every privilege level in use on the device needs both lines.

aaa authorization config-commands is the related trap one level up. Without it, commands typed in configuration mode are not sent for authorization even when aaa authorization commands 15 is present — exec-mode commands are checked, config-mode commands sail through. Since configuration mode is where the damage happens, the line belongs in every deployment that authorizes commands at all.

Every line in the block above routes authentication to a server that can become unreachable. Two safeguards make that survivable, and both are in the example:

  • The local fallback keyword. group TARANAC local tries Taranac first and falls back to the local user database only when the server does not answer. It does not fall back when Taranac answers with a reject — a denied login stays denied, which is what you want. This requires a local account to actually exist: keep a username … privilege 15 secret … on the device.
  • A separate console method list. aaa authentication login CONSOLE local bound to line con 0 keeps the console on local authentication permanently. When the network is down and the TACACS+ server is unreachable, the console is how you get in. A device whose console also points at default can lock you out completely.
  • privilege level 15 on that same console line. Without it the rescue account gets you a prompt and nothing else — see the box below.

aaa authentication enable default group TARANAC enable ends in the enable keyword, falling back to the locally configured enable secret. That fallback fires only when the server is unreachable. A reject is a valid answer, and no local or enable keyword rescues you from one.

ip tacacs source-interface Vlan1 pins the source address of TACACS+ packets to that interface’s IP. That address is what Taranac matches against the registered device.

Leave it out and IOS picks the egress interface closest to the server, which is usually right — until a routing change, a second uplink or an SVI renumbering makes it wrong. The failure is unhelpful: Taranac sees a request from an unregistered client and drops it without logging a policy decision, so the device reports a timeout and falls back to local while Taranac’s logs show nothing at all. Pin it explicitly.

The same applies to RADIUS via ip radius source-interface.

The example sets timeout 30, not the familiar 5 seconds. That is deliberate.

When a user is enrolled in push MFA, Taranac holds the AAA exchange open while it sends a push to their phone and waits for them to approve it. The device must stay willing to wait for a human. At timeout 5 the switch gives up long before anyone can reach into a pocket, and the login fails over to the local database — which looks exactly like a server outage.

The effective approval window is the smaller of the device timeout and Taranac’s own server-side cap. Raising one side alone changes nothing; if you want to give admins more time to tap “approve”, raise both together.

For TACACS+ this is clean — the TCP connection simply stays open for the duration. See the RADIUS section for why the UDP case behaves differently.

Cisco’s PPP and interface authorization families carry their index inside the attribute name: inacl#1, outacl#1, route#1, interface-config#1, rte-fltr-in#1. They go on a TACACS+ profile as raw AV-pairs, against the service the device actually asks for (ppp, slip) rather than shell.

Taranac accepts # anywhere in an attribute name except as the first character. # is tac_plus-ng’s comment character, but only when it opens a token: set inacl#1 = "…" parses, while set #attr = "…" comments out the rest of its own line and stops the whole configuration from loading. That is why the ban stays exactly where it is — one bad attribute name would take AAA down for every device, not just the profile carrying it.

show tacacs is the first stop. The output differs between trains, and 17.x is considerably more useful:

SW#show tacacs
Tacacs+ Server - public :
Server name: TARANAC-1
Server address: 10.0.0.20
Server port: 49
Socket opens: 875
Socket closes: 875
Socket aborts: 0
Socket errors: 0
Socket Timeouts: 0
Failed Connect Attempts: 0
Total Packets Sent: 938
Total Packets Recv: 938

Health has to be inferred from the counters. Socket opens matching Socket closes with zeros across aborts, errors and timeouts is a healthy server. Any movement in Socket Timeouts or Failed Connect Attempts means reachability trouble — wrong address, wrong port, or a firewall in the path.

Then confirm the whole chain end to end:

  • test aaa group TARANAC <user> <password> legacy — authenticates without risking your session, and tells you whether the secret and policy are right.
  • Log in from a second session and run something your policy should deny. A denial arriving from Taranac is the only real proof that command authorization is live; a successful login proves only authentication.
  • Check the accounting records in Taranac. Commands appearing under the right user, with the right privilege level, confirms the accounting lines took effect — and is how you catch a missing privilege level before an auditor does.

Some estates standardise on RADIUS for administrative login. IOS supports it, with real limitations. Note the named method list (VTY) rather than default — it keeps the console untouched, which serves the same rescue purpose as the CONSOLE list above.

aaa new-model
!
radius server TARANAC-1
address ipv4 10.0.0.20 auth-port 1812 acct-port 1813
timeout 30
retransmit 1
key <RADIUS_SECRET>
!
aaa group server radius TARANAC-R
server name TARANAC-1
!
aaa authentication login VTY group TARANAC-R local
aaa authorization exec VTY group TARANAC-R local
aaa accounting exec VTY start-stop group TARANAC-R
!
! enable has one method list on IOS, and it is always 'default'
aaa authentication enable default group TARANAC-R enable
!
ip radius source-interface Vlan1
radius-server attribute 6 on-for-login-auth
radius-server attribute 8 include-in-access-req
radius-server attribute 25 access-request include
!
line con 0
! no AAA here — local console rescue
line vty 0 4
login authentication VTY
authorization exec VTY
accounting exec VTY
transport input ssh

Taranac returns the privilege level as a Cisco AV-pair, shell:priv-lvl=15, rendered from a RADIUS profile — see RADIUS profiles. aaa authorization exec is what makes the device apply it; without that line the user authenticates and lands at privilege 1 regardless of what the profile said.

A profile that leaves the timeouts blank still sends them. The built-in Cisco IOS / IOS-XE RADIUS template — vendor ID 9, Cisco-AVPair as VSA 1, one templated shell:priv-lvl={level} attribute — defaults to Service-Type = Login-User, Session-Timeout = 3600 and Idle-Timeout = 600, and the renderer falls back to those whenever the profile itself carries nothing. Both seeded profiles, General Full Access (priv 15) and General Read-Only (priv 1), inherit them. Set the timeouts explicitly on the profile if an hour is not what an administrative session should get.

The three radius-server attribute lines are worth keeping. They make the switch include Service-Type on login authentication, Framed-IP-Address, and the Class attribute in accounting — the last one is what lets Taranac correlate an accounting record back to the session that produced it.

RADIUS has no privilege-escalation exchange in any RFC, but IOS sends one anyway. enable at a privilege-1 prompt produces an ordinary Access-Request whose User-Name is the synthetic $enab<N>$ — there is no person in it at all. Three things were measured on IOSv 15.2 and are in no Cisco document:

  • The name generalises. enable 7 and enable 5 send $enab7$ and $enab5$, not only $enab15$.
  • Service-Type does not carry the level. It is Administrative-User (6) for every level; the level exists only inside the username.
  • A bare Access-Accept is enough. The switch reached privilege 15 on a reply carrying no vendor attributes whatsoever.

Since 1.2.5 Taranac answers these instead of rejecting them as an account nobody created. The password is a single global enable password on Settings → RADIUS, accepted for every requested level. Because the request names nobody, there is no user, no group, no policy rule and nothing for MFA to apply to — which is the argument for the main path: grant the level at login through shell:priv-lvl, where the person is known and MFA works. The mechanism, the log badge and the rejection reasons are on RADIUS → Privilege escalation (enable).

Cisco IOS / IOS-XE is in the family this covers, alongside Cisco Business, H3C Comware 7 and Eltex ESR. Cisco ASA is not — it sends the operator’s real name for an escalation, so Taranac answers it as an ordinary login and always did.

Push MFA over RADIUS deserves one note. RADIUS is UDP, so the instinct is to raise retransmit to widen the approval window. It does not work that way: the push budget is capped on the server side, and retransmits arriving during the wait are deduplicated and answered from cache — they neither extend the window nor trigger a second push. The cap is radius.max_request_time, 30 seconds by default, which is why the device example uses timeout 30. Raise the two together; leave retransmit low.

Verify with show aaa servers, which reports RADIUS only — TACACS+ never appears there, which is why a TACACS-only device returns nothing at all:

SW#show aaa servers
RADIUS: id 1, priority 1, host 10.0.0.20, auth-port 1812, acct-port 1813
State: current UP, duration 674590s, previous duration 0s
Dead: total time 0s, count 0
Authen: request 3, timeouts 0, failover 0, retransmission 0
Response: accept 3, reject 0, challenge 0
Response: unexpected 0, server error 0, incorrect 0, time 131ms
Account: request 5, timeouts 0, failover 0, retransmission 0
Request: start 3, interim 0, stop 2
Response: start 3, interim 0, stop 2

State: current UP with Dead: count 0 is what healthy looks like. The challenge counter is the one to watch when MFA is in play — a non-zero value means Access-Challenge exchanges are happening, which is the multi-step authentication working as intended.

The switch-side 802.1X and MAB configuration for Cisco IOS is covered step by step in Build an 802.1X lab — global plumbing, the NAC server group on ports 1814/1815, the CoA listener, and the access-port template with authentication open as monitor mode. It is not repeated here.

What belongs on this page is the part that changed between software trains. IOS 15’s authentication port commands and IOS-XE’s session manager are different generations of the same feature, and the verification commands are not interchangeable:

TaskIOS 15.xIOS-XE 17.x
List sessionsshow authentication sessionsshow access-session
One port in detailshow authentication sessions interface Gi1/1 detailsshow access-session interface Gi1/1 details
Port configuration styleauthentication commands on the interfaceIBNS 2.0 — policy-map type control subscriber

On 17.x, show authentication sessions is rejected outright as an invalid command rather than deprecated with a hint, which sends people looking for a configuration fault that does not exist. If a session list command errors out, try the other spelling before assuming the port is broken.

Both trains accept the same Taranac-side authorization results, since those arrive as RADIUS attributes rather than CLI. What Taranac sends for a Cisco device:

Profile settingAttributes sent
VLANTunnel-Type = VLAN, Tunnel-Medium-Type = IEEE-802, Tunnel-Private-Group-ID = <vlan>
Named ACLFilter-Id = <name> — the ACL must already exist on the switch
Downloadable ACLone Cisco-AVPair += ip:inacl#<rule> per rule, <rule> being the text you typed
URL redirectCisco-AVPair += url-redirect=<url> and url-redirect-acl=<acl>
Session timeoutSession-Timeout, Termination-Action = RADIUS-Request

Cisco IOS / IOS-XE is the only vendor in the shipped dictionary that declares url_redirect at all, which makes the captive-portal redirect flow a Cisco capability out of the box. Downloadable ACLs are declared by four — Cisco IOS, Cisco NX-OS, Juniper and Huawei; everywhere else the inline rules are skipped and the render warns about it.

For CoA, Cisco IOS / IOS-XE is the best-covered platform in the dictionary. It is one of only three vendors — with Cisco NX-OS and Juniper — whose re-authentication is a native CoA-Request (subscriber:command=reauthenticate plus subscriber:reauthenticate-type=last); every other vendor falls back to a Disconnect and waits for the endpoint to come back on its own. Bounce port (subscriber:command=bounce-host-port) is shared with Aruba, Extreme EXOS and Ruckus ICX; port shutdown (subscriber:command=disable-host-port) only Cisco and Ruckus ICX declare at all. A VLAN change is a CoA too, but it carries the Tunnel-* triplet rather than a subscriber:command.

SymptomLikely cause
Login hangs, then succeeds with local credentialsServer unreachable. Check Socket Timeouts / Failed Connect Attempts in show tacacs, or State in show aaa servers.
Login rejected, and Taranac logs show nothingThe request never arrived as a known client — source address does not match the registered device (ip tacacs source-interface), or the secret does not match. Unknown clients are dropped without a policy decision.
Login works, user lands at privilege 1Missing aaa authorization exec, or the matched policy rule returns no profile.
Push MFA always deniesDevice timeout too low. Raise it to 30 s and match the server-side cap.
Commands run that policy should denyNo aaa authorization commands line for that privilege level, or aaa authorization config-commands missing for config mode.
Commands missing from the audit trailNo aaa accounting commands line for that privilege level.
Locked out after applying AAANo CONSOLE method list, or no local account for the local fallback. Recover via console or password recovery — or, if you have not saved yet, by reloading.
Console rescue logs in, but enable is deniedExpected under aaa new-model: the console lands at privilege 1 and enable asks the server for $enab15$. Bind privilege level 15 to line con 0.
Enable over RADIUS rejected as an unknown userBefore 1.2.5 $enab<N>$ was looked up as an account. On 1.2.5+ set the global enable password on Settings → RADIUS: a wrong one is reported as enable_bad_password, an unset one as enable_not_configured.
Saving a TACACS+ profile holding inacl#1 returns 422Releases 1.2.2 – 1.2.7 banned # in an attribute name. Fixed in 1.2.8.
dACL is accepted but the rules do nothingThe sequence number is missing — the rule must start with <n>=, since Taranac appends it to ip:inacl# verbatim.
show authentication sessions is invalidIOS-XE 17.x — use show access-session.
show aaa servers returns nothingExpected on a TACACS-only device. Use show tacacs.