Guide

How to Configure AAA Local Authentication on Cisco IOS

login local gets SSH working, and that is where most hardening walkthroughs stop. AAA is the next step: one framework that decides, per line, which database answers a login and what happens when that database cannot. It is also the command on IOS most likely to lock you out of your own router, because aaa new-model changes how every line authenticates the instant you press Enter — before you have written a single method list. This guide covers what that command silently changes, the local database it will read, named method lists for the VTY and console lines separately, privilege levels for tiered access, and the rollback habit that makes all of it safe to practice. It assumes SSH already works; if it does not, start with how to configure SSH on a Cisco router or switch.

What aaa new-model changes the moment you type it

aaa new-model is not a switch you flip now and configure later. It takes effect immediately, and it changes the authentication rules on lines you have not touched.

Before AAA, each line decides for itself. line vty 0 4 with password and login checks a shared line password. login local checks the local username database. no login checks nothing at all. After aaa new-model, the line is no longer the decision — a method list is, and the line only names which list to use. If you have not written one, IOS falls back to an implicit default of local. The scope is wider than it looks: it applies local authentication to all lines and interfaces at once, with line con 0 the one exception to that implicit default.

Read that exception carefully, because it produces two opposite mistakes. The first is immediate: your VTY lines were on a shared line password, no username exists anywhere in the config, and the next SSH login is refused because the router now wants a username that was never created. The line stanza will not tell you that: on many images IOS strips login and login local off the lines as AAA takes over, so an orphaned password under a line that says nothing about how it authenticates is expected, not a config the router ate. login authentication <list> is the only line command left that decides anything.

The second mistake is slower and worse. People learn that the console is exempt and file that away as a guarantee. It is not one. line con 0 sits outside the implicit default only. The moment you configure aaa authentication login default <methods>, that list governs the console too, alongside every other line. A default list that cannot answer takes the console with it.

Enable mode moves under the same framework. Without AAA, enable secret is checked directly. With AAA, aaa authentication enable default decides how a user reaches privileged EXEC, and the implicit method is enable — the secret you already set. That is fine until somebody writes a default enable list that cannot answer, at which point the router authenticates you at level 1 and refuses to let you climb.

One thing the command does not do is tear down your current session. Line authentication is evaluated at login, not continuously, so the session you are typing in survives while every new login follows the new rules. That gap is your entire safety margin; the last section is about spending it deliberately.

! Before: the line carries the policy
R1(config)# line vty 0 4
R1(config-line)# login local
R1(config-line)# transport input ssh
R1(config-line)# exit

! After: AAA carries the policy, and this command acts at once
R1(config)# aaa new-model

Configuration only. The second command changes how every line authenticates before a single method list exists.

Line loginBefore aaa new-modelAfter aaa new-model
What decides the loginThe login command on the line itselfA method list; the line only names which list runs
A shared line passwordPrompted for, and nothing else is neededNot what a local method reads — it wants a username that exists
A line with no list namedBehaves exactly as configuredFollows the default list, implicitly local until you define one
line con 0Behaves exactly as configuredOutside the implicit default only — a default list you write does apply
Reaching enable modeenable secret is checked directlyaaa authentication enable default decides, with enable as the implicit method
Blast radius of one mistakeThe line you editedEvery line at once, including ranges you never opened

Build the local database before you turn AAA on

Order matters, and it is the cheapest mistake to avoid. Create the accounts first, then enable AAA. Doing it the other way round opens a window in which the implicit local list is pointing at an empty database, and the width of that window is however long it takes you to type the next command.

username admin privilege 15 secret <password> is the line you want. The choice between secret and password is the whole of local credential storage on IOS. secret stores a one-way hash: reading the configuration does not give anyone the original string. password stores the value in a form the device can read back, which means so can anybody holding the config file. There is no scenario in a management-plane design where password is the better choice on a username line.

enable secret does the same job for privileged EXEC and takes precedence over enable password wherever both exist. Configure the secret and remove the password outright. Keeping both leaves a reversible string in the running-config for no benefit. Under AAA there is a second reason to get this right: the enable secret is the credential behind the enable method, which is the fallback the next section leans on. A method list that falls back to enable with no enable secret configured falls back to nothing.

Newer images let you choose the hash rather than accept the default. username admin privilege 15 algorithm-type scrypt secret <password> produces a type 9 hash where the older default is type 5 MD5. Check what your image accepts instead of assuming — CML reference images differ by platform and release, and an option on IOS-XE may not exist on the IOL node next to it. If the keyword is rejected, the type 5 hash is still vastly better than a reversible password.

Pick different strings for the admin account and the enable secret. If they match, the local enable fallback in the next section stops being a fallback and becomes a second door with the same key.

service password-encryption is obfuscation, not security

service password-encryption takes the reversible passwords in the configuration — line passwords, enable password, any username ... password — and rewrites them as type 7. Type 7 is a Vigenère cipher with a published key. Any decoder on the internet reverses it instantly, and so does Cisco equipment, because reversing it is the whole point of the format.

It is still worth turning on. It defeats shoulder-surfing, it keeps clear-text credentials out of screenshots pasted into tickets, and it costs nothing. What it does not do is protect a configuration file from anyone who obtains it. Treat every type 7 string in a config you are reviewing as though it were printed in the clear, because to an attacker it is.

Two behaviors surprise people. It covers the passwords present when you enable it as well as any added afterwards, so turning it on late does not leave the existing entries behind. And turning it off does not restore clear text — the already-encoded entries stay type 7 until somebody retypes them. It does not touch secret hashes either, which were never reversible to begin with and gain nothing from it.

R1(config)# username admin privilege 15 secret <strong-password>
R1(config)# username noc privilege 5 secret <different-password>
R1(config)# enable secret <a-third-password>
R1(config)# no enable password
R1(config)# service password-encryption

Accounts and the enable secret go in before AAA does, not after.

Named method lists with fallback, applied per line

A method list is an ordered list of ways to answer one question. aaa authentication login VTY-AUTH local enable reads as: to log this user in, try the local database; if that cannot answer, use the enable secret. The name is arbitrary and local to the device — what matters is that a line points at it.

The ordering rule is where most of the misunderstanding lives. AAA moves to the next method only when the current one returns an error, meaning the method could not answer at all. A failure — which is what a wrong password produces — ends the attempt there and no later method is consulted. So local enable is insurance against the local database being unusable. It is not a second guess at a password you fat-fingered. Test that insurance rather than assume it: if you add exec authorization by local later, confirm the enable fallback still lands you in a usable session.

Two lists exist here for a reason. Remote access and physical access are different risks, and a list applied per line is how IOS lets you say so. The VTY list carries the fallback, because a locked-out VTY on a device in another building is a drive, a badge and an afternoon. The console list does not need one: if you are at the console you have already solved the access problem, and giving the console the weaker path is precisely backwards.

Use the default list only when you genuinely mean every line, because that is exactly what it does — console included. Named lists are not more work; they are the same commands with the scope written down.

R1(config)# aaa new-model
R1(config)# aaa authentication login VTY-AUTH local enable
R1(config)# aaa authentication login CONSOLE-AUTH local
R1(config)# line vty 0 4
R1(config-line)# login authentication VTY-AUTH
R1(config-line)# transport input ssh
R1(config-line)# exec-timeout 5 0
R1(config-line)# exit
R1(config)# line vty 5 15
R1(config-line)# login authentication VTY-AUTH
R1(config-line)# transport input ssh
R1(config-line)# exec-timeout 5 0
R1(config-line)# exit
R1(config)# line con 0
R1(config-line)# login authentication CONSOLE-AUTH
R1(config-line)# exec-timeout 5 0
R1(config-line)# logging synchronous

Two lists, applied per line. Every VTY range gets one, not just 0 through 4.

Design choiceVTY list (remote)CONSOLE list (physical)
Typical methodslocal enable — local database, enable secret behind itlocal — local database only
Why a fallbackLockout costs a site visit, so keep a second way inYou are already at the device; a fallback only weakens it
Applied withlogin authentication VTY-AUTH under every line vty rangelogin authentication CONSOLE-AUTH under line con 0
If you forget to apply itThe range follows default — often still works, so the gap hidesThe console follows default too, which is the lockout
Paired hardeningtransport input ssh and an access-class on the same linesexec-timeout and logging synchronous

The lockout that follows a missing console list

Write VTY-AUTH, apply it to the VTY lines, feel finished. The console names no list, so it falls back to default — and what that means depends entirely on whether a default exists. If you never defined one, Cisco’s own note on aaa authentication login is blunt: on the console, login succeeds without any authentication check at all. The door you were keeping as your way back in is standing open. If you did define a default and pointed it at a database with no accounts, or at a server this router cannot reach, the console follows that instead and the same door is locked from the outside. One omission, two opposite failures, and neither is the one you meant.

This is why the graded lab builds CONSOLE-AUTH explicitly rather than leaning on whatever the default happens to be. Naming it turns the console’s policy into a line in the configuration a reviewer can read, instead of a behavior somebody has to remember six months from now with the change window open.

The other half of the same mistake is arithmetic. You apply the list to line vty 0 4 on a platform that provisions line vty 0 15. Lines 5 through 15 never get the named list, so they follow default, and on many images carry a different transport input too. The gap hides because your own session lands on a low-numbered line and works. Run show running-config | section ^line vty, count the ranges, and apply the list to all of them. Source filtering on those same lines is a separate control worth adding once this works — see restricting SSH access with an ACL.

Leaving room for a server later

Local authentication is the right place to start and, for a lab or a small estate, the right place to stay. It is also the seam where a TACACS+ or RADIUS server drops in later without redesigning anything: the list becomes group tacacs+ local enable and the lines that name it do not change at all.

Privilege levels for tiered CLI access

IOS has sixteen privilege levels, 0 through 15, and ships using three of them. Levels 2 through 14 start with no commands of their own, and a user at one of them inherits every command at or below their level — a level-5 account keeps all of user EXEC and gains only what you move down. That is what makes tiered access a design decision rather than a setting.

Two things must line up before a tiered account behaves. The account has to carry a level, and something has to read that level.

username noc privilege 5 secret <password> sets it. Under plain login local, IOS applies that level at login and the user lands at 5. Once AAA is enabled, exec authorization is what reads it, so aaa authorization exec default local is the line that makes privilege 5 mean anything. Without it the account authenticates correctly and lands at level 1 — and this is the exact point where people conclude the privilege keyword is broken. It is not broken; nothing was asked to read it. One boundary to know: exec authorization is not applied to the console unless you add aaa authorization console, so prove the tier over SSH.

Then decide what level 5 can actually do. privilege exec level 5 <command> moves a command down to level 5, where it becomes available to level 5 and every level above. Grant only what the role needs, one command at a time.

enable secret level 5 <password> is a different mechanism. The account's privilege level is where a user lands; a level-specific enable secret is where a user can climb to with enable 5. A deliberate tiered design decides both. An accidental one gives a junior operator level 1 with no way up and a senior one level 15 with no step in between.

  • Level 0 — a handful of commands, typically disable, enable, exit, help and logout
  • Level 1 — user EXEC, the > prompt, and the read-only commands that live there
  • Levels 2-14 — no commands of their own; a user here keeps levels 0-1 and gains whatever you move down with privilege <mode> level <n> <command>
  • Level 15 — full privileged EXEC, and where an admin account belongs
R1(config)# aaa authorization exec default local
R1(config)# username noc privilege 5 secret <strong-password>
R1(config)# enable secret level 5 <different-password>
R1(config)# privilege exec level 5 clear counters
R1(config)# privilege exec level 5 configure terminal
R1(config)# privilege configure level 5 interface
R1(config)# privilege interface level 5 shutdown

A level-5 role: the account carries the level, exec authorization reads it, and every granted command is named.

Two traps worth naming

show running-config is the tempting grant and the wrong one. Handing it to a mid-tier role hands over every password hash, every ACL, every management address and the shape of the whole network. If the role needs to read one thing, grant the narrower command that reads that thing.

The second trap is granting a door without the rooms. privilege exec level 5 configure terminal gets a level-5 user into configuration mode and grants them nothing inside it, because configuration commands still sit at level 15 until you raise each one. That is why the example above continues with privilege configure level 5 interface and privilege interface level 5 shutdown — the mode, then the command within it. Granting configure terminal alone produces a user who can enter config mode and accomplish nothing.

Verify what you built, not what you typed

Verification here has two halves, and only the second one counts. The configuration says what you intended. A login says what the device does.

Start with the config side because it is fast. show running-config | include ^aaa puts every method list on one screen, in the order AAA will read them. show running-config | section ^line shows which list each line names, and is where a console with no login authentication becomes visible as an absence rather than a surprise. show running-config | section ^line vty confirms you covered every range on the platform. The IOS command cheat sheet collects the rest of the show commands these hardening steps touch.

Then prove it from outside. Open a second SSH session and log in as the account you expect a real user to have — not the one you are already holding, which proves nothing. show privilege in that new session prints the level it is actually running at, which is the only honest answer to whether the tier took. show users on the device lists who is connected and on which line, so you can confirm the session arrived on a VTY governed by the list you think it is.

When a login is refused and the configuration looks correct, debug aaa authentication names which list ran and which method answered, which is usually the fastest way to discover that the line was following default all along. Run it from a session you already hold; if that session is SSH or Telnet, terminal monitor first or the debug runs and prints nothing to you. Read the two or three lines you need, then undebug all. Leaving AAA debugging on across a login storm is its own outage.

! From the admin session that made the change:
R1# show running-config | include ^aaa
aaa new-model
aaa authentication login VTY-AUTH local enable
aaa authentication login CONSOLE-AUTH local
aaa authorization exec default local

! From the second session, logged in as the level-5 account:
R1# show privilege
Current privilege level is 5

Illustrative, not a capture: the shape each command reports, run from the session that is allowed to run it.

A rollback habit that costs thirty seconds

Every lockout in this guide has the same shape. A change reveals itself only at the next login, and it is applied from a session that never has to log in again. One habit defeats all of them.

Open a second session before you commit anything. Console plus SSH is ideal because the two paths fail independently; two SSH sessions is the minimum. Make the change in one, test it in the other. If the test session is refused, you still hold a shell to undo it from, and the whole incident lasts a minute instead of a drive.

Then schedule the undo. reload in 5 in privileged EXEC arms a reload five minutes out. Make the change, do not save, and prove you can log in fresh. If you can, reload cancel. If you cannot, wait it out — the router reloads into the startup-config and your change is gone, precisely because you never wrote it. copy running-config startup-config is the last step, after the proof, not the first.

That ordering is also what makes AAA safe to learn rather than safe to read about. The failure modes in this guide are invisible until you trigger them. Graded CCNA labs run on real Cisco IOS under CML, where locking yourself out costs a lab restart and teaches you more than the paragraph that warned you. The rest of this cluster — SSH, line hardening, VTY ACLs — sits under device security.

  1. Create the username accounts and the enable secret first.
  2. Open the second session on a different path.
  3. reload in 5.
  4. aaa new-model, then the method lists, then apply them per line.
  5. Apply a list to every VTY range and to line con 0 explicitly.
  6. Add aaa authorization exec default local if any account carries a privilege level.
  7. Log in fresh on the second path; check show privilege in an SSH session.
  8. reload cancel, then copy running-config startup-config.

Frequently asked questions

Does aaa new-model lock me out as soon as I type it?

Not from the session you are already in. Line authentication is evaluated at login, so an open session survives the command; new logins are the risk. The command immediately applies local authentication to all lines and interfaces except the console, so if your VTY lines relied on a shared line password and no username exists in the local database, the next SSH attempt is refused. Create the accounts before you enable AAA, and keep a second session open while you test.

Why does my privilege 15 user still land at the user EXEC prompt under AAA?

The level on the account has to be read by something. Under login local, IOS applies it at login. Once AAA is enabled it is exec authorization that reads it, so the account drops to level 1 until you configure aaa authorization exec default local alongside the authentication list. Add that line, log in again on a fresh SSH session, and confirm with show privilege rather than trusting the prompt character.

If the first method in a list fails, does AAA try the next one?

Only when the first method returns an error, meaning it could not answer at all. A wrong password is a failure rather than an error, and it ends the attempt there. That is why a list of local followed by enable protects you when the local database cannot be used, but gives you no second chance at a password you mistyped. Test both paths on a lab so you know exactly which case your fallback covers.

Is service password-encryption enough to protect passwords in a config?

No. It rewrites reversible passwords as type 7, which is a published cipher that any decoder reverses in seconds. Treat it as protection against someone reading over your shoulder, not against anyone who obtains the configuration file. Use the secret keyword wherever a command offers it, since those values are hashed rather than encoded, and keep service password-encryption for the entries that accept nothing better. Turning it off later does not restore clear text either.

Do I need a named method list for the console if I already wrote one for the VTY lines?

Yes. A line that names no list follows the default list, and that cuts two ways. If you never defined a default, Cisco documents that console login succeeds with no authentication check at all — the console is open, not quietly protected. If you did define one and pointed it somewhere unreachable, the console follows it too and you have lost your last way in. A named console list avoids both, and it puts the console policy in the configuration instead of leaving a reviewer to infer it.

Practice AAA on a graded lab

Build these on real Cisco IOS in CML, then upload the config for a grade.

All security practice labs →

Practice on real Cisco IOS

Build it on real Cisco IOS and get instant pass/fail grading on your own config.