Skip to content
Linux Administration
Lab 21 of 27·40mIntermediate

SSH keys and hardening a server's front door

Set up key-only access, then turn off the things that let an attacker guess their way in — and verify each change before you rely on it.

You need

  • A Linux VM you can reach over SSH, with sudo access
  • A second terminal, kept open for the whole lab

Do first

The rule that matters more than any setting: keep an existing session open while you change sshd_config. If you lock yourself out with one connection open, you fix it. With zero open, you rebuild the box.

1. Generate a modern key pair

ssh-keygen -t ed25519 -C "lab key" -f ~/.ssh/lab_ed25519
ls -l ~/.ssh/lab_ed25519*
ssh-keygen -lf ~/.ssh/lab_ed25519.pub

Ed25519, not RSA: shorter, faster, and no key-size decision to get wrong. The private key must be 600ssh refuses to use a key others can read, which is a feature.

Verify

stat -c '%a %n' ~/.ssh/lab_ed25519 # 600 /home/you/.ssh/lab_ed25519

2. Install the public key correctly

sudo useradd -m -s /bin/bash deploy
sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
sudo install -m 600 -o deploy -g deploy /dev/null /home/deploy/.ssh/authorized_keys
sudo tee -a /home/deploy/.ssh/authorized_keys < ~/.ssh/lab_ed25519.pub >/dev/null
sudo ls -la /home/deploy/.ssh

The modes are not decoration. sshd ignores authorized_keys if the file or the directory is group- or world-writable, and it does so silently from the client's point of view — the failure appears only in the server's log. That is the single most common "my key does not work" cause.

Verify

sudo stat -c '%a %U %n' /home/deploy/.ssh /home/deploy/.ssh/authorized_keys # 700 deploy /home/deploy/.ssh # 600 deploy /home/deploy/.ssh/authorized_keys

3. Test the key before changing any config

ssh -i ~/.ssh/lab_ed25519 -o IdentitiesOnly=yes deploy@localhost 'whoami; echo OK'

IdentitiesOnly=yes stops the agent offering every other key, so you are testing the key you think you are testing. If this fails, read the server side:

sudo journalctl -u ssh -n 20 --no-pager | grep -i -E "deploy|authent|key"

Do not proceed until this works. Every step after this removes a fallback.

Verify

ssh -i ~/.ssh/lab_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes deploy@localhost 'echo OK' # OK

4. Harden sshd, one drop-in file

sudo tee /etc/ssh/sshd_config.d/99-lab-hardening.conf >/dev/null <<'CONF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
AllowUsers deploy
X11Forwarding no
AllowAgentForwarding no
CONF
sudo sshd -t && echo "config syntax OK"

sshd -t validates without applying. Run it every time — a typo in sshd_config means the daemon refuses to start, and if you have already restarted it you are locked out.

A drop-in beats editing sshd_config: your changes survive a package upgrade and are one file to review. Confirm the include exists first:

grep -r "^Include" /etc/ssh/sshd_config

Verify

sudo sshd -t; echo "exit=$?" # exit=0

5. Apply it, then verify from the outside

With your other terminal still connected:

sudo systemctl reload ssh || sudo systemctl reload sshd

Now check what the server actually decided, rather than what you wrote:

sudo sshd -T | grep -E \
  "^(permitrootlogin|passwordauthentication|maxauthtries|allowusers)"

sshd -T dumps the effective configuration after all includes and defaults. This is the only trustworthy check, because a later Include can override your file.

Prove password auth is genuinely off:

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no deploy@localhost

It must fail with "Permission denied (publickey)" — meaning the server offered only key auth.

Verify

sudo sshd -T | grep -c "^passwordauthentication no" # 1

Verify

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no \ -o BatchMode=yes deploy@localhost 2>&1 | tail -n 1 # Permission denied (publickey).

What this does not cover

Key-only access stops password guessing. It does not stop a stolen key. In production you also want short-lived certificates or an SSH CA, fail2ban or equivalent for noise reduction, and a bastion so production hosts are not reachable from the internet at all.

Clean up

sudo rm -f /etc/ssh/sshd_config.d/99-lab-hardening.conf
sudo sshd -t && (sudo systemctl reload ssh || sudo systemctl reload sshd)
sudo userdel -r deploy
rm -f ~/.ssh/lab_ed25519 ~/.ssh/lab_ed25519.pub

Where this goes next

The door is locked. Next: the disk behind it filling up at three in the morning.