Blue locked door
Photo by Maxim Zhgulev on Unsplash

I do not pro­fess to know every­thing when it comes to server ad­min­is­tra­tion. After all, I am a front-end de­vel­oper who sim­ply en­joys mess­ing with servers. There’s a cer­tain kind of nov­elty to it. 😀

I thought it would be in­ter­est­ing to write about what I usu­ally do to se­cure my VPS, and take in­puts from the com­mu­nity on how to bet­ter this - even if for a hobby.

The post is pri­mar­ily for my own ref­er­ence, but an av­er­age Linux user might also find it use­ful as a bird’s eye view of where to start.

Some com­mands might be spe­cific to Debian/Ubuntu as that’s what I use on my servers.

Let’s be­gin.

Securing a Cloud VPS

Create the VPS with an SSH key.

Any cloud VPS provider will let you add your SSH key(s) to your ac­count set­tings and then al­low you to se­lect which key(s) to boot­strap the server’s root ac­count with when cre­at­ing it us­ing their UI and API.

If you don’t use an SSH key, you will typ­i­cally get an email with a plain-text pass­word to lo­gin to the root ac­count with.

Use only pub­lic-key au­then­ti­ca­tion.

Using key au­then­ti­ca­tion is an ex­cel­lent step to strong se­cu­rity. Combine it with dis­abling pass­word lo­gins and you’re rea­son­ably se­cure al­ready.

This is quite easy to con­fig­ure in the /etc/ssh/sshd_config file. Make the fol­low­ing two changes. These might be com­mented out so it might sim­ply be a case of un­com­ment­ing them.

PasswordAuthentication: no
PubkeyAuthentication: yes

That’s it. You should restart the SSH dae­mon now: sudo systemctl restart ssh.

Create a new user with a strong pass­word.

A sysad­min might have no­ticed I did not sug­gest dis­abling root lo­gin in the pre­vi­ous step.

Well, you do need a reg­u­lar user ac­count with sudo priv­i­leges that works or you end up lock­ing your­self out. Since we’ve al­ready dis­abled pass­word au­then­ti­ca­tion, it’s a good idea to add your SSH pub­lic key man­u­ally to the new ac­count.

To do all the above:

  • Create a new user: adduser purple
  • Add to sudo group: usermod -aG sudo purple
  • Add an SSH pub­lic key for lo­gin. You have two ways to go about this, so I’ll de­tail them next.

Add an SSH pub­lic key for lo­gin

I pre­fer to sim­ply copy it.

On my lo­cal ma­chine:

xcopy -sel copy ~/.ssh/

On the server, paste into an authorized_keys file un­der your user’s .ssh di­rec­tory. No ex­tra spaces any­where, no line breaks. Save the changes and you’re done. 🙂

If you’d like to avoid this mi­nor headache, you can also use ssh-copy-id com­mand line util­ity from your com­puter but must keep pass­word au­then­ti­ca­tion en­abled un­til this is sorted out.

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

The -i flag points to the iden­tity file.

Disable root lo­gin once user lo­gin suc­cess­ful.

Once you’re able to lo­gin us­ing your SSH key on your ac­count, open the /etc/ssh/sshd_config file again and dis­able root lo­gin:

PermitRootLogin: no

And fi­nally, restart the ssh dae­mon.

Set up a fire­wall and deny all in­com­ing re­quests.

ufw is a very easy-to-use so­lu­tion and avail­able on Ubuntu’s re­pos.

sudo apt install ufw

You can deny all in­com­ing re­quests to start with. Based on what you want to use the server for, you can open those ports specif­i­cally later on. We al­ready know we’ll need the SSH port (22) so we make sure to add it to the fire­wall rules be­fore en­abling it.

You’ll need to use sudo now since you’re no longer op­er­at­ing as root.

sudo ufw deny incoming
sudo ufw allow ssh
sudo ufw enable

Set up fail2ban.

fail2ban is a sim­ple util­ity to block pesky brute-force lo­gin at­tempts. It tem­porar­ily bans an IP if it finds that a bot/​hu­man be­hind it is try­ing to break in. The time of the ban in­cre­men­tally in­creases with each ban.

If you use the de­fault SSH port (22) to log in to your server, this is ab­solutely es­sen­tial. I find that within min­utes of cre­at­ing a cloud server, a steady stream of lo­gin at­tempts be­gin.

Ubuntu has fail2ban avail­able in its re­pos. Install it, con­fig­ure your jails, and en­able it.

Upgrade sys­tem pack­ages.

There’s noth­ing fancy to do here. Just up­grade your sys­tem pack­ages and any user-in­stalled pack­ages reg­u­larly.

Port knock­ing

I had read about port knock­ing on Mike Stone’s blog post. At first, it was­n’t quite clear to me why some­one might want to use it or what it re­ally is. While re­search­ing whether it’s a good idea to change the de­fault SSH port (more on that be­low), I came across the idea once again and de­cided to read up more.

Essentially, you send a se­ries of re­quests to pre-de­fined ports, which then in­structs the fire­wall to open the SSH port for this user’s IP ad­dress. You can then lock the SSH port again by send­ing the re­quests in a re­verse or­der af­ter you lo­gout of the server.

Keep in mind this is just one more layer to your se­cu­rity setup. Relying on this alone is not a good idea. How-To Geek has a pretty de­cent ar­ti­cle on how to con­fig­ure this.

Change the de­fault SSH port?

This is a con­tro­ver­sial sub­ject. You’ll find it has pros, but also cons. Some say it’s bad, some say you can use it as an­other layer in a multi-lay­ered setup.

That said, I prob­a­bly have no rec­om­men­da­tion to give out here. Please do your own re­search. 😉

Optionally, de­pend­ing on your use case, there’s more you can do. Let’s say you are set­ting up a web server us­ing NGINX - the fol­low­ing ap­plies:

Hide NGINX ver­sion from server re­sponse head­ers.

Linode’s guide on set­ting up NGINX is well writ­ten and I should add, well re­searched! I had been fol­low­ing the prac­tice of mak­ing sym­links be­tween the sites_available and sites_enabled di­rec­to­ries up un­til now. I was sur­prised to know that’s ac­tu­ally Apache’s work­flow.

The guide also de­tails how to dis­able the server_tokens in your NGINX con­fig. Doing so pro­tects you against ver­sion-spe­cific at­tacks.

Let’s check the HTTP re­sponse head­ers in two dif­fer­ent sce­nar­ios by us­ing curl:

curl -i <>

Before dis­abling 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 dis­abling server_tokens:

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

Follow Mozilla Observatory’s rec­om­men­da­tions.

Mozilla has an ex­cel­lent and a very en­joy­able page on Web Security.

You can test your cur­rent server con­fig by head­ing to the Observatory and work­ing your way up based on their rec­om­men­da­tions and test re­sults. You get to re­fresh a test every five min­utes. Aim for an A rat­ing.

There’s a ton of stuff to learn:

  • set­ting up a Content Security Policy (CSP),
  • dis­abling frame-em­beds of your site,
  • in­struct­ing the browser not to de­tect a con­tent-type on its own,
  • set­ting up a re­fer­rer pol­icy to pro­tect your vis­i­tor’s pri­vacy,
  • con­fig­ur­ing Cross Origin Resource Sharing (CORS),
  • con­fig­ur­ing HTTP to HTTPS redi­rec­tions on both NGINX and Apache,
  • en­abling HSTS,
  • and more!

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

Wrapping Up

So what do you think? Is there any­thing I can add to this list? What can I do bet­ter if it’s al­ready on the list? 🙂