Skip to content

Securing SSH Access

💡
Before you start

You need a Linux machine where you can use sudo, and you should not practise on a server anyone depends on. The commands here change real system state. A spare machine, a virtual machine, or a cloud instance you can rebuild is the right place; a laptop you own is fine too. If the terminal itself is new to you, do Introduction to the Linux Terminal first — it takes about ten minutes and everything below assumes it. Every step tells you how to undo it.

The Front Door

SSH is how you administer a Linux server, which makes it the single most valuable target on the machine. Any host with a public IP receives automated password-guessing attempts continuously — not because anyone is interested in you specifically, but because scanning the entire internet is cheap.

The good news is that hardening SSH is mostly a matter of turning off things you do not use. Two changes — key-only authentication and no root login — remove essentially the whole automated threat.

⚠️
Keep a second session open the entire time.

Every change below is made over the connection it modifies. Open a second SSH session before you start and leave it connected. Test the new configuration in a third session. If you lock yourself out, that still-open session is the difference between a quick fix and a trip to a rescue console.

Step 1: Key-Based Authentication

Generate a key on your client machine, not the server:

ssh-keygen -t ed25519 -C "alice@laptop"

Ed25519 is the modern default: short, fast, and strong. Use RSA only if you must talk to something ancient, and then -t rsa -b 4096. Always set a passphrase — it is what protects the key if the laptop is stolen.

Copy the public key to the server:

ssh-copy-id alice@server

This appends to ~/.ssh/authorized_keys with the right permissions. Doing it by hand is fine too, but the permissions are strict and OpenSSH will silently refuse a key if they are wrong:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Confirm the key works before disabling passwords. Open a new session and log in. If it still asks for your password, the key is not being accepted — fix that first.

Step 2: The Configuration

The server configuration is /etc/ssh/sshd_config. On current Debian and Ubuntu it ends with an Include /etc/ssh/sshd_config.d/*.conf line, and putting your changes in a drop-in file there is cleaner — it survives package upgrades instead of producing merge prompts.

sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 30
AllowUsers alice bob
  • PermitRootLogin no — attackers know root exists. Log in as yourself and use sudo
  • PasswordAuthentication no — the single highest-value line. It ends password guessing entirely
  • KbdInteractiveAuthentication no — closes the other interactive path, which is easy to forget and leaves passwords usable
  • AllowUsers — an explicit allowlist. Anyone not named cannot authenticate at all
  • MaxAuthTries — attempts per connection
💡
First match wins in sshd_config.

Unlike most config files, OpenSSH uses the first occurrence of a keyword and ignores later ones. A setting already present earlier in the main file will beat your drop-in unless the Include comes first — check the top of sshd_config to see where the include sits.

Step 3: Test Before Reloading

sudo sshd -t          # syntax check — silent means valid
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowusers'

sshd -T prints the effective configuration after all includes and matches are resolved. This is the authoritative answer to "is my setting actually active?", and it is worth trusting over reading the files.

sudo systemctl reload ssh

Use reload, not restart — existing sessions survive. Now open a new session and confirm you can still get in.

The Socket-Activation Gotcha

⚠️
On recent Ubuntu, changing Port in sshd_config does nothing.

Since Ubuntu 22.10, ssh is socket-activated: systemd owns the listening port, not sshd. The Port directive is ignored. Change it in the socket instead:

sudo systemctl edit ssh.socket

and set ListenStream= (empty, to clear the default) followed by ListenStream=2222, then sudo systemctl daemon-reload && sudo systemctl restart ssh.socket. Check which applies to you with systemctl is-enabled ssh.socket.

Changing the port is worth keeping in perspective: it dramatically reduces log noise from automated scanners, but it is not a security control. A targeted attacker finds the new port in seconds. Do it for quieter logs, not for protection.

Beyond the Basics

  • Restrict what a key can do — prefix an entry in authorized_keys with options such as restrict,command="/usr/bin/backup.sh",from="203.0.113.5". Ideal for automation keys that should do exactly one thing
  • Use a hardware security keyssh-keygen -t ed25519-sk creates a key that requires a physical touch and cannot be copied off the device
  • Prefer an SSH agent with confirmationssh-add -c prompts on every use, so a compromised client cannot use your key silently
  • Avoid agent forwarding to untrusted hosts; root there can use your agent. ProxyJump is the safer way to reach a machine through a bastion
  • Check the host key on first connect — that fingerprint prompt is your only defence against a man-in-the-middle, and clicking through it defeats the point

Verifying It Worked

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no alice@server

This should be refused. If it prompts for a password, password authentication is still enabled somewhere — go back to sshd -T and find where.

sudo journalctl -u ssh --since "1 hour ago" | tail -30
sudo lastb | head          # failed login attempts

Now Do It Yourself: Five Steps

You will create a key pair, understand why its permissions matter, install it on a server, and turn off password logins — without locking yourself out, which is the way this job usually goes wrong. Steps 1 to 3 need nothing but the machine in front of you; steps 4 and 5 need a server you can afford to break.

1
Create a modern key pair

Go: open a terminal on your own computer — the one you will connect from, never the server.

Do: run this, replacing the comment with something that identifies the machine. When it asks for a passphrase, type a real one.

ssh-keygen -t ed25519 -C "alice@laptop"

You should see: prompts for a file location and a passphrase, then a fingerprint and a piece of ASCII art. Press Enter to accept the default location. Choose ed25519 over RSA: the keys are far shorter, faster, and every current OpenSSH supports them.

If not: ssh-keygen: command not found means the OpenSSH client is not installed — on Debian and Ubuntu that is sudo apt install openssh-client. 🔴 The passphrase is not optional in practice. Without one, anyone who copies that single file becomes you on every server that trusts it. With one, they also need the passphrase, and ssh-agent means you still only type it once per session.

2
Look at the two files, and check the fingerprint

Go: same terminal.

Do: run these two commands.

ls -l ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
ssh-keygen -lf ~/.ssh/id_ed25519.pub

You should see: two files — the private key at mode 600, and the public one readable more widely. Then a line like 256 SHA256:mf964P6/G6gQ5bggUiCQKgOHhzOJdG5SCd6tDpk88lw alice@laptop (ED25519). The rule is simple and absolute: the file without .pub never leaves your machine; the one with .pub is meant to be handed out freely.

If not: if the private key is anything other than 600, fix it now with chmod 600 ~/.ssh/id_ed25519. OpenSSH refuses to use a private key that others can read, with UNPROTECTED PRIVATE KEY FILE, and that refusal is a feature. The directory matters too: chmod 700 ~/.ssh.

3
Put the public key on the server

Go: still on your own machine. You need the server's address and a password login that currently works.

Do: run ssh-copy-id alice@your-server, then log in again with ssh alice@your-server.

You should see: the tool reporting the number of keys added, then a normal login — asking for your key passphrase rather than your account password, or nothing at all if the agent already holds it. On the server the key now sits in ~/.ssh/authorized_keys, one key per line.

If not: if it still asks for your account password, the server did not accept the key. The cause is almost always permissions on the server side: ~/.ssh must be 700 and authorized_keys 600, and the home directory itself must not be group-writable — sshd silently ignores keys in a directory anyone else could alter. Diagnose with ssh -v alice@your-server and read the lines mentioning publickey.

4
Turn off password logins — keeping a second session open

Go: log in to the server. 🔴 Open a SECOND terminal and log in again, and leave it open. That spare session is what saves you if the next step goes wrong; it stays authenticated even after sshd restarts.

Do: in the first session, edit the server's sshd configuration with sudo nano /etc/ssh/sshd_config and set these three lines, then validate and reload.

PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes

You should see: sudo sshd -t printing nothing at all — silence means the file is valid. Only then run sudo systemctl reload sshd. Now, from a third terminal, prove a fresh login still works before you close anything.

If not: if the new login fails, use the second session you kept open, undo the change, and reload again. This step could not be executed while writing this page — there is no SSH server on the machine it was written on — so treat these as the standard directives rather than captured output, and rely on sshd -t and your spare session, which are the checks that actually protect you. ⚠️ Never test by closing every session and hoping.

5
Restrict what a single key is allowed to do

Go: on the server, open ~/.ssh/authorized_keys.

Do: put options in front of a key, on the same line, separated by commas.

from="203.0.113.5",no-agent-forwarding,no-port-forwarding ssh-ed25519 AAAA...

You should see: that key now works only from that address, and cannot be used to forward ports or your agent. Each key in the file can carry different restrictions, which is what makes per-key limits practical: a deployment key can be locked to one source address and refused everything else.

If not: if the key stops working entirely, the options are almost certainly on their own line — they must be on the same line as the key, before it, with no line break. Also add command="..." if a key should only ever run one specific thing, which is the right shape for automated backups and deploys.

🎉
Check yourself before moving on

Without scrolling up: before reloading sshd after a configuration change, what two things must you have done? Answer: run sudo sshd -t and seen it print nothing, and kept a second authenticated session open. The first catches a broken file; the second saves you when the file is valid but the policy locks you out.

Now do it without the page: generate a second key labelled for a backup job, and write the authorized_keys line that would let it run one named command and nothing else, from one address. You need command= and from= together — step 5's shape, tightened.

Summary

  • Keep a second session open — every change is made over the thing you are changing
  • Keys first, then disable passwords — confirm the key works before you close the door
  • Disable root login and keyboard-interactive, and use AllowUsers
  • sshd -t then sshd -T — validate, then confirm the effective config
  • First match wins in sshd_config
  • Port changes need the socket on Ubuntu 22.10+, and buy quiet logs rather than security
🎉
Two lines do most of the work.

PasswordAuthentication no and PermitRootLogin no. Together they make the continuous automated guessing against your server irrelevant — everything else on this page is refinement.