Securing your new Ubuntu server Link to heading

I’ve setup so many Ubuntu servers at this point that I’ve developed a methodology to make it secure and accessible, or at least… I think I have, I’m making notes here so I don’t forget.

Configuring SSH Link to heading

If you’ve ever watched your SSH logs before (sudo journalctl -fu ssh) you might notice constant, methodical attempts to access your server; these are automated bots going through a list of IP addresses attempting popular usernames and passwords. They are testing for the low-hanging fruit of insecure servers, using the most common username and password combinations with the most common access methods, thousands of times per day, every day, for millions of IPs, all over the world. If they find a working combination, chances are the bot will automatically bring its new victim into the fold and have it start making the same intrusion attempts to other IPs, at this point your server is part of a botnet, and it’s probably going to be sold as part of a Denial of Service… uhh service, or something even more nefarious, and if you’re really unlucky you’ll be on the hook for any legal ramifications down the road…

Creating your own user Link to heading

If you’ve been provided with only a root user then you should first create your own. Why? root is by far the most common username of any system, changing it is an easy way to deter 99% of automated intrusion attempts. It also means you won’t have explicit root permissions, you’ll need to use sudo to elevate your permissions to that of the root user whenever you need to, this is standard practice in Linux and is similar to User Access Control in Windows; it helps avoid accidents, makes it easier to audit administrative changes and restricts elevated access behind your password.

  1. Create a new user:
    sudo adduser <username>.
  2. Add your new user to the sudo group:
    sudo adduser <username> sudo.
  3. Exit your root login:
  4. Re-connect with your new username:
    ssh <username>@<host>.

Switching to key-pair authentication Link to heading

Key pairs are used for a lot of things, mainly encryption adjacent, here it will be used in-place of a password.

Passwords are flawed.

“The password is dead” has been a popular concept since 2004. Every day, computers get faster at password cracking so, we make passwords longer and more complicated, then people have to remember them, but they can’t, so they use simpler passwords, or find some trick to make it easy to remember, which in-turn is used to crack them faster. Password security is a never-ending arms race of which humans have lost. A password wallet will go a long way to improving your situation, and making all your passwords incredibly long is good practice, but for SSH at least, key-pairs are the industry standard for an easier, convenient, and secure solution.

  1. On your own computer (not the server), open a terminal and use the command:
    ssh-keygen -t ed25519 -C <username>

    This creates a key-pair using the ed25519 type, which is the best™ one. -C means comment, providing your username just makes it easier to differentiate between different key-pairs if you have lots of them; you could also add the hostname or IP address of your server to the comment if you wanted, i.e:
    -C <username>@hostname

  2. Add your new public key to the server:
    ssh-copy-id -i ~/.ssh/ <username>@<server>

    The path ~/.ssh/id_ed25519 should be the path to your new Public Key, the ssh-keygen command in the previous step will have provided this path in its output:
    Your public key has been saved in /home/hugo/.ssh/

    This handy command conducts the deceptively complex process of importing your public key into ~/.ssh/authorized_keys on your server, creating the file if needed, and setting the correct permissions on the ~/.ssh directory, (otherwise SSH will refuse to use it) which should only be accessible by your own user and group, with permissions of 700. A daunting process for many people especially those just getting started, and one riddled with easy mistakes that have high consequences (being locked out of your server, permanently).

    Check the output of ssh-copy-id, if successful it will inform you on the Number of key(s) added:, you can now SSH into your server without needing a password, hurray! 🎉

    Be sure this works before proceeding because we’re about to disable password authentication entirely, simply run your SSH command, it shouldn’t prompt you for a password any more:
    ssh <username>@server

  3. We’re now going to restrict SSH access to members of a specific group called ssh-users, then disable root SSH access and password authentication.

    1. Create the group:
      sudo groupadd ssh-users.
    2. Add yourself to this group:
      sudo useradd <username> ssh-users.
    3. Create a new config extension file:
      sudo nano /etc/ssh/sshd_config.d/99-secure.conf.
    4. Paste in the following:
      PermitRootLogin no
      AllowGroups ssh-users
      PasswordAuthentication no
    5. Save and close the file. Most SSH configurations are fairly self-explanatory, here we’ve used:
      • PermitRootLogin determines whether the root user can use SSH, if set to no then root is denied.
      • AllowGroups restricts SSH access to only the users within the specified group, we’ve specified the one group ssh-users. Technically, adding this line negates PermitRootLogin entirely, even if it was still enabled AllowGroups would still take precedence, but both are included here to ensure root is disabled even if you opt to not use AllowGroups.
      • PasswordAuthentication is used to control whether a password can be used for SSH authorisation, we’ve disabled it.
    6. Reload the SSH service configuration:
      sudo systemctl reload ssh.
    7. Test you can still connect to the SSH server, I recommend opening a new terminal and starting a new SSH connection without closing your current one, otherwise if it doesn’t work you’ll be locked out with no way to fix it! You should also try connecting with the root account, to confirm it no-longer works.
    8. Assuming all that works, congratulations SSH is now a little more secure! Hurray! 🎉 🎊

Some useful tips to keep in mind using SSH in future: Link to heading

  • Your private keys are saved inside of ~/.ssh on your computer, you should keep these safe, consider making a backup somewhere so, if your PC breaks or you buy a new one you can restore this directory and retain access.
  • If you do end up losing your private key, depending on your type of server you might still be able to gain access through a couple different methods:
    • If you’re renting a VPS (Virtual Private Server), some providers like Hetzner or Digital Ocean provide a virtual console where you can login using a password still.
    • If you’re using a local server running on a computer at home, you can always connect a monitor and keyboard and login to the local terminal with your password that way.
    • If you’re using a Raspberry Pi, as well as connecting a monitor you can also connect a serial console using UART on the IO pins, but you’ll need a USB-UART adapter (or another Raspberry Pi and some wires) to try this.
    • Of course, with all the above being considered you should take measures to protect access to any virtual console and the physical security of your server, use multifactor authentication where available and utilise a military-grade fire-proof reinforced safe with anti-lock-picking measures and 24/7 security detail where appropriate.

Configuring the firewall with UFW Link to heading

UFW stands for Uncomplicated Firewall, or at least that’s what the developers claim. It handles all the complex IPTables commands and provides a really simple interface to allow or deny ports and IP Addresses, as well as a secure default rule set once enabled.

One fancy feature of UFW is that applications can bundle rule set templates with their package, making it easy to allow multiple ports without having to check the documentation specific to that program, for example to allow all ports relevant to SSH we can do sudo ufw allow OpenSSH. You can see the full list of application templates available by running sudo ufw app list:

$ sudo ufw app list
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  Postfix SMTPS
  Postfix Submission

If your application isn’t listed, or you want a custom configuration, you can allow specific ports by doing sudo ufw allow 22. For comparison, the iptables command to do the same thing would be iptables -I INPUT -p tcp --dport 22 -j ACCEPT, but this also assumes you’ve configured additional rules to drop incoming connections that aren’t explicitly allowed, rules that UFW will configure for us automatically when we enable it. With UFW we can also make use of automatic rate-limiting features, it will temporarily block connections from IPs are that spamming your server, this is useful for example to block repeated login attempts to your SSH port by malicious actors. To enable automatic rate-limiting of a port we use the limit command instead of allow, i.e. sudo ufw limit OpenSSH.

  1. Allow any applications and ports you need, such as SSH, web-servers etc:

    # Opens ports required for SSH access with automatic rate-limiting.
    sudo ufw limit OpenSSH
    # Opens ports required for the NGINX web-server (you might need this if you're hosting a website etc.)
    sudo ufw allow "Nginx Full"
    # Opens just port 3389 (Used for RDP access)
    sudo ufw allow 3389

    The above are just examples, if you don’t have any specific applications running that need open ports, then the only rule you need to add right now is OpenSSH.

  2. Enable the firewall:
    sudo ufw enable

  3. Test you can still SSH into the server. Open a new terminal and start a new SSH connection, don’t close your current one yet, or you’ll lose access permanently if the firewall is blocking new connections.

  4. If everything is working as expected then you have configured your firewall successfully! Hurray! 🎉 🎊 🚀

Automatic updates Link to heading

To ensure your server is always up-to-date, especially for the latest security fixes, you should configure and enable Unattended Upgrades which handles the automatic download and installation of updated APT packages. APT manages the installation of most software in Ubuntu and Debian as well as important kernel packages.

  1. Install Unattended Upgrades:
    sudo apt install unattended-upgrades

  2. Check out the configuration file, don’t edit it yet:
    sudo cat /etc/apt/apt.conf.d/50unattended-upgrades

  3. This is a pretty long file and with no syntax highlighting it can look a bit daunting at first, but each section includes helpful comments describing what they do, we’ll go through the relevant sections that will be included and then summarise with a total file at the end. Rather than edit this file, we’ll create a new configuration snippet, which will override the default settings.

    • First area to note is the Unattended-Upgrade::Allowed-Origins line, this section lists which source packages will be automatically upgraded from:

      Unattended-Upgrade::Allowed-Origins {
        // Extended Security Maintenance; doesn't necessarily exist for
        // every release and this system may not have it installed, but if
        // available, the policy for updates is such that unattended-upgrades
        // should also install from here by default.
        //  "${distro_id}:${distro_codename}-updates";
        //  "${distro_id}:${distro_codename}-proposed";
        //  "${distro_id}:${distro_codename}-backports";

      By default, only security updates are configured, specified by these lines:
      "${distro_id}:${distro_codename}"; and "${distro_id}:${distro_codename}-security";
      Next are lines enabling Extended Security Maintenance (ESM), this is a paid feature offered by Canonical, if you have the feature enabled then Unattended Upgrades will automatically install these too by default. If your serve is for personal use then you can register and get Pro features for free.
      Next is updates, proposed and backports, we only need updates and will include it in our configuration file at the end. proposed are beta packages not yet fully tested and backports are updates included in the next version of Ubuntu, these might cause conflicts, so it’s best to leave them disabled.

    • AutoFixInterruptedDpkg enables automatically resolving an interrupted update process. This is a good idea to enable - if your server is interrupted, possibly due to a power-outage or system crash, it can continue installing updates once normal process is restored, otherwise it will hang until you resolve it manually:

      Unattended-Upgrade::AutoFixInterruptedDpkg "true";
    • Remove-Unused-Dependencies is useful for removing unused packages left-over after a package that uses them has been uninstalled or simply no-longer requires it:

      Unattended-Upgrade::Remove-Unused-Dependencies "true";
    • Automatic-Reboot when required this will schedule a reboot either at the specified time, or immediately depending on how you configure the next few settings.

      Unattended-Upgrade::Automatic-Reboot "true";
    • Automatic-Reboot-WithUsers by default reboots won’t happen if users are logged in, this setting forces a reboot anyway. This is useful if you’re not scheduling reboots, to prevent immediately interrupting someone’s work.

      Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
    • Automatic-Reboot-Time specifies a time of day to do reboots instead of immediately. Even when configured, reboots will only be scheduled if required by an update.

      Unattended-Upgrade::Automatic-Reboot-Time "02:00";
    • These are all the settings we’re going to configure, be sure to read through the rest of the template file and add in anything else you want to include, our final config file looks like this:

      Unattended-Upgrade::Allowed-Origins {
      Unattended-Upgrade::AutoFixInterruptedDpkg "true";
      Unattended-Upgrade::Remove-Unused-Dependencies "true";
      Unattended-Upgrade::Automatic-Reboot "true";
      Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
      Unattended-Upgrade::Automatic-Reboot-Time "02:00";
  4. Create your new config file and paste in the above contents:
    sudo nano /etc/apt/apt.conf.d/52unattended-upgrades
    If you check the contents of the configuration directory ls -al /etc/apt/apt.conf.d/ you might notice all the files are prepended with numbers:

    $ ls -al /etc/apt/apt.conf.d/
    total 68
    drwxr-xr-x 2 root root 4096 Jun 19 00:16 .
    drwxr-xr-x 8 root root 4096 Jun 12 13:35 ..
    -rw-r--r-- 1 root root  630 Apr  8  2022 01autoremove
    -rw-r--r-- 1 root root   92 Apr  8  2022 01-vendor-ubuntu
    -rw-r--r-- 1 root root  129 Jan 20 18:50 10periodic
    -rw-r--r-- 1 root root  108 Jan 20 18:50 15update-stamp
    -rw-r--r-- 1 root root  311 Apr  6 13:48 20apt-esm-hook.conf
    -rw-r--r-- 1 root root   85 Jan 20 18:50 20archive
    -rw-r--r-- 1 root root   80 Feb 19  2021 20auto-upgrades
    -rw-r--r-- 1 root root 1040 Feb 17  2022 20packagekit
    -rw-r--r-- 1 root root  625 Dec  8  2021 50command-not-found
    -rw-r--r-- 1 root root 6133 Jun 13 19:31 50unattended-upgrades
    -rw-r--r-- 1 root root  182 Feb 20  2022 70debconf
    -rw-r--r-- 1 root root   50 Jun 12 13:35 99hetzner
    -rw-r--r-- 1 root root  338 May 16  2022 99needrestart
    -rw-r--r-- 1 root root  305 Jan 20 18:50 99update-notifier

    Configuration files like this are loaded in-order alphanumerically, the config file that comes higher in the alphanumeric rating will be loaded in first, and its settings will be overwritten by the config file that loads in next. By starting the name of our config with 52 we ensure our settings will load in after the default settings in 50unattended-upgrades overwriting them with the settings we’ve specified.

  5. Finally, restart unattended-upgrades and enable it to run on start-up:
    sudo systemctl restart unattended-upgrades
    sudo systemctl enable unattended-upgrades

Congratulations! Your server is now Secure!™Yaaaay! 🎉 🎊 🚀 🚀*

Further reading Link to heading

*This article is not security advice, your data is at risk, always consult a licensed security practitioner before storing data online. This article and its contents are provided with absolutely no warranty, use at your own risk.