SSH is how you manage Cisco gear remotely without sending credentials in cleartext. Enabling it takes five pieces of configuration: a hostname, a domain name, an RSA key pair, a local user, and vty lines locked to SSH. This guide walks through all five on IOS XE, with every output line captured from a live device (IOS XE 17.18 in our CML lab), then covers verification, restricting access with an ACL, and the errors you will actually hit.
One thing most guides still get wrong: modern IOS XE requires a 3072-bit RSA key minimum to enable the SSH server. The old "2048 bits is fine" advice no longer applies. More on that below.
Prerequisites
- Console access to a Cisco router or switch running IOS or IOS XE
- Privileged EXEC (enable) access
- An IP address on the device that your management station can reach
Check the Starting State
Before configuring anything, show ip ssh tells you exactly what is missing. On a fresh device:
R1# show ip ssh
SSH Disabled - version 2.0
%Please create an RSA key of atleast 3072 bits or an EC key of
atleast 384 bits to enable SSH server functionality
Authentication methods:publickey,keyboard-interactive,password
! (algorithm list trimmed)
Authentication timeout: 120 secs; Authentication retries: 3
IOS Keys in SECSH format(ssh-rsa, base64 encoded): NONEThat message (typo and all - it really prints "atleast") is IOS XE telling you the modern key-size floor: 3072-bit RSA or 384-bit EC. A 2048-bit key that worked on older code will not enable the SSH server on current IOS XE.
Step-by-Step Configuration
1. Set the Hostname and Domain Name
The RSA key pair is named after hostname.domain, so both must exist before you generate keys:
Router> enable
Router# configure terminal
Router(config)# hostname R1
R1(config)# ip domain name pinglabz.lab2. Generate the RSA Key Pair
Generate a 3072-bit pair (the minimum on current IOS XE; 4096 also works if you can spare the CPU during generation):
R1(config)# crypto key generate rsa modulus 3072
%This command is deprecated. Use the command in exec mode instead
The name for the keys will be: R1.pinglabz.lab
% The key modulus size is 3072 bits
% Generating 3072 bit RSA keys, keys will be non-exportable...
[OK] (elapsed time was 2 seconds)Two things worth noticing in that real output. First, the keys generate fine either way, but IOS XE now flags the config-mode form as deprecated - on 17.x you can run crypto key generate rsa modulus 3072 straight from privileged EXEC instead. Second, there is no "How many bits in the modulus [512]" prompt when you specify modulus on the command line, which is why scripts and automation always use this form.
3. Create a Local User Account
R1(config)# username admin privilege 15 secret Pinglabz123Use secret, not password - secret stores a salted hash, while password stores the string in a reversible format.
4. Configure the VTY Lines
R1(config)# line vty 0 4
R1(config-line)# transport input ssh
R1(config-line)# login local
R1(config-line)# exittransport input ssh kills Telnet on those lines at the same time (if anything still connects to this device over Telnet, it stops working the moment you apply this - which is usually the point).
5. Force SSH Version 2
R1(config)# ip ssh version 2SSHv1 has known protocol weaknesses. Current IOS XE negotiates version 2 anyway, but pinning it makes the config explicit and keeps auditors happy.
Verify the Configuration
Run show ip ssh again:
R1# show ip ssh
SSH Enabled - version 2.0
Authentication methods:publickey,keyboard-interactive,password
! (algorithm list trimmed)
Authentication timeout: 120 secs; Authentication retries: 3
Minimum expected Diffie Hellman key size : 2048 bits
IOS Keys in SECSH format(ssh-rsa, base64 encoded): R1.pinglabz.lab
Modulus Size : 3072 bitsSSH Enabled - version 2.0 plus a named key pair is what you want to see. Then save:
R1# write memory
Building configuration...
[OK]Connect from a Client
Linux, macOS, and modern Windows (PowerShell on Windows 10/11) all ship OpenSSH, so the client side is one command everywhere:
$ ssh admin@10.78.10.1Enter the password from step 3 and you land at the R1# prompt. OpenSSH can also run a single command remotely, which is handy for scripted checks. Here is a real session from a Linux host in our lab, asking the router who is connected:
$ ssh admin@10.78.10.1 show users
Line User Host(s) Idle Location
0 con 0 idle 00:00:10
* 2 vty 0 admin idle 00:00:00 10.78.10.50The asterisk marks our own SSH session on vty 0, coming from 10.78.10.50. On the router side, show ssh shows the same session with its negotiated ciphers:
R1# show ssh
Connection Version Mode Encryption Hmac State Username
0 2.0 IN chacha20-pol hmac-sha2-256-etm@openssh.com Session started admin
0 2.0 OUT chacha20-pol hmac-sha2-256-etm@openssh.com Session started adminIf PuTTY is your tool of choice on Windows, it works the same way: host = the device IP, port 22, connection type SSH.
Restrict Who Can SSH In
An open SSH port on every interface is an invitation. Lock the vty lines to your management subnets with a standard ACL:
R1(config)# access-list 10 remark SSH-MGMT-ONLY
R1(config)# access-list 10 permit 10.78.0.0 0.0.0.255
R1(config)# line vty 0 4
R1(config-line)# access-class 10 inAnything outside the permitted range is rejected before authentication even starts. From a host in a subnet the ACL does not permit:
$ ssh admin@10.78.10.1
ssh: connect to host 10.78.10.1 port 22: Connection refusedThat instant "Connection refused" (rather than a password prompt) is the signature of an access-class block. Add the missing subnet to the ACL and the same host connects normally.
Hardening Checklist
Once SSH works, tighten it:
R1(config)# ip ssh time-out 60
R1(config)# ip ssh authentication-retries 2
R1(config)# login block-for 120 attempts 3 within 60ip ssh time-outcaps how long an unauthenticated connection can sit in negotiation (default 120 seconds).ip ssh authentication-retriescuts the password guesses per connection from 3 to 2.login block-forlocks the login process for 120 seconds after 3 failed attempts within a minute - cheap brute-force protection.
The finished vty section should look like this (show run | section line vty):
line vty 0 4
access-class 10 in
login local
transport input sshTroubleshooting
SSH refuses to enable
show ip ssh says "SSH Disabled" and asks for a key: you have no RSA key pair, or it is smaller than 3072 bits on current IOS XE. Generate a new one (step 2). If crypto key generate rsa complains about the key name, set the domain name first - no ip domain name, no keys.
Connection refused
Either an access-class ACL on the vty lines is rejecting your source address (see above), or SSH never enabled - check show ip ssh first.
Password rejected or immediately disconnected
Confirm login local is on the vty lines and the username exists with secret. If the vty lines still have a line password and plain login, the router expects the line password rather than your user account.
Old client or server cannot negotiate
Very old IOS speaks only legacy key-exchange and ssh-rsa signatures that modern OpenSSH disables by default. You will see algorithm-mismatch errors on connect. Client-side workarounds exist (-oKexAlgorithms=+diffie-hellman-group14-sha1 and friends), but the right fix is upgrading the IOS side.
Key Takeaways
- Five steps enable SSH: hostname,
ip domain name, RSA keys, a local user withsecret, andtransport input ssh+login localon the vty lines. - Current IOS XE requires a 3072-bit RSA key minimum -
show ip sshtells you this on a fresh box, and older 2048-bit guidance is out of date. crypto key generate rsa modulus 3072runs without interactive prompts, and on 17.x lives in exec mode (the config-mode form still works but is deprecated).- Verify with
show ip ssh(server state) andshow ssh(live sessions). - Restrict sources with
access-classon the vty lines - a blocked client sees "Connection refused" before any password prompt.
More Cisco admin how-tos: Essential Cisco Commands Cheat Sheet and How to Configure HTTP Method on a Cisco Switch.