You finally did it. That one Linux person you know finally convinced you to lease a virtual server. Now you're stuck with the task of configuring it and you have absolutely no idea what to do. Let's take a dive into securely configuring a fresh Linux box, starting with the basics.
Although this guide is aimed at newbies, I'll assume your Linux friend has at least taught you basic UNIX commands and how to use SSH.
Once you log in, you'll probably see something like this:
Linux tutorial 4.9.0-8-amd64 #1 SMP Debian 4.9.110-3+deb9u6 (2018-10-08) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Mar 9 15:39:18 2019 from xxx.xxx.xxx.xxx
If you didn't choose Debian, you'll see something else, but this guide should work regardless of the distribution you're running. Almost all distros are similar under the hood.
If your Linux friend has a more minimalist taste (like me!), they'll probably recommend something like Alpine Linux or Void Linux.
Before we do anything, let's update and reboot. In Debian, run
apt-get update followed by
apt-get upgrade. In Alpine, run
apk update, then
apk upgrade. In Void, run
xbps-install -Syu. For Arch, run
pacman -Syu. For others, consult the manual. Don't forget to
reboot after running the upgrade!
Let's dig in!
Step 1 - Cut the shit
If you've chosen a Debian-based distro, like Ubuntu or, well, Debian, we'll start by stripping out some useless services in order to lessen the attack surface. Having fewer services running means there are fewer potential entry points for attackers.
Instead of telling you what to remove, I'll tell you what you'll need to keep for a minimal working system.
Log daemon – logging
Services don't have permissions to write directly to the system logs, otherwise they would be able to remove and manipulate them. For this reason, we have logging daemons. They accept connections and write to logs on behalf of other programs. Common ones include
Cron daemon – scheduling
Cron is a service to schedule tasks to run over and over, on a specific schedule. It's extremely flexible, and some distros come with preset Cron configurations (called Crontabs) to perform system tasks like archiving old logs. Common Cron daemons are
SSH daemon – remote control
This is what we're going to use to control the server remotely, so you'll want to keep this. If you're setting up some sort of router, this will likely be
dropbear. In literally every other case the SSH server is OpenSSH – with the binary creatively called
TTY daemon – console access
The TTY is what you use to fix your mess when you've screwed up the networking or misconfigured the firewall and are not able to SSH in. If you're on a Linux desktop, you can enter a TTY by pressing Control, Alt, and one of the function keys along the top row. On your new server, you should be able to access the console through the administration panel of your provider. If they don't provide this, you obviously won't be needing this daemon. TTY daemons are
NTP – time synchronisation
NTP is the Network Time Protocol, it keeps the system clock in sync with, well, the time. Without this, your time will drift, which may cause issues with the verification of TLS certificates, as they only work between two fixed points in time. Want to see what happens? Set the time to 1975:
date +%Y%m%d -s "19750101" then try to load some webpages. NTP is usually handled by
ntpd, but I've seen
chronyd offered in some distro installers as well.
Removing all the things
Let's start removing some of the useless stuff that distros ship with. If you're unfortunate enough to be using a SystemD-based distro like Debian, Ubuntu or Arch, the following should work:
root@tutorial:~# systemctl stop dbus
root@tutorial:~# systemctl disable dbus
Synchronizing state of dbus.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable dbus
If, however, you opted for something a little more minimalistic, then you won't need to disable anything. Minimal distros like Alpine or Void only ship with stuff you need. Just for kicks, here's how you'd disable and stop a service in runit-based distros like Void:
root@void:~# sv stop dbus
ok: down: dbus: 0s, normally up
root@void:~# rm /var/service/dbus
And OpenRC-based distros like Alpine:
root@alpine:~# service dbus stop
* Stopping Dbus daemon ...
root@alpine:~# rc-update del dbus
* service dbus removed from runlevel default
That will disable and stop
dbus, a program for typically graphical programs to send messages to each other. Completely superfluous on a server.
If your provider doesn't give you access to TTYs, you won't need them. My Debian came with
getty running on
tty1. Disabling it is easy:
systemctl stop email@example.com followed by
systemctl disable firstname.lastname@example.org.
For Alpine, edit
/etc/inittab with your favourite editor and comment the TTYs you don't want:
# Set up a couple of getty's
tty1::respawn:/sbin/getty 38400 tty1
#tty2::respawn:/sbin/getty 38400 tty2
#tty3::respawn:/sbin/getty 38400 tty3
#tty4::respawn:/sbin/getty 38400 tty4
#tty5::respawn:/sbin/getty 38400 tty5
#tty6::respawn:/sbin/getty 38400 tty6
# Put a getty on the serial port
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100
I have kept
ttyS0 enabled, as my provider, Linode (affiliate link), gives me both graphical access with Glish and serial console access with Lish. You'll need to force the init system to recheck its configuration after you edit this file. Do this with
kill -HUP 1.
If you're using Void, do the following:
root@void:~# sv stop agetty-tty1
ok: down: agetty-tty1: 1s, normally up
root@void:~# rm /var/service/agetty-tty1
Repeat for each TTY you want to disable. If you want to re-enable a TTY, do this:
ln -s /etc/sv/agetty-tty1 /var/service/ and
sv start agetty-tty1. Void actually starts the service once it detects the symlink, but I always like to check.
Step 2 - Hardening SSH
Next, let's make it harder for attackers to go through the front door.
First, let's generate an SSH key on our workstation (not on the server!):
user@workstation:~$ ssh-keygen -t ed25519
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_ed25519.
Your public key has been saved in /home/user/.ssh/id_ed25519.pub.
You can enter a password when it prompts you, or leave it blank. If you set one, you'll need to type this when you first use SSH every time you log in to your workstation. It'll cache the password for the session once you enter it once. If your system isn't configured to use the SSH agent, you will have to enter the password each time you use SSH.
I'm using an ED25519 keypair. It's secure by modern standards (2019), but do your own research. This isn't a cryptography guide.
/home/user/.ssh/id_rsa.pub and copy the contents to
/root/.ssh/authorized_keys on the server.
/root/.ssh/authorized_keys will end up looking like this:
root@tutorial:~# cat /root/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN/74jyGbUAv/OTzDsTsya1lPrkgF50AOCw0JbC5W0QN user@workstation
Next, fully close your SSH session and log in to the server again, as
root. You should only have to enter your SSH key password if you set one, not the password for the
Now that you're connected with public key authentication, we can nuke password authentication from orbit. Open up
/etc/ssh/sshd_config and change this:
Change/add the following lines as well. Don't add them if they're already in the file. Duplicates will prevent SSHd from starting.
Which does this:
- Prevent the
root user from logging in with a password, but not by other means
keyboard-interactive authentication, which is essentially the same as password authentication
- Don't print the message of the day when users connect, nobody wants to see the copyright notice
- Don't do reverse DNS lookups on users when they connect, saving precious time and resources, and making connections faster on busy servers and servers with slow connections.
- Don't use PAM (duh!)
Restart SSH with
service ssh restart (works for both Debian and Alpine!) or
systemctl restart ssh (Arch).
Let's also change the
root password for good measure. Do this with
Log out then log back in. You'll see the message of the day has disappeared:
Last login: Sat Mar 9 15:39:19 2019 from xxx.xxx.xxx.xxx
Yes, we've not disabled
root login. No, this is not a security risk, because we've disabled password login. Nobody can log in as
root without having the private SSH key, which we've encrypted. Sure, malware could easily track keystrokes, get our passphrase, and decrypt the key, but this is even easier with password login. If having a compromised workstation is part of your threat model, look into using a hardware security module like a smart card or YubiKey.
Step 3 - Firewall
We'll set up Uncomplicated Firewall -
ufw - to restrict incoming connections:
root@tutorial:~# apt-get install ufw
root@tutorial:~# ufw default deny
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
root@tutorial:~# ufw allow ssh
Rules updated (v6)
root@tutorial:~# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
root@tutorial:~# systemctl start ufw
root@tutorial:~# systemctl enable ufw
Synchronizing state of ufw.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable ufw
For Alpine, do
service ufw start and
rc-update add ufw. For Void, do
ln -s /etc/sv/ufw /var/service/ and
sv start ufw.
We've only permitted one incoming port for SSH. If you want to open another port for something, use
ufw allow 2222/tcp. If you change your mind, it's easy to delete rules as well:
root@tutorial:~# ufw status numbered
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 25/tcp ALLOW IN Anywhere
[ 3] 22/tcp (v6) ALLOW IN Anywhere (v6)
[ 4] 25/tcp (v6) ALLOW IN Anywhere (v6)
root@tutorial:~# ufw delete 4
Proceed with operation (y|n)? y
root@tutorial:~# ufw delete 2
Proceed with operation (y|n)? y
Rule deleted (v6)
Don't get caught out by the list entries changing. If you've got 3 rules and you remove number
1, the ones you saw in the status as
3 are now numbered
ufw allow can accept mappings from
ssh is the same as
22/tcp. You can play around with this by looking up mappings with
root@tutorial:~# getent services ssh
root@tutorial:~# getent services 22/tcp
There we have it. You've got a secured Linux server, for all your Linuxing needs!
If you want to take your security even further, you can set up login notifications and brute-force attack prevention, harden SSH ciphers and improve the security of your local SSH client to prevent side-channel cryptanalysis. Watch this space for links on how to do that, or subscribe to the RSS feed if you're one of the cool people who use RSS.