The guide SSH — From Passwords to Keys secured your side of the connection: keys instead of passwords, an agent, nicknames for the machines you reach. This guide is about the other end — the machine that accepts connections. A rented server or a Raspberry Pi exposed to the internet is asked to prove its identity thousands of times a day by automated scripts guessing passwords. Hardening is how you stop answering.
Everything here was verified on disposable Ubuntu 26.04, Fedora 44 and Arch containers running OpenSSH 10.2 through 10.5, from the inside. Nothing in this guide needs anything beyond the machine you are hardening and the client you are reading this on.
The one rule before any change
SSH has no password reset email. If you break the SSH server remotely, the way back in is whatever console your provider gives you — assume it is slow and annoying. On a Raspberry Pi, the fallback is a keyboard and a screen instead. So every change in this guide follows the same ritual:
- Change the configuration.
- Validate it with
sshd -tbefore restarting anything. - Restart the SSH service.
- Prove the new setup works by logging in from a new terminal, while the old session stays open.
- Only then close the old session.
The reason for step 4 is that an already-established session survives a restart. This was tested directly: a session was left running a twelve-second command, the service was restarted underneath it, and the command finished with its output intact. Your current connection is your safety rope. Keep holding it until the new way in is proven.
And if the step-4 login fails, the move is backward, not forward:
undo the edit, sshd -t, restart, and confirm the old behavior is back
— all from the session that still works. Find the cause, then try
again. The user-restriction section below is the classic place this
saves you: one misspelled username there refuses even key-holders.
Where the configuration lives
Server settings live in /etc/ssh/sshd_config, a plain text file. Edit
it with sudo nano /etc/ssh/sshd_config. The file is mostly comments;
the lines that matter start with a keyword and a value.
After any edit, validate before restarting:
$ sudo sshd -t
Silence and exit code zero mean the file parses. A typo produces an error naming the line:
/etc/ssh/sshd_config: line 146: Bad configuration option: Portr
/etc/ssh/sshd_config: terminating, 1 bad configuration options
That output came from adding Portr 22 — one extra letter. Note what the
validation just prevented: a server that refuses to start. To see the
configuration the server would actually run, not just check the syntax:
$ sudo sshd -T | grep -i passwordauthentication
passwordauthentication yes
If that test ever disagrees with your edit (it says yes after you set
no), another file is winning. The server uses the first value it
reads, and the main file can pull in extra files from
/etc/ssh/sshd_config.d/ near the top. Look for an Include line in
/etc/ssh/sshd_config and check that directory.
One naming note: the service is called ssh on
Ubuntu and sshd on Fedora and Arch, so the restart command differs.
On Arch the server is not preinstalled — sudo pacman -S openssh
installs it; Fedora ships it in the base system, and Ubuntu handles it
at install time rather than through its metapackages:
$ sudo systemctl restart ssh # Ubuntu
$ sudo systemctl restart sshd # Fedora, Arch
The configuration directives behave identically on both. What differs is the service name, and one Ubuntu-specific quirk you will meet in the port section.
Keys only — the main event
If you followed the client guide, your key is already on the server
(ssh-copy-id put it there), and key login works. Verify that with your
own eyes before going further: open a new terminal, log in with the key,
no password. If that login fails, stop here and fix it first.
Open /etc/ssh/sshd_config and set:
PasswordAuthentication no
Then validate, restart, and test — in that order. With password authentication off, a password attempt is refused before any password is asked for, and the client shows it:
sshguide@localhost: Permission denied (publickey).
The word publickey in
parentheses is the complete list of login methods the server still
offers — before the change, the same attempt showed
(publickey,password). And the server does not explain why a login
was refused, to you or to anyone guessing. That silence is a feature.
While you are in the file, look at root. The default setting,
PermitRootLogin prohibit-password, already blocks root password
logins. Making it explicit closes the question permanently:
PermitRootLogin no
With no, root cannot log in over SSH at all — you log in as yourself
and use sudo for admin work, exactly as Chapter 5 taught. On a
localhost test rig, root password attempts were rejected under both
settings, with the same message either way.
Restricting who can log in
AllowUsers turns the login policy into an explicit list. Everyone not
on it is refused, whatever credentials they offer:
AllowUsers yourname
Restart, and any account not on the list is refused whatever it offers,
while the allowed account keeps working. If several
people maintain the machine, list them all separated by spaces, or use
AllowGroups with a group (Chapter 5 showed how groups work).
The effect is small but real: accounts you never created cannot be
logged into, and a script guessing admin or root passwords is
wasting its time on a server that stopped listening to those names.
The port change — what it is and what it is not
Every exposed SSH server on the internet is probed on port 22 by automated scripts around the clock. Moving the SSH port makes that background noise stop. It does not make you safer, and the difference matters: a port scan sweeps all 65,535 ports in short order, so moving the port hides you from lazy scripts, not from anyone who wants in. Moving the port is log hygiene. Keys are the security. Never let a moved port be the reason key-only auth “can wait”.
With that said, if the noise bothers you, the change is one line:
Port 2222
After a restart, the server answers on 2222 and refuses connections on
22 (-p tells the client which port to knock on):
$ ssh -p 2222 [email protected]
ssh: connect to host localhost port 22: Connection refused
Two findings from live testing that most port-change instructions skip.
First, listing two Port lines makes the server listen on both ports —
adding Port 2222 while Port 22 remains does not close anything.
Delete the old port line if you want it closed.
Second, and more important: on current Ubuntu releases, ssh runs
behind systemd socket activation, and a service called ssh.socket
holds port 22 open. Editing sshd_config and restarting does nothing
until you switch to the plain service:
$ sudo systemctl disable --now ssh.socket
$ sudo systemctl enable --now ssh.service
After that, Port lines behave as documented. Fedora and Arch ship no
socket layer, and the port change worked as described in our testing
on both.
If you move the port, the client has to follow: use ssh -p 2222, or
put a Port 2222 line under the server’s entry in ~/.ssh/config
(the client guide’s nicknames).
Reading your logs
Every connection attempt, successful or not, lands in the journal:
$ sudo journalctl -u ssh # Ubuntu
$ sudo journalctl -u sshd # Fedora
Failed key attempts look like this:
Connection closed by authenticating user sshguide ::1 port 52148 [preauth]
Successful restarts announce themselves too:
sshd[2246]: Server listening on 0.0.0.0 port 2222.
The listening line doubles as proof that a port change took effect. Scrolling this journal after a week online explains, better than any article, why keys beat passwords: the volume of guesses against a real server is constant and enormous.
What this guide does not cover
A firewall limits which ports are reachable before SSH is even asked —
Chapter 10 touched on the landscape, and the setup differs by distro
(ufw on Ubuntu, firewalld on Fedora). Automated blocking of
misbehaving addresses is its own topic (fail2ban, CrowdSec), and it can
wait: with key-only logins enabled, password guessing already fails at
the door.
The hardened configuration, complete
For a single-admin server, the hardening file is short:
# /etc/ssh/sshd_config — the lines that matter
Port 22
PasswordAuthentication no
PermitRootLogin no
AllowUsers yourname
Validate, restart, and test from a new terminal:
$ sudo sshd -t
$ sudo systemctl restart ssh
That is the whole hardening file for most personal servers. The port line stays at 22 unless log noise bothers you; keys do the protecting.
Where you are now
Your server now answers only to named users carrying keys, refuses root logins outright, and writes down everyone who knocks. Combined with the client setup from the SSH guide, both ends of the connection are yours. The Automation chapter shows how cron turns maintenance into scheduled work, and the full sshd_config manual documents every directive this guide left out.