A guideline for SSH
SSH is the tool I reach for every day. This post collects the parts I use most: connecting, saving hosts in a config file, changing the port, moving to key-based login, disabling password login for root, and even sending a mail on every login.
Hosts used in this guide
| host | IP address |
|---|---|
| client | 192.168.0.1 |
| server | 192.168.0.254 |
| e-mail server | 192.168.0.253 |
Connect with SSH hosts
ssh 192.168.0.254
Save connections
To avoid typing the same details every time, I store and label my hosts in the user's.ssh/config file:
~/.ssh/config
A minimal host entry looks like this:
Host HOST
Hostname 192.168.0.254
Port 22
User root
With that saved, I connect by just passing the label:
ssh HOST
And I can override any stored parameter on the command line:
ssh -p 111 user@HOST
Change configuration on the fly
The main configuration file for SSH on a Debian system is:
/etc/ssh/sshd_config
Tip: if you are already logged in and change the SSH config — even the port — you can restart the service safely. Your current session stays up until you log out, so you always have a way back in if something is wrong.
Restart the service
To apply the changes, I restart the service:
service ssh restart
Change your SSH Port
The default SSH port is22, which every bot on the internet already knows. Changing it does not stop a determined attacker, but it cuts the brute-force noise in the logs to almost nothing. I find thePort line in the main config file and change it:
Port 22
Hide the port with knocking
Changing the port cuts the noise, but the port is still open to anyone who scans for it. With port knocking the SSH port stays closed in the firewall until I send a secret sequence of connection attempts to a set of ports. Only then does the firewall briefly open SSH — and only for my address.
I useknockd for this:
apt install knockd
The config defines the knock sequence and the firewall rule it triggers. One sequence opens the port, the reverse sequence closes it again:
[options]
UseSyslog
[openSSH]
sequence = 7000,8000,9000
seq_timeout = 5
command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 9000,8000,7000
seq_timeout = 5
command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
The firewall's default policy for the SSH port staysDROP, so without the right sequence the port does not even answer. From my client I knock in order, then connect as usual:
knock <server> 7000 8000 9000
ssh <server>
Port knocking is obscurity, not a real access control — but combined with keys it keeps the port off the radar of automated scanners entirely.
Use SSH without a password
Instead of a password I use a key pair. RSA still works, but I switched toEd25519 — it is the modern default: fast, short keys, and no weak parameter choices to get wrong. First I create the pair:
ssh-keygen -t ed25519
The public key goes onto the remote hosts; the private key stays with me and lets me log in to all of them. That also means anyone who steals the private key can reach every host, so it is worth protecting it with a passphrase. To skip the passphrase, press Enter through the prompts:
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/test/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_ed25519
Your public key has been saved in /home/test/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:nm9VTWqhaFf8yeOmjq0Xt0X74aa574WpkVL5dJvk2c4 test@testpc
The key's randomart image is:
+--[ED25519 256]--+
| . |
| . + .|
| o o =.*.|
| . B * O++|
| .S* o B.oo|
| .o.o . o=.|
| o. + +E+|
| .= .+.+.|
| ++++=* |
+----[SHA256]-----+
Then I copy the public key to a host:
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 22 root@192.168.0.254
It prompts once for the password, then copies the key over:
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/test/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.0.254's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -p '22' 'root@192.168.0.254'"
and check to make sure that only the key(s) you wanted were added.
Prevent root login via password
Once the key is in place, there is no reason to allow password login for root anymore. I set this parameter toprohibit-password:
PermitRootLogin prohibit-password
The parameter has these options:
yes— root can log in with a passwordno— root cannot log in over SSH at allwithout-passwordorprohibit-password— root can log in, but only with a key pair
After restarting SSH, a host that does not have the private key (.ssh/id_ed25519) can no longer log in — exactly what I want.
Use only modern crypto
Even with keys, the server still offers a range of ciphers and key-exchange algorithms for backward compatibility, and some of the older ones are weak. I restrict the server to the modern set in the main config file:
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com
PubkeyAcceptedKeyTypes ssh-ed25519,ssh-ed25519-cert-v01@openssh.com
I also drop the RSA and ECDSA host keys and keep only the Ed25519 one, so nothing weaker is ever negotiated:
rm /etc/ssh/ssh_host_rsa_key* /etc/ssh/ssh_host_ecdsa_key*
Ban brute-force with fail2ban
For the attempts that still reach SSH,fail2ban watches the auth log and bans an IP after too many failed logins:
apt install fail2ban
I enable thesshd jail in a local override, so a package update never overwrites it:
# /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 3
findtime = 10m
bantime = 1h
After a restart, three failed logins from an address get it banned for an hour. I can watch the jail with:
systemctl restart fail2ban
fail2ban-client status sshd
Send mail on SSH login
I also wanted a heads-up whenever someone logs in. This file runs on every successful SSH login:
/etc/ssh/sshrc
The script mails the IP address of the user who just logged in, and writes a line to syslog as well:
ip=`echo $SSH_CONNECTION | cut -d " " -f 1`
# Get the IP Adress of the connected user
logger -t ssh-wrapper $USER login from $ip
# Log to syslog
echo "User $USER just logged in from $ip" | sendemail -q -u "SSH Login" -f "Originator" -t "Login <logins>" -s 192.168.0.253 &
# Send mail
A note on OAuth
Keys, a moved port, knocking and fail2ban are the practical layers I actually run. I have also played around with OAuth/OIDC on top of this: logging in through an identity provider and handing out short-lived SSH certificates instead of managing static keys per machine. For more than a handful of hosts that is a cleaner model — no key sprawl, access expires on its own — but it is a bigger topic than a config tweak, so I will give it its own post once I have run it properly.