Securing a VPS

06 Feb 2024

37signals released a self-hosted chat app called Campfire. Opting for self-hosting entails the necessity of operating a Linux Virtual Private Server (VPS). Some people claim it’s not possible for a one-person or small team to run a VPS securely, but I don’t agree with that.
I have been running multiple virtual servers for more than a decade for my pet projects and managing a few for customers too. I am more of an offensive security professional, than defensive though, but unless you are hosting something with a high threat level, following this guide will make your server secure enough. And if you are in doubt, you can hire someone to configure your server once, and you should be good to go.

All the examples below are for Ubuntu 22.04, but they apply to any operating system.

You’ll need to access your server via SSH and by default, your provider might provide an SSH account with a password. But there is a more secure way to authenticate your SSH session,called key-based authentication. Depending on your hosting provider, your VPS might be built with your SSH key included already (Digital Ocean supports that), even then, it might be worth verifying that password-based logins are disabled. If you have SSH key-based authentication working, you can skip to the next paragraph.
I assume you already have an SSH key pair, but if you don’t, you can google how to generate one. Once that’s done, you need to transfer it to the server using the following command:

cat ~/.ssh/id_rsa.pub | ssh username@remote_host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Now that you have the public key on the server, you can log in without a password.

As a next step, let’s disable password-based SSH access. On your server, open the config file for sshd in the editor you are comfortable with (Vim and nano are installed on most servers by default):

sudo vim /etc/ssh/sshd_config

Search for PasswordAuthentication, set it to no, and make sure the line isn’t commented out. Save the file and restart sshd:

sudo service sshd restart

Having key-based authentication makes brute-forcing SSH really hard, but your key might get leaked or stolen, so adding a second factor is a good idea. Let’s see how can we use an authenticator app for 2FA. First, we’ll need to install the Google Authenticator package:

sudo apt-get install libpam-google-authenticator

Then run the app to configure it:

$ google-authenticator
Do you want authentication tokens to be time-based (y/n)

Answer yes, and read the QR code in your app of choice, then enter the generated code and make sure you save your recovery codes. Answer yes to the “Do you want me to update your “/root/.google_authenticator” file? (y/n)” question.
Set the rest of the config options based on your preference and threat level (rate-limiting is highly recommended though). Next, we’ll need to enable the 2FA for sshd by editing /etc/pam.d/sshd. Find the line @include common-auth, and comment it out, then append this line to the end of the file:

auth required pam_permit.so
auth required pam_google_authenticator.so nullok

Now open /etc/ssh/sshd_config and:

  • find KbdInteractiveAuthentication and set it to yes
  • find ChallengeResponseAuthentication and set it to yes
  • find AuthenticationMethods and set to publickey,keyboard-interactive

If you can’t find these settings, just append them as new lines to the end of the file. Restart sshd by running sudo service sshd restart. You’ll then have 2FA enabled for SSH on your server.

The next thing to do is to enable automated security updates on the server. To enable them, run sudo dpkg-reconfigure --priority=low unattended-upgrades and go through the interactive config creation process. After that, open /etc/apt/apt.conf.d/50unattended-upgrades in your editor and make sure that only the security origins are enabled(that’s how it should be by default).

The next step is to set up a firewall. The simplest to use is ufw, and it comes with Ubuntu by default. You just need to enable it:

sudo ufw enable

By default ufw blocks all incoming and allows all outgoing traffic, so let’s allow ssh before we get ourselves locked out:

sudo ufw allow ssh

We want to also allow connections to the webserver on port 80 and 443:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

If you host Campfire, you use Docker and it has some defaults that interferes with ufw, so we need to disable those. To do so, create a /etc/docker/daemon.json file with the following content:

{
    "iptables": false
}

You’ll also need to edit /etc/ufw/before.rules and add the following snippet before the COMMIT line in the file:

-A ufw-before-forward -i docker0 -j ACCEPT
-A ufw-before-forward -i testbr0 -j ACCEPT
-A ufw-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

And put this after the COMMIT line:

*nat
-A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE
COMMIT

Restart the docker service with sudo service docker restart and ufw with sudo ufw disable && sudo ufw enable. After that, your ufw rules should be applied as expected.

This basic setup should suffice as a starting point.

My next recommendation is to set up fail2ban and block the IP address of SSH brute-force bots. We need to install the package with sudo apt install fail2ban, and then we can configure it. We should create a “local” config by copying the default one: cd /etc/fail2ban && sudo cp jail.conf jail.local. Now open the jail.local file in your editor and search for the [sshd] string. Under that config section, enter these lines:

enabled = true
bantime  = 1w

This enables the SSH jail and makes fail2ban ban the IP address for 1 week in case of an SSH brute-force attempt. I like this aggressive setting because these bots won’t stop anyway. You can check default jails and enable the ones you’d like, and you can even create your own ones, but I won’t cover those in this article.

If you do the above, you are already well-protected against typical server attacks.

In addition, you should set up log rotation and backups. For backups, I recommend to configure an email or another form of notification of a successfull backup, that helps to notice when they stop working for whatever reason.

You could also take security to an even higher level by putting SSH behind a VPN, or moving it to a different port and honeyport the default one. Setting up Cloudflare and a WAF also wouldn’t hurt. For Cloudflare, make sure your DNS history doesn’t contain the real IP of the server and block direct traffic, allow only Cloudflare’s range on the firewall.

That’s it for now.

Hire me for a penetration test

Let's find the security holes before the bad guys do.

Or follow me on Twitter

Related posts