How I secure a cloud VPS.

Steps detailing how I as a front-end dev secure my cloud VPS - SSH configuration, NGINX, and more!

I do not profess to know everything when it comes to server administration. After all, I am a front-end developer who simply enjoys messing with servers. There’s a certain kind of novelty to it. 😀

I thought it would be interesting to write about what I usually do to secure my VPS, and take inputs from the community on how to better this – even if for a hobby.

The post is primarily for my own reference, but an average Linux user might also find it useful as a bird’s eye view of where to start.

Some commands might be specific to Debian/Ubuntu as that’s what I use on my servers.

Let’s begin.

Create the VPS with an SSH key.

Any cloud VPS provider will let you add your SSH key(s) to your account settings and then allow you to select which key(s) to bootstrap the server’s root account with when creating it using their UI and API.

If you don’t use an SSH key, you will typically get an email with a plain-text password to login to the root account with.

Use only public-key authentication.

Using key authentication is an excellent step to strong security. Combine it with disabling password logins and you’re reasonably secure already.

This is quite easy to configure in the /etc/ssh/sshd_config file. Make the following two changes. These might be commented out so it might simply be a case of uncommenting them.

PasswordAuthentication: no
PubkeyAuthentication: yes

That’s it. You should restart the SSH daemon now: sudo systemctl restart ssh.

Limit algorithms allowed.

Go one step further and disable old or less secure crypto algorithms. Thanks, Phil!

I prefer ed25519.

Create a new user with a strong password.

A sysadmin might have noticed I did not suggest disabling root login in the previous step.

Well, you do need a regular user account with sudo privileges that works or you end up locking yourself out. Since we’ve already disabled password authentication, it’s a good idea to add your SSH public key manually to the new account.

To do all the above:

  • Create a new user: adduser purple
  • Add to sudo group: usermod -aG sudo purple
  • Add an SSH public key for login. You have two ways to go about this, so I’ll detail them next.

Add an SSH public key for login

I prefer to simply copy it.

On my local machine:

xcopy -sel copy ~/.ssh/

On the server, paste into an authorized_keys file under your user’s .ssh directory. No extra spaces anywhere, no line breaks. Save the changes and you’re done. 🙂

If you’d like to avoid this minor headache, you can also use ssh-copy-id command line utility from your computer but must keep password authentication enabled until this is sorted out.

ssh-copy-id -i ~/.ssh/ <username>@<host>

The -i flag points to the identity file.

Disable root login once user login successful.

Once you’re able to login using your SSH key on your account, open the /etc/ssh/sshd_config file again and disable root login:

PermitRootLogin: no

And finally, restart the ssh daemon.

Set up a firewall and deny all incoming requests.

ufw is a very easy-to-use solution and available on Ubuntu’s repos.

sudo apt install ufw

You can deny all incoming requests to start with. Based on what you want to use the server for, you can open those ports specifically later on. We already know we’ll need the SSH port (22) so we make sure to add it to the firewall rules before enabling it.

You’ll need to use sudo now since you’re no longer operating as root.

sudo ufw deny incoming
sudo ufw allow ssh
sudo ufw enable

fail2ban is a simple utility to block pesky brute-force login attempts. It temporarily bans an IP if it finds that a bot/human behind it is trying to break in. The time of the ban incrementally increases with each ban.

Set up fail2ban.

If you use the default SSH port (22) to log in to your server, this is absolutely essential. I find that within minutes of creating a cloud server, a steady stream of login attempts begin.

Ubuntu has fail2ban available in its repos. Install it, configure your jails, and enable it.

Upgrade system packages.

There’s nothing fancy to do here. Just upgrade your system packages and any user-installed packages regularly.

Port knocking

I had read about port knocking on Mike Stone’s blog post. At first, it wasn’t quite clear to me why someone might want to use it or what it really is. While researching whether it’s a good idea to change the default SSH port (more on that below), I came across the idea once again and decided to read up more.

Essentially, you send a series of requests to pre-defined ports, which then instructs the firewall to open the SSH port for this user’s IP address. You can then lock the SSH port again by sending the requests in a reverse order after you logout of the server.

Keep in mind this is just one more layer to your security setup. Relying on this alone is not a good idea. How-To Geek has a pretty decent article on how to configure this.

Change the default SSH port?

This is a controversial subject. You’ll find it has pros, but also cons. Some say it’s bad, some say you can use it as another layer in a multi-layered setup.

That said, I probably have no recommendation to give out here. Please do your own research. 😉

Optionally, depending on your use case, there’s more you can do.

Let’s say you are setting up a web server using NGINX — the following applies:

Hide technologies

NGINX version.

Linode’s guide on setting up NGINX is well written and I should add, well researched! I had been following the practice of making symlinks between the sites_available and sites_enabled directories up until now. I was surprised to know that’s actually Apache’s workflow.

The guide also details how to disable the server_tokens in your NGINX config. Doing so protects you against version-specific attacks.

Let’s check the HTTP response headers in two different scenarios by using curl:

curl -I <>

Before disabling server_tokens:

Server: nginx/1.17.10 (Ubuntu)
Date: Sun, 28 Jun 2020 05:53:06 GMT
Content-Type: text/html
Content-Length: 179
Connection: keep-alive

After disabling server_tokens:

Server: nginx
Date: Sun, 28 Jun 2020 05:53:52 GMT
Content-Type: text/html
Content-Length: 179
Connection: keep-alive


Update your Caddyfile host to say: {
    header -x-powered-by
    header -server

This should cover more than just Caddy (for example, Apache and PHP).

Follow Mozilla Observatory’s recommendations.

Mozilla has an excellent and a very enjoyable page on Web Security.

You can test your current server config by heading to the Observatory and working your way up based on their recommendations and test results. You get to refresh a test every five minutes. Aim for an ‘A‘ rating.

There’s a ton of stuff to learn:

  • setting up a Content Security Policy (CSP),
  • disabling frame-embeds of your site,
  • instructing the browser not to detect a content-type on its own,
  • setting up a referrer policy to protect your visitor’s privacy,
  • configuring Cross Origin Resource Sharing (CORS),
  • configuring HTTP to HTTPS redirections on both NGINX and Apache,
  • enabling HSTS,
  • and more!

Seriously, even if all you have the time for is reading through the page, do it. It’s well worth it.

Wrapping Up

So what do you think? Is there anything I can add to this list? What can I do better if it’s already on the list? 🙂

Discuss on Fosstodon, or write me an email. Last updated on 14 September 2021.


Comment via email.