A web server is just another computer and requires basic security configuration. In the first part, we show you how to secure your SSH access and configure your firewall. You can use this configuration for any type of server.
- Step by step guide for basic server security
- External files
Always stay in the loop!
Subscribe to our RSS/Atom feeds.
- server with operating system installed (e.g. Debian, Ubuntu, CentOS)
- basic knowledge of shell commands
- SSH client on your client
- security token (YubiKey, Nitrokey) or app (FreeOTP) with OATH-TOTP support
- nmap (optional)
We will use Debian 9 in this series.
Step by step guide for basic server security
First of all, you have to install a server operating system. After that, you will be able to log in using SSH on your computer:
When connecting for the first time, SSH will ask for your confirmation that you connect to the right server. Type
yes. After confirming, you have to enter your initial password. That’s all.
Step 0: Check for updates
When you are connected to your server for the first time, use
apt (Advanced Package Tool or the package manager of your OS) to check for updates:
sudo apt update and install updates, if available.
Step 1: Install and configure ufw (your firewall)
Download and install ufw (Uncomplicated Firewall) since ufw is one of the easiest ways to configure your firewall:
sudo apt install ufw.
Then, you can check the status of ufw:
sudo ufw status which will most likely show
Change the default rules to:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Now you have to enable the SSH port 22:
sudo ufw allow ssh
However, this enables this port for everyone on the internet. Every possible IP address can connect to port 22 of your server. There are hundreds of automated bots on the internet which try to connect to port 22 in order to get into your server. We observed these bots for two hours and saw about 8,000 login attempts. A good practice is to enable SSH just for your personal IP address:
sudo ufw allow from [IP-address-of-your-client] to any port 22
The problem is that your ISP may change your public IP address from time to time. This means that you are unable to connect to your server until you get your configured IP address back. You can observe your IP address for several days and most likely you will see that it doesn’t change much. So, you likely want to allow a subnet to connect with your server:
sudo ufw allow from [IP-address-of-your-client]/[subnet] to any port 22
This is the common CIDR notation. Your suffix [subnet] will be “24” to “26” in most cases. This drastically restricts the number of hosts which can connect to your port 22.
Finally, you have to start and enable ufw:
sudo ufw enable. Confirm that this command may disrupt existing SSH connections by entering
y. You should see
Firewall is active and enabled on system startup.
Please note that we only allow connections to port 22 so far. Later, we will also enable ports 80 (HTTP) and 443 (HTTPS), so people can connect to your website. If you don’t plan to run a web server but other server software, only allow the according port number(s).
Step 2: Create a non-root account
A basic security measure of all operating systems is to use non-root accounts for the most time. This ensures that an attacker doesn’t get all privileges if he exploits a program which runs in a non-root context.
On Debian, enter
sudo adduser [username]. You will be prompted for a new password twice. Skip the user information by pressing Enter several times if you are the only user of this server and confirm this by pressing
The new user doesn’t have the possibility to get administrative rights by default. To change this, you have to add the new user to the
sudo group (other operating systems call this group
sudo usermod -aG sudo [username] to add the new user to the
sudo group. You can see all groups of a user by entering
sudo groups [username]. In some cases, you also have to install sudo itself:
sudo apt install sudo.
Finally, switch to your new account:
sudo su [username]. From now on, you have to explicitly enter root mode by preceding each command with
sudo, if necessary.
Step 3: Harden your SSH configuration
The default SSH configuration allows password-based and root login. It is considered more secure to allow only non-root users and use keys instead of passwords. Furthermore, there are dozens of legacy algorithms allowed by default which we disable.
Step 3a: Switch from passwords to keys
Generate a key pair on your client, not on your server:
ssh-keygen -b 4096 -t rsa -f ~/.ssh/id_[servername](4096 bit RSA key pair)
ssh-keygen -t ed25519 -f ~/.ssh/id_[servername](256 bit Ed25519 key pair)
This will generate either a RSA or Ed25519 key pair and store it in your local “~/.ssh/” folder. You can optionally enter a password which you must enter each time you use your SSH key to connect to your server. This password is used to encrypt your private key using 128 bit AES.
You will find two files in “~/.ssh/”:
You now have to copy the public key to the server by using the following command:
ssh-copy-id -i ~/.ssh/id_[servername].pub [username]@[IP-address-of-your-server]
You will have to enter the password of
[username] and finally you will see: “Number of key(s) added: 1”. Immediately, open a second terminal windows and try to connect to your server without logging out before:
ssh [username]@[IP-address-of-your-server]. If configured correctly, there will be no password prompt since your new key is used for authentication.
Step 3b: Configure sshd
When you successfully checked that your new key is in use, connect to your server using SSH and open the configuration file of sshd:
/etc/ssh/sshd_config. Be sure that you actually open
ssh_config (no d) is the configuration file for clients.
Change the file accordingly:
PasswordAuthentication no(disable password-based login)
PermitRootLogin no(disable root login)
LogLevel VERBOSE(log login attempts)
# Ciphers and keying add:
# Authentication: add:
sudo systemctl restart sshd (or use the command of your operating system). Immediately, open a second terminal windows and try to connect to your server without logging out before:
ssh [username]@[IP-address-of-your-server]. If configured correctly, you are logged in without any prompts or errors.
Step 4: Enable 2FA for SSH
First of all, we have two install
libpam-google-authenticator. This is a module for PAM (pluggable authentication module):
sudo apt install libpam-google-authenticator. This also installs
After installing, configure the module by entering:
google-authenticator. When asked “Do you want authentication tokens to be time-based (y/n)”, confirm by pressing
Recommended configuration is:
- Do you want me to update your “/home/[username]/.google_authenticator” file (y/n) →
- Do you want to disallow multiple uses of the same authentication token? →
- … we allow an extra token before and after the current time … Do you want to do so? (y/n) →
- Do you want to enable rate-limiting (y/n) →
Now, scan the provided QR code using a TOTP app. Write down the emergency codes and store them in a secure place.
OMG, there is a Google link and Google knows my secret! Yes, there is actually a Google link. You will find your QR code there, on a Google website. Does this mean that Google knows your secret? No, since this link is generated locally on your server. If you don’t click it, no data will be transferred to Google. Google can display any QR code using this URL.
Finally, we must tell SSH that there is a 2FA PAM module. Open your
/etc/pam.d/sshd file on the server:
auth required pam_google_authenticator.soto the bottom of the file
Save the file and quit.
/etc/ssh/sshd_config as before. Change:
Save all changes, quit and restart sshd:
sudo systemctl restart sshd. When you try to connect to your server in future, there will be a “Verification code:” prompt. Enter the TOTP generated by your app or your emergency codes when you lost your app/phone etc.
On GitHub, we provide a Gist showing a minimal configuration file: sshd_config on GitHub.
Step 5: Back up your SSH keys
As always, it is important to back up your cryptographic keys. On your client, go to
~/.ssh/ and back up all files in there. You should also back up the
/etc/ssh/sshd_config of your server.
Follow us on Mastodon:
Step 6: Test the configuration
Finally, we want to test our configuration. Most of the configuration like “Does SSH use my public key?” or “Does SSH use 2FA?” can directly be tested by restarting sshd and connecting with your server again. But you can’t see if SSH uses only modern algorithms.
One possibility is to use built-in commands:
- on your client, use
ssh -G [hostname]to view all parameters in use
- on your server, use
sudo sshd -Tto view all parameters in use
Furthermore, there are several online tools available, however, we blocked most IP addresses. So we must use a tool on our local computer which is allowed to connect to port 22.
nmap is perfect for this test. Enter
nmap -Pn --script ssh2-enum-algos [IP-address-of-your-server].
nmap will show:
- kex_algorithms: (2)
- server_host_key_algorithms: (1)
- encryption_algorithms: (3)
- mac_algorithms: (3)
- compression_algorithms: (2)
Step 7: Configure your local SSH client
After configuring and testing server-side configuration, we can still improve our SSH setup on our local SSH client. Open
~/.ssh/config on your client. This file contains the user-specific SSH configuration on your local machine.
The idea is to create a list with global parameters, and server-specific configuration. Let’s have a look at the following example:
As you can see, the configuration contains global parameters as well as two host-specific entries. These parameters are:
HashKnownHosts: Hash all host names and addresses when they are added to
~/.ssh/known_hosts(doesn’t affect existing names and addresses). The idea is to protect identifying information in the file.
VisualHostKey: Shows an ASCII art representation of the remote host key fingerprint at login.
IdentitiesOnly: Only use the authentication identity files configured in the ssh_config files (
/etc/ssh/ssh_config) even if there are more identify files available.
HostKeyAlgorithms: Specific algorithms for different purposes as explained above.
VerifyHostKeyDNS: Uses DNS and SSHFP to validate the remote host key. This is only useful if you set a server-side SSHFP resource record.
LogLevel: There are different log levels. Valid levels are QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG (DEBUG1), DEBUG2, and DEBUG3. The default is INFO.
HostName: The real host name to log into. IP addresses are also valid, however, you must change them if DNS configuration (A, AAAA resource records) changes.
User: The remote user name that is used to log in as.
IdentityFile: The local identity file used for authentication. We generated these in step 3a.
For more parameters, have a look at the man page of ssh_config.
Additionally, you can configure your local
/etc/ssh/ssh_config file, if needed. This configuration is valid for all local users.
This article is part of the "Web server security" series.
Read other articles of this series.
Done! You configured your firewall, so only your client can connect with your server using port 22. Moreover, you hardened your SSH configuration and tested it. Finally, you created a backup. An attacker has find a valid IP address to connect with your server and then he has to provide the correct private RSA/Ed25519 key and the TOTP.
Keep in mind that SSH keys are like passwords: Store them securely, only use them on trusted devices, generate new keys from time to time and securely delete old SSH keys.
Advanced users can set an SSHFP record (Secure Shell fingerprint record) on their DNS server.
- Minimal sshd_config on GitHubexternal link
- Feb 9, 2019: Added built-in SSH commands to view parameters in use.
- Jan 16, 2019: Added more complete client-side configuration.
- Jan 7, 2019: Removed
-oflag since the OpenSSH format for private keys is used by default (OpenSSH 7.8+). Added information how the private key is encrypted.