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 toyes
- find
ChallengeResponseAuthentication
and set it toyes
- find
AuthenticationMethods
and set topublickey,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.
Or follow me on Twitter
I run an indie startup providing vulnerability scanning for your Ruby on Rails app.
It is free to use at the moment, and I am grateful for any feedback about it.If you would like to give it a spin, you can do it here: Vulnerability Scanning for your Ruby on Rails app!