Hardening sshd
Goal
You harden sshd with a drop-in configuration file, verify with sshd -t and sshd -T, and then actually confirm that key access is maintained and password authentication is rejected.
Why it matters
Editing sshd on a remote server is cutting the branch you are sitting on. That is why the order is fixed: keep the session → edit → sshd -t → reload → confirm with a new session → only then end the existing session. And recent distributions split the configuration with Include /etc/ssh/sshd_config.d/*.conf. Because the first value wins when the same key appears several times, "I fixed it but it has no effect" is common. The habit of checking the final applied values with sshd -T blocks this problem at the source.\n\n## This lab runs on a virtual machine\n\nBecause sshd runs as a real systemd service, you can make it reread the configuration with systemctl reload ssh and confirm by connecting with a new session. It used to run in a Pod, where there was no systemctl and you had to handle the process directly with pkill sshd.\n\nWe took care of one thing in advance when setting up the environment. Ubuntu 24.04 has ssh.socket turned on, and in that state systemd decides the listening port — the Port in sshd_config is ignored. Even if you edit the configuration and restart, it listens only on 22, and there is neither an error nor a warning. To open the lab's port 2222, we turned off socket activation and turned on ssh.service. If you run into "I changed the port but it doesn't open" on a real server, look here first.\n\nWe leave port 22 as it is. It is so that there is a way back even if you write the configuration wrong — on a real server too, for the same reason, you open the new port first and check it before closing the old port.
Steps
- Create the
/root/hardendirectory and save the complete current effective configuration to/root/harden/baseline.txt. - Create
/etc/ssh/sshd_config.d/90-labhub.confand put in the following values.PasswordAuthentication no,KbdInteractiveAuthentication no,PubkeyAuthentication yes,MaxAuthTries 3,LoginGraceTime 30,X11Forwarding no,PermitEmptyPasswords no,LogLevel VERBOSE - Run the syntax check, confirm that it passes, and write the exit code to
/root/harden/syntax.txtin the formatrc=0. - From the final applied values, pick out only the four lines
passwordauthentication,kbdinteractiveauthentication,maxauthtries, andlogingracetime, and save them to/root/harden/effective.txt. - At the end of the same file, add a
Match User backupblock withForceCommand internal-sftpandAllowTcpForwarding no. Then save the effective configuration under that condition to/root/harden/match.txt.forcecommand internal-sftpmust be visible in the file. - In
/root/.ssh/authorized_keys, put the optionrestrict,pty,from="127.0.0.0/8"at the very start of the line that holds theid_labhubkey. (You have to turn pty back on for shell access to be maintained.) - After starting sshd again, confirm two things.
- Key access still succeeds → save the result to
/root/harden/still-works.txt - If you request only password authentication, it is rejected → save that error output to
/root/harden/password-denied.txt
- Key access still succeeds → save the result to
- Make
/root/harden/checklist.txtwith the following 5 lines. The values are based on thesshd -Toutput.passwordauthentication=no/kbdinteractiveauthentication=no/pubkeyauthentication=yes/maxauthtries=3/logingracetime=30
Notes
sshd -tis the syntax check,sshd -Tprints the effective configuration, andsshd -T -C user=backup,host=x,addr=127.0.0.1is the conditional effective configuration.- You restart sshd in the form
pkill -x sshd; sleep 1; /usr/sbin/sshd. The sshd in the lab environment already has the port 2222 setting. - You check password authentication rejection with
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -p 2222 root@127.0.0.1 true. Save the failure message. - Common mistake 1: adding only
restrictin step 6 and not turningptyback on, so shell access breaks. - Common mistake 2: only killing sshd and not starting it again in step 7, so all the following steps fail.
Recording the current effective configuration
Create the /root/harden directory and save the complete current effective configuration to /root/harden/baseline.txt.
sshd has an extended test mode that prints all the finally applied values. It is a different option from the syntax check.
Writing the drop-in configuration
Create /etc/ssh/sshd_config.d/90-labhub.conf and put in the following values.
PasswordAuthentication no, KbdInteractiveAuthentication no, PubkeyAuthentication yes, MaxAuthTries 3, LoginGraceTime 30, X11Forwarding no, PermitEmptyPasswords no, LogLevel VERBOSE
Make it a .conf file under sshd_config.d. The number at the front of the file name decides the reading order.
The syntax check passes
Run the syntax check, confirm that it passes, and write the exit code to /root/harden/syntax.txt in the format rc=0.
The option that checks only the syntax prints nothing if it succeeds. Judge by the exit code.
Checking the applied values
From the final applied values, pick out only the four lines passwordauthentication, kbdinteractiveauthentication, maxauthtries, and logingracetime, and save them to /root/harden/effective.txt.
The output of the extended test mode has all lowercase keys. Pick out only the four you need.
Conditional application with a Match block
At the end of the same file, add a Match User backup block with ForceCommand internal-sftp and AllowTcpForwarding no. Then save the effective configuration under that condition to /root/harden/match.txt. forcecommand internal-sftp must be visible in the file.
The extended test mode has an option that gives connection conditions. You write the three values user, host, and addr joined by commas.
Strengthening the authorized_keys options
In /root/.ssh/authorized_keys, put the option restrict,pty,from="127.0.0.0/8" at the very start of the line that holds the id_labhub key. (You have to turn pty back on for shell access to be maintained.)
Use together the option that restricts the source and the option that turns off all features. You have to allow the loopback range for the connection to be maintained.
Check first: this step works on the ~/.ssh/id_labhub key. That key was made in the earlier lab (SSH key authentication), but each lab starts a new box, so it is not here. Check with ls ~/.ssh and if it is missing, create it first.
ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_labhub
cat ~/.ssh/id_labhub.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Real verification after the restart
After starting sshd again, confirm two things.
- Key access still succeeds → save the result to
/root/harden/still-works.txt - If you request only password authentication, it is rejected → save that error output to
/root/harden/password-denied.txt
You have to start sshd again for the settings to take effect. This environment has no systemctl, so handle the process directly.
Hardening checklist
Make /root/harden/checklist.txt with the following 5 lines. The values are based on the sshd -T output.
passwordauthentication=no / kbdinteractiveauthentication=no / pubkeyauthentication=yes / maxauthtries=3 / logingracetime=30
You can take the values as they are from the sshd -T output. Keep them all lowercase.