So you’ve deployed your new VPS or cloud server and SSH is served up on port 22 with password authentication. If you’re reading this, you already know that’s entirely insecure and just begging to be attacked. I’ll detail my procedure for hardening SSH on Fedora Linux, the distro I run. This should also work on downstream RHEL and CentOS, and broadly speaking on any SSH server, though some bits may differ.
On my native setup the factors in play are:
On my servers, I swap out stock firewalld for iptables-services. I prefer the more straightforward controls of iptables, and I’m old school like that. Your situation may differ in this, or any number of other ways, so take what you can use from what follows and tweak as necessary.
Pick a good port
Due care dictates you can’t keep SSH on port 22. It’s one of the most conspicuous ports and among the first to be probed by attackers. Even if you had SSH locked down airtight, do you really want that kind of attention?
While technically true that moving a service to a less obvious port constitutes the dreaded security by obscurity, you should do it anyway. Here’s why: No one’s out there doing all-65,535 TCP port scans en masse. It just takes too long. Pick a good anonymous port way up in the user range, and you’ll really never get scanned.
Use this random.org tool to generate a random high port number. Go ahead, refresh a few times until you see one that tickles your fancy. Now cross check it against nmap-services. Make sure it’s not in there associated with some service. You want a port that scanners aren’t looking for. The list in nmap-services only covers 13% of the TCP port space, so there are plenty of ports to go around.
For this demonstration, I’ve just generated random port number 35160. Don’t use this one, pick your own. Also, pick different port numbers for different projects – don’t reuse the same one everywhere. Don’t be predictable.
Open the port in iptables
I use iptables to control packet filtering. Adapt this part to your own situation if you’re using firewalld or nftables. The goal is to open the port.
As root, edit /etc/sysconfig/iptables
. There will already be a rule in there for port 22:
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
Leave that rule for now, but add an identical rule for the new port. Change 35160 here to your generated port number:
-A INPUT -p tcp -m state --state NEW -m tcp --dport 35160 -j ACCEPT
Then issue systemctl restart iptables
to bounce the service.
That’ll cover your IPv4. If you have a live IPv6 interface (most don’t, check the output of ifconfig
for an inet6
address that doesn’t begin with fe80::
) then do the steps above for /etc/sysconfig/ip6tables
(bearing in mind the slight differences in syntax) and bounce the ip6tables service likewise.
Tag the port under SELinux
SELinux comes enabled and set to enforcing mode out of the box in Fedora, RHEL, and CentOS, although this can be changed after the fact. It’s easy to overlook if you don’t have experience with it.
Try issuing sestatus
. If the command isn’t found, you don’t have SELinux on your host and you can skip this section. Otherwise, read its output. If you see enforcing
, perform the following step.
Issue (change 35160 to your generated port number):
semanage port -a -t ssh_port_t -p tcp 35160
SELinux will now permit the sshd process to bind to the new port number.
Reconfigure sshd for the port change
Now for the easy and obvious part. Edit /etc/ssh/sshd_config
. Uncomment the line that now says #Port 22
and change it to Port 35160
(use your generated port number). Then issue systemctl restart sshd
to bounce the service.
If connected to an existing SSH session, don’t worry, it won’t be interrupted. But to confirm the change, issue netstat -plunt
and you should see the sshd service listening on the newly configured port.
At this point, disconnect (or just open a 2nd shell) and SSH back in on the new port. The command line OpenSSH client uses lowercase -p
for a port option, like:
ssh -p 35160 [email protected]
For the SFTP client, use a capital -P
option:
sftp -P 35160 [email protected]
If you got this far, you can safely delete the port 22 allow rule from /etc/sysconfig/iptables
and restart the iptables service, as above. The service no longer listens on it, but this will stealth port 22 to scanners.
Generate ssh keys
Having changed the port, you’ll next make the move from password to public key authentication. This procedure is well documented everywhere, but here it is again.
If you’ve never generated keys before or need to generate a new keypair, on your local client, as your ordinary user, in the .ssh/
subdirectory of your home directory, issue:
ssh-keygen -t rsa
You’ll be prompted for a filename (don’t overwrite any existing keys) and a passphrase, though you can opt to leave it blank. A pair of files will be produced. Say you entered id_rsa_myserver for the filename. You’ll have one file named id_rsa_myserver (the private key) and one named id_rsa_myserver.pub (the public key).
On the remote server, as your ordinary user, if it doesn’t already exist, create an .ssh/
subdirectory of your home directory and chmod 700 .ssh
to set the required permissions. Then descend into it and, if it doesn’t already exist, edit a new file authorized_keys
, paste the contents of id_rsa_myserver.pub into it, and chmod 600 authorized_keys
to set the required permissions. Or, if authorized_keys
already exists, just append the public key material to it.
Test pubkey authentication
Now disconnect (or open another shell) and try:
ssh -p 35160 -i ~/.ssh/id_rsa_myserver [email protected]
If it’s working, authentication should be passwordless, or at most prompt you for the passphrase once for your shell keyring cache.
For the SFTP client:
sftp -P 35160 -i ~/.ssh/id_rsa_myserver [email protected]
To save on typing, I highly recommend keeping a list of these as aliases in .bashrc
for commonly used servers.
Keep your keys safe folks. Never disclose your private keys or transfer them off your local client. Regularly back up all your keys to encrypted media.
Finish sshd configuration changes
Almost done. Edit /etc/ssh/sshd_config
once more. Prohibit password authentication and force pubkey authentication by implementing:
PasswordAuthentication no
While in there, a few other recommended edits:
LoginGraceTime 30
PermitRootLogin no
MaxAuthTries 4
AllowTcpForwarding no
X11Forwarding no
Some of these directives may have little effect but I do them out of habit. See man sshd_config
for explanations.
Finally, issue systemctl restart sshd
and you’re done.
Congratulations. You have secured SSH service on your server. No one’s scanning for services on some random, anonymous port, and no one’s getting in without the private half of the keypair anyway if they do find it. Keep it safe!