Setting up HTTPS: the quest for a solid security config

I bought a shared hosting plan that lasted me almost a year before I decided to get something more powerful. Today I’ll tell you what I did to get a respectable HTTPS configuration in my recently deployed cloud server.

Motivation

Having a shared hosting plan is great. You log in to cPanel and you have tons of menu options, a search bar to look for settings and you can call yourself a certified system administrator from day 1…
On the other side you can only have static content, a bundle of common software options and your performance will depend on the server you’re hosted in: if it has poor specs (or too many clients in it), you’d see your blog, forum or e-commerce store drag with lag beyond what any user would bare.
Besides my static content, I had my own little blog, but even that was too much to ask. I was accessing my blog’s admin page via HTTP, sending my (weakest) password in clear text over the wire. I wanted to add security and only serve content via HTTPS. To do that, my monthly fee would rise from 2€/month to 6€/month , with no added performance benefits. This was the final nail in the coffin for my would-be server. I needed a real server, no shackles included.
Fast forward a couple of weeks and I’m currently very happy using DigitalOcean’s cheapest droplet.

Planning ahead for the move

In the last months I had increasing interest for NodeJS. I wanted to build a website with it, and I knew I wouldn’t stop thinking about it until I actually had it. Suffice to say that well before my move to a cloud server, I already knew what backend would serve my content. But there were other concerns! What about speed? Node is all about speed, but there’s no built-in cache module. No listening on port 80 or 443 out of the box, not without running it as root. I searched for a better way and found that I could and should leverage on a bulletproof, battle-hardened web-server like Apache or NGINX to have requests cached and even handle SSL for me. In turn, this would make my actual server code ever so simple. But which one to choose?
I had worked with Apache before, but I had heard of NGINX as a more scalable option, with excellent caching capabilities. Having searched for configuration files, I found them to be intuitive enough, and the choice was made. As soon as I got my Droplet and a server certificate I could start working on making everything stick together.

More on the case of NGINX

Node has a HTTPS module. There are ways to run a Node server on port 443. But does it really make sense to do so? This is my first server, and I might/will use it for more than one project. So how would I ever run multiple servers on standard web-server ports?
And what about cache? If I had 10 node servers running, I’d have some serious code plumbing work in order to cache requests for all servers. Don’t even get me started on SSL handling.
I think you get my point. Just Node is probably not the best way to deploy things: NGINX can act as a reverse proxy.
Think about it. SSL settings and cache are all handled for me. I start my node server(s), and set them up in user available ports. I then set config files for my website and redirect all requests to the port on which the node server is running. This way I can have multiple sites configured, and multiple servers per site (e.g. requests to goncalotomas.com/api-for-a-random-project might get redirected to the node server running on port 9090).

NGINX setup

So here we are, setting up website configuration files with a server certificate in hand. Let’s see what an initial version would look like:

server {
# HTTP B gone
listen 80;
redirect 301 https://$host$request_uri
}
server {
# SSL settings
listen 443 ssl;
server_name goncalotomas.com;
ssl_certificate /path/to/your/ca-bundle/file
ssl_certificate_key /path/to/your/private/key
  location / {
proxy_pass http://localhost:9090;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

Remember kids: HTTPS only. It’s the safest way to go. That’s what the first server entry is doing there: redirecting all standard HTTP requests to HTTPS.
Note that this is a simple config file I copied from the NGINX docs. That doesn’t necessarily mean you get the best HTTPS config just using these settings. In fact, if you were to visit securityheaders.io with our current configuration, you’d see a big fat F… because we still don’t have any security headers. But as with any security topic: one should not fiddle with what one does not understand. So I will give you a one-liner definition for the security headers we will be using:

Content Security Policy (CSP): Basically a way of whitelisting content sources, including images, scripts and styles. Anything you add to your pages that comes form a source not listed in the CSP will not be executed/displayed.
HTTP Strict-Transport-Security (HSTS): A way to tell browsers to keep using HTTPS connections to speak with your server.
X-XSS-Protection: Force enabling a browser’s XSS filter, just in case a user might have disabled it.
X-Content-Type-Options: Making sure that MIME types are not inferred through their content. If the MIME content-type is “text/html”, it will always be displayed as “text/html”.
X-Frame-Options: Whitelist of sources where your content can be iframed in, essentially offering protection against clickjacking.
HTTP Public Key Pinning (HPKP): List of hashes containing public keys used in your certificates. Useful for protecting against rogue certificate attacks.

I need to warn you about HPKP. The current specification document strongly recommends having a backup key and listing that public key as well. If your certificate gets compromised, you still have your backup.
It is also recommended to keep the backup key offsite and offline, because if you keep both in the same server and if an attacker is able to compromise one key, he will find the backup key in no time. I’m warning you because if you keep your backup in the same place or if you don’t have a backup and your certificate key is compromised, then adding a new cert might make your visitors see nasty warnings in their browser, since you did not pin the new key in the header. That’s the last thing you want!

I could list all values for each individual header, but this post is already huge. When you’re querying your own website at securityheaders.io, it tells you what you should have in your missing server headers. It shouldn’t take you more than 30 minutes to set them up. 🙂

But wait… There’s more!

There are more things you should keep track of. Firstly, read this wonderful article. And this one. They tell you just how security headers are nothing but a bunch of bullshit if you don’t watch out for your other settings. A successful attacker seldom uses complex attack vectors — he/she will choose the easiest path. If you’re going to work or be responsible for security, you need to put in some extra effort. Enter the infamous SSL settings.

I have found two main issues with SSL settings: Diffie-Hellman protocol parameters and the ciphersuites that your server can work with. Let’s talk about both:

Diffie-Hellman parameters

There’s a reasonable chance that this website explains this issue way better than I do. But I’ll try anyways because I’m that guy that tries to explain everything.
In short, there are several groups of 1024-bit prime numbers that are used in the Diffie-Hellman key exchange protocol by millions. Would anything tempt the NSA more than precomputing and brute force these numbers so they could be able to eavesdrop communications that everyone thought were safe?
What you can do to solve this issue is to use a stronger, out of the mainstream group of Diffie-Hellman parameters in order to make sure your server’s key exchange protocol is as safe as can be. I trust your Googling skills to find how to make it work.

Accepted ciphersuites

This one is a tricky one because people tend to overlook important security news. Security vulnerabilities get discovered now and then, and it’s your job to make sure that no vulnerable configuration is accepted by your server. That is done by maintaining an updated list of accepted ciphersuites. At the time of writing this, here is my “ciphersuite String”:

ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA

Again, it is your job to keep this list updated: allow for newer protocols to come and block old or vulnerable ones. Do it right and it will save your bacon. I like bacon, so I’m going to keep my list updated.

Outcome

After this unbelievably long post and all these complicated notions and settings, what you should get is a top grade in both securityheaders.io and in ssltest. This means that you have put a good effort in protecting your users’ data across the wire. Some users, especially developers, value this very much. If you’re selling a service or product and you have not done this work, one might think where else you might have saved time and money… 🤔

Bonus: migration headaches

In the beginning I said I had a shared hosting and how it was so easy to create and manage email accounts, add DNS settings, mess with databases, etc… Big boys have to do everything by hand I guess.. And for your pleasure and amusement, here I am writing about the nightmare that was understanding and migrating everything correctly.

Firstly, the email. My hosting had 2GB disk space, for website files and email (freaking scrooges!). After a long session of Googling I found my lord and saviour, Zoho. They have a free hosted email package with 5GB, and all you have to do is follow a migration wizard that basically clones your old email boxes so you don’t lose anything, and it gives you precise and intuitive instructions on how to change the DNS settings in order to make for a smoother journey. If it took me 10 days to find the courage to start, it took me an hour to completely migrate every account. They are awesome.

Then came the DNS settings. I had already deployed NGINX, set up shop with my Node server and it was just a matter of switching the A address to my new server’s IP address. I don’t really know if this is standard procedure, but my website’s DNS server was on the cPanel service I wanted to cancel. I had to go to my domain settings (this time out of cPanel) and set the domain DNS server to be my host’s primary DNS server (different from the cPanel server). After that was done, several agonising hours passed until DNS magic happened and goncalotomas.com pointed to its new address.

There is still much to be done. Performance optimisations, custom 404 pages, add true Material Design looks, and there’s a good chance I’ll never be happy with what I have. But I’m glad I had the will and patience to learn about these security settings. 😴

Thanks for sticking with me ‘till the end. You’re either a loyal friend or clearly someone with nothing better to do. Either way, you’re awesome. Subscribe to my feed here in Medium so you get notifications!

GT

Show your support

Clapping shows how much you appreciated Gonçalo Tomás’s story.