Securing a site using nginx

SslLab results for fajfar.com

How to secure your site running behind Nginx.

In the past securing a webpage meant spending money and buying a certificate. There was always the self signed alternative but that was not viable for public projects because of the invalid certificate warning.

Today this is not really a issue anymore because there are some alternatives that provide you with free signed certificates for personal and non-profit use.

So there is nothing holding you back from securing your website.

Acquiring a certificate

https://certbot.eff.org/

Before we can continue we will have to get a certificate to use with your website. In the past I have used starSSL which has since fallen from grace with the browser vendors, now I am at letsenrypt.org.

The nice thing about letsenrypt.org is that they provide you brilliant way of getting and renewing the certificates. If you have shell access to the server.

A way of doing it without shell access is also described, but I have not tried it.

Lets assume that you got certbot running and have retrieved a certificate for testdomain.com, using the default settings.

You should find the following files in the /etc/letsencrype/live/testdomain.com

1
2
me@server:/etc/letsencrypt/live/testdomain.com$ ls
cert.pem chain.pem fullchain.pem privkey.pem README

Congratulation you have just acquired the needed certificates for your website.

Using the certificates

Now we delve “deep” into the Nginx configuration files. If your installation of the server is remotely standard you should have a directory /etc/nginx/sites-available where all available sites are listed. In this folder find the configuration for testdomain.com and open with your editor of choice.

Update the server configuration following the steps:

  • Change the listening port from 80 to 443
  • Add the ssl flag to the listen directive
  • Add a ssl directive with th on flag
  • Add a ssl_certificate directive that points to you fully chained certificate
  • Add a ssl_certificate_key directive that points to your certificate key file

In the end you should have something like the following:

1
2
3
4
5
6
7
8
9
server {
listen 443 ssl;
server_name testdomain.com;
ssl on;
ssl_certificate /etc/letsencrypt/live/testdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/testdomain.com/privkey.pem;
# other server stuff
}

This is basically all you need to make your website use HTTPS to communicate with the world.

At this point our communication with the user is mostly secure. If we want that A+ rating we will have to do some additional things.

Protocol and Ciphers

1
2
3
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;

Protocol

By default all TLS versions are active. Because TLSv1.0 is now considered insecure it should be removed from the list of available TLS versions.

Disabling TLSv1.0 will prevent older clients from connecting to your website

A good starting point for this is the blog post from PCI security standards.

Ciphers

Honestly I do not fully understand what is happening here but i get the concepts and reason for doing it.

After reading about Forward secrecy and then good guide on Server site TLS I opted for the medium cipher setting.

Diffie Hellman

Diffie Hellman is a method to exchange cryptographic keys over an unsecured channel. More on this can be found in this wikipedia article about Diffie–Hellman key exchange.

After trying to understand all that I just defaulted to longer D-H parameters are better but there has to be an upper bound! Currently the recommended D-H parameter length is 2048 so after reading these to blogs:

I would suggest and personally use the 4096 bit variant. In combination with the ssl_session_cache all negative impact of the longer parameters should be negated.

So how do we generate them?

Using openssl this is just a question of time.

1
openssl dhparam -out dhparam.pem 4096

This command will let you enjoy this screen for quite some time:

generating D-H parameters

After 45 minutes I just left it to itself and went to sleep. So expect to spend some time with this.

There are alternatives.

Sites like https://2ton.com.au/dhtool/ provide you with pre-calculated D-H parameters.

Lighten the load

The easiest way to lighten the load is to reuse all of that math being done. This is luckily easily done with the ssl_session_cache directive.

1
ssl_session_cache shared:SSL:10m;

This will create a shared cache of 10Mb. According to the internet 1Mb should be good for 4K sessions.

What exactly this means is beautifully described at https://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077

And a benchmark can be found at https://www.peterbe.com/plog/ssl_session_cache-ab

The benchmark is also the source of the configuration sample :)

Putting it all together

To not cause confusion, here is the complete server configuration for testdomain.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 443 ssl;
server_name testdomain.com;
ssl on;
ssl_certificate /etc/letsencrypt/live/testdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/testdoamain.com/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_dhparam /etc/ssl/dhparam.pem;
add_header Strict-Transport-Security "max-age=31536000";
location / {
root /var/www/;
index index.html;
try_files $uri $uri/ /index.html;
}
}

Redirecting HTTP -> HTTPS

Now you have a nice little HTTPS configuration going. It gets an A+ ratting but you still have to get your traffic from the unsecured “channel” to the secured one.

I suggest to do that with a 301 Moved Permanently so that each browser only makes the mistake once :)

Here is the server configuration for testdomain.com

1
2
3
4
5
server {
listen 80;
server_name testdomain.com www.testdomain.com;
return 301 https://testdomain.com$request_uri;
}