SSH keys and SSH server hardening

Key-only login, no password and no root, on Ubuntu Server and Desktop.

You create a passphrase-protected key, upload it to the server and write the SSH settings into a separate file: passwords and root off, optionally another port. The commands are filled in from your details and for your system (Windows, Mac, Linux).

Vladimír NeporAuthorVladimír Nepor
PublishedUpdated
Your computer holds the private key protected by a passphrase. It logs in over SSH with the key only: the server knows its public part in authorized_keys and the 00-hardening.conf file disables password login and the root user.Your computer~/.ssh/keyprivate keywith a passphraseSSHkey onlyserver~/.ssh/authorized_keys ✓sshd_config.d/00-hardening.confpassword ✕root ✕02 · YOUR COMPUTEREd25519 keywith a passphrase, one per computerchapter →03 · SERVERKey to the serverauthorized_keys and permissionschapter →05 · HARDENINGKey onlypasswords and root offchapter →06 · OPTIONALChanging the portin the right order with the firewallchapter →
Contents

01 · Start

Before you start

The result of this guide is a server you log in to over SSH with a key only: password login and direct root login are disabled. The server settings live in a separate file; the main configuration stays untouched.

Fill in your details and the commands throughout the guide are filled in. The Copy button copies them with your values. Empty values stay as placeholders (<ADMIN_USER>, <SERVER_IP>…), the same as in the other guides.

Your details: the commands in this guide are filled in from them. They are stored only in your browser and other Ubuntu guides use them too.

Your computer

Your admin account (with sudo) created during the Ubuntu installation. Not root and not common names like admin, ubuntu or user. Bots try those first.

Invalid value, the commands keep the placeholder.

IP address in your local network (see your router or run ip -4 addr on the server), a Tailscale address or a domain name.

Invalid value, the commands keep the placeholder.

File name of the key in ~/.ssh. One key per server (or group of servers). The name makes it easy to find and revoke.

Invalid value, the commands keep the placeholder.

Comment stored in the key (who and from which computer). It tells you in authorized_keys which key to delete when you retire the computer.

Invalid value, the commands keep the placeholder.

Name for ~/.ssh/config on your computer. Instead of a long command you then just type ssh shortcut.

Invalid value, the commands keep the placeholder.

Optional change of the default port 22. It reduces log noise; real protection comes from key login and the firewall. Range 1024 to 49151.

Invalid value, the commands keep the placeholder.

Prerequisite

The SSH server is running on the server. which sshd checks it (prints a path); empty output = sudo apt install openssh-server. Details in the Installing the SSH server step of the Securing access chapter.

The procedure in short

  1. Key on your computer: you create a key pair protected by a passphrase.
  2. Key to the server: you upload the public part.
  3. Key login: only the key passphrase instead of the server password.
  4. Hardening the SSH server: passwords and root off, optionally another port.
  5. Verification: the key gets in, the password does not.

02 · Your computer

Key on your computer

The key is created on the computer you will connect from. It has two files: the private part stays with you, the public part goes to the server.

Step 1: Generate an ed25519 key

In a terminal (PowerShell on Windows) run:

Terminal · Mac / Linux
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/<KEY_NAME> -C "<PC_NAME>"
PowerShell · Windows
ssh-keygen -t ed25519 -a 100 -f $env:USERPROFILE\.ssh\<KEY_NAME> -C "<PC_NAME>"

Enter a passphrase. It protects the key if the file ends up in the wrong hands; a forgotten passphrase cannot be recovered, you then create a new key.

OptionWhat it does
-t ed25519the Ed25519 key type: short, fast and secure
-a 100key derivation rounds for the passphrase; slows down guessing it if someone steals the file
-fyour own file name, one key per server (or group of servers) is easy to find and revoke
-Ckey comment (who and from which computer); stored on the server in authorized_keys too
No need to type the passphrase all the time

ssh-agent remembers the key after the first passphrase until you log out. It runs by itself on Mac and Linux; on Windows enable the OpenSSH Authentication Agent service.

Step 2: Show the public key

Terminal · Mac / Linux
cat ~/.ssh/<KEY_NAME>.pub
PowerShell · Windows
Get-Content $env:USERPROFILE\.ssh\<KEY_NAME>.pub

A single line starting with ssh-ed25519 AAAAC… and ending with the key comment. Never share the private key (the file without .pub).

Protecting the key on your computer

This is about the computer you connect from. The key replaced the password: whoever has the private key file and knows its passphrase gets into the server.

  • Key passphrase: without it a stolen key is usable right away.
  • Encrypted disk: BitLocker (Windows), FileVault (Mac), LUKS (Linux). A stolen laptop then does not reveal the key.
  • Updated system and screen lock. Do not use the key on other people's or shared computers.
  • One key per computer. Do not copy the private key between devices. Create a new one with its own comment on another computer.
  • Phone with 2FA codes: protect it with a lock just like the computer.

Lost or retired computer

On the server, open ~/.ssh/authorized_keys and delete the line that ends with the lost computer's comment. Save with ctrl+x.

Terminal
nano ~/.ssh/authorized_keys

The change applies to new logins immediately; the stolen key no longer gets in.

03 · Server

Key to the server

The public key belongs on the server in your user's ~/.ssh/authorized_keys. The Ubuntu Server installer can import it from GitHub (Import SSH key). If you did that, skip to the key login.

Step 3: Upload the key over a password login

The computer and the server must be in the same network. The command asks for the user's password on the server:

Terminal · Mac / Linux
ssh-copy-id -i ~/.ssh/<KEY_NAME>.pub -o IdentitiesOnly=yes -o PreferredAuthentications=password -p 22 <ADMIN_USER>@<SERVER_IP>
PowerShell · Windows
Get-Content $env:USERPROFILE\.ssh\<KEY_NAME>.pub | ssh -o IdentitiesOnly=yes -o PreferredAuthentications=password -p 22 <ADMIN_USER>@<SERVER_IP> "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"

-o IdentitiesOnly=yes -o PreferredAuthentications=password forces a password login. Without them SSH first tries every key on your computer and the server may close the connection with Too many authentication failures. Windows has no ssh-copy-id; the PowerShell command does the same.

04 · Login

Key login

Step 4: Log in with the key

Terminal · Mac / Linux
ssh -i ~/.ssh/<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>
PowerShell · Windows
ssh -i $env:USERPROFILE\.ssh\<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>

SSH asks for the key passphrase, the one you entered when creating it. If it asks for the user's password on the server, the key was not accepted, see permissions below.

Asks for the user's password? Fix the permissions

The server rejected the key, most often because ~/.ssh or authorized_keys is readable by other users.ssh-copy-id sets the permissions only for a newly created folder. Log in with the password (it still works) and run on the server:

Terminal
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
ls -ld ~/.ssh ~/.ssh/authorized_keys
Output
drwx------ 2 <ADMIN_USER> <ADMIN_USER> 4096 … /home/<ADMIN_USER>/.ssh
-rw------- 1 <ADMIN_USER> <ADMIN_USER>   98 … /home/<ADMIN_USER>/.ssh/authorized_keys

Then log in with the key again. If it still fails, the detailed output helps.

Optional: a server shortcut instead of the full command

To avoid typing the address, port and key every time, add the server to the SSH configuration on your computer. When you change the port, update the Port line too.

~/.ssh/config · Mac / Linux
Host <SERVER_ALIAS>
    HostName <SERVER_IP>
    Port 22
    User <ADMIN_USER>
    IdentityFile ~/.ssh/<KEY_NAME>
    IdentitiesOnly yes
%USERPROFILE%\.ssh\config · Windows
Host <SERVER_ALIAS>
    HostName <SERVER_IP>
    Port 22
    User <ADMIN_USER>
    IdentityFile ~/.ssh/<KEY_NAME>
    IdentitiesOnly yes

Connecting is then just:

Terminal
ssh <SERVER_ALIAS>

05 · Server

Hardening the SSH server

Check the key login first

Continue only once the key login works. Keep two SSH windows open: the changes do not drop existing connections, so if a new login fails, you can revert everything from the other window.

Step 5: Write the settings into a separate file

Into /etc/ssh/sshd_config.d/; the main /etc/ssh/sshd_config stays untouched:

Terminal
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
# Port <SSH_PORT>    (optional: uncomment and set your own port)
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
AuthenticationMethods publickey
EOF
LineWhat it does
PasswordAuthentication nono password login over SSH (locally at the server the password still works as your way back if you lose the key)
KbdInteractiveAuthentication nodisables interactive password entry too
PermitRootLogin noroot cannot log in over SSH at all; administration only through your own user and sudo
AuthenticationMethods publickeyexplicitly key only; when you add 2FA, change this line as described in the 2FA guide
Portstays commented out (port 22) until you enter one in your details, see chapter 06

Why a separate file and why 00-

  • The first value found wins. sshd_config includes the files from sshd_config.d/ right at the top, in alphabetical order. The 00- prefix makes your settings win even over 50-cloud-init.conf, which enables passwords on some installations.
  • The main file stays original, so openssh-server package updates do not ask about a configuration conflict and do not overwrite your settings.
  • All settings in one place: easy to review, copy to another server or remove.

Step 6: Apply the settings

Syntax check first (on an error nothing restarts), then reload. On Ubuntu 24.04 and newer SSH is started by ssh.socket, which reads the port from the configuration only after daemon-reload:

Ubuntu 24.04, 26.04
sudo sshd -t && sudo systemctl daemon-reload && sudo systemctl restart ssh.socket ssh
Ubuntu 22.04
sudo sshd -t && sudo systemctl restart ssh

06 · Optional

Changing the port

Optional. Automated scanners try port 22 first; another port (1024 to 49151) reduces log noise. Real protection comes from key login and the firewall. With SSH only over Tailscale a different port adds almost nothing.

Step 1: Enter the port in your details and check it is free

Enter the port in your details above; the commands below use it. First check that it is free:

Terminal
sudo ss -tulpn | grep ':<SSH_PORT>'    # empty output = the port is free

Step 2: Allow the port in the firewall first

Only if UFW is active. Otherwise you will not get in after the restart:

Terminal
sudo ufw status                                  # active? then allow the new port first
sudo ufw allow <SSH_PORT>/tcp comment 'SSH'

Step 3: Write the port into 00-hardening.conf

The command removes any old Port line and inserts the new one on the first line. The rest of the file stays unchanged:

Terminal
sudo sed -i -E '/^#? *Port /d' /etc/ssh/sshd_config.d/00-hardening.conf
sudo sed -i '1i Port <SSH_PORT>' /etc/ssh/sshd_config.d/00-hardening.conf
head -n 1 /etc/ssh/sshd_config.d/00-hardening.conf

Step 4: Apply the settings and log in on the new port

In a new window; keep the old one open. If you use the shortcut, update its Port line.

Ubuntu 24.04, 26.04
sudo sshd -t && sudo systemctl daemon-reload && sudo systemctl restart ssh.socket ssh
Ubuntu 22.04
sudo sshd -t && sudo systemctl restart ssh
Terminal · Mac / Linux
ssh -i ~/.ssh/<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>
PowerShell · Windows
ssh -i $env:USERPROFILE\.ssh\<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>

Step 5: Remove the old rule for port 22

Terminal
sudo ufw delete allow 22/tcp
Set the port in one place only

Port (and ListenAddress) do not follow "the first value wins", every Port line adds another port SSH listens on. The default sshd_config has #Port 22 commented out.

07 · Check

Verification

Step 1: Effective server settings

The output should match the second block (the order of lines may differ):

Terminal
sudo sshd -T | grep -E '^(port|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin|authenticationmethods) '
Output
port 22
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
permitrootlogin no
authenticationmethods publickey

Step 2: What SSH actually listens on

Only your port:

Terminal
sudo ss -tlnp | grep -E 'ssh|systemd'
cat /run/systemd/generator/ssh.socket.d/addresses.conf    # Ubuntu 24.04+: port taken from the configuration

Step 3: In a new window: the key gets in, the password does not

The key login works (first command). An attempt to log in with a password must be rejected:

Terminal · Mac / Linux
ssh -i ~/.ssh/<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>
PowerShell · Windows
ssh -i $env:USERPROFILE\.ssh\<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>
Terminal
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password -p 22 <ADMIN_USER>@<SERVER_IP>
Output
<ADMIN_USER>@<SERVER_IP>: Permission denied (publickey).

08 · Other users

A user without a key: password instead of a key

For users who will not use a key, for example a less technical client whose server runs at home. Instead of the key they enter a password, but always together with a code from their phone (2FA). They get no sudo.

Step 1: Create the user in the ssh-password group

Terminal
sudo groupadd -f ssh-password
sudo adduser <USER>
sudo usermod -aG ssh-password <USER>

Step 2: Allow the group to log in without a key

SSH asks group members for no key, only for answers through PAM. Everyone else keeps logging in with a key:

Terminal
sudo tee /etc/ssh/sshd_config.d/10-ssh-password.conf <<'EOF'
Match Group ssh-password
    AuthenticationMethods keyboard-interactive
EOF
sudo sshd -t && sudo systemctl restart ssh

Step 3: Check that the password alone does not get in

Until you turn on 2FA, interactive login is off and the user cannot log in at all:

Terminal
sudo sshd -T -C user=<USER>,host=localhost,addr=127.0.0.1 | grep -E '^(kbdinteractiveauthentication|authenticationmethods) '
Output
kbdinteractiveauthentication no
authenticationmethods keyboard-interactive

They can log in only after 2FA is on (the next step), with the password and the code. You create the secret key for their phone there, in the Other users step.

09 · Problems

Troubleshooting

Watch the SSH server log (in the second window or on the server console) and log in from your computer with a detailed output:

Terminal
sudo journalctl -u ssh -f    # on the server: watch the log while you try to log in
Terminal · Mac / Linux
ssh -v -i ~/.ssh/<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>    # detailed course of the login
PowerShell · Windows
ssh -v -i $env:USERPROFILE\.ssh\<KEY_NAME> -o IdentitiesOnly=yes -p 22 <ADMIN_USER>@<SERVER_IP>    # detailed course of the login
Message / stateCause and fix
Authentication refused: bad ownership or modespermissions too loose, see permissions
Too many authentication failuresthe client tries every key; use -o IdentitiesOnly=yes (it is in the guide's commands and in the shortcut)
Asks for the user's password, not the key'sthe key is missing on the server or has wrong permissions, see step 3 and permissions
Connection refused / timeout after changing the portthe port is not allowed in UFW, or on 24.04 the daemon-reload and ssh.socket restart are missing
Password login still workscheck sudo sshd -T; the file with the alphabetically lower name in sshd_config.d/ takes precedence

Sources

Official documentation and further reading

Need a secure server setup?

We design, secure and document networks and servers.

Servers & Networks