As soon as you provide some sort of password secured login on your website, you have to implement SSL/TLS to secure the password transmission. Plus, there is the general tendency nowadays to encrypt traffic which does not transport traffic, just to protect the privacy of your internet visitors. With encryption, eavesdroppers can only know which domain (hostname) and server (IP address) you connect to, but not which page and what kind of information from that host you are reading.

Setting up SSL/TLS for your server has become quite easy with the uprise of Let’s Encrypt, a CA that is trusted by major browsers and grants certificates automatically and for free. Some might say that Let’s Encrypt is even simpler than setting up self-signed certificates, because it can configure some web servers automatically. However, since I do not like an external component to change my web server configuration, I have to adjust the web server manually anyway, and then self-signed certificates are still one step simpler.

Self-signed certificate

Thus, the first step to understanding how to add SSL support to nginx, is creating a self-signed certificate. The certificate can be generated with OpenSSL:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt

This will create a certificate that is valid for one year together with an RSA key, and it skips password protection, which you usually do not want on a production web server.

For the most simple configuration, you just have to add the two files to your existing nginx server configuration.

server {
    [...]
    listen 443 ssl;

    ssl_certificate /etc/ssl/certs/sprakit-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/sprakit-selfsigned.key;

    [...]

Let’s say you already have a standard configuration without any encryption:

server {
    listen 80;
    server_name sprakit.com;
    charset utf-8;

    root /var/www/sprakit.com/public_html;
    index index.html index.htm;
}

Then with SSL it might look like this:

server {
    listen 80;
    listen 443 ssl;
    server_name sprakit.com;
    charset utf-8;

    ssl_certificate /etc/ssl/certs/sprakit-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/sprakit-selfsigned.key;

    root /var/www/sprakit.com/public_html;
    index index.html index.htm;
}

With this configuration you can already visit your website via https, if you ignore the self-signed certificate warning. A security test on this standard configuration results in a B score, if the trust problem is ignored. Cipher strength and protocol support are deemed good, only the key exchange is problematic, because the “server supports weak Diffie-Hellman (DH) key exchange parameters”.

These security issues can be solved by manually setting the required SSL settings on nginx. cipherli.st provides a set of secure configuration options for different web servers. These options can be put into a snippet file as digitalocean proposes. I did not enable HSTS, because it seems to be a more advanced topic for later and I also want to be able to serve my website over plain http - at least, until I am confident with my SSL skills.

# file: /etc/nginx/snippets/ssl-params.conf

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver $DNS-IP-1 $DNS-IP-2 valid=300s;
resolver_timeout 5s;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

This file can then be included into the server configuration with the include command:

server {
   listen 80;
   listen 443 ssl;

   [...]
   include snippets/ssl-params.conf;
   [...]
}

The SSL security test did not change much, the website still is scored a B-score. The remaining open issue is weak Diffie-Hellman key exchange, but this is simple to fix. New strong DHparams can be generated with openssl:

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Also add those to ssl-params.conf:

[...]
ssl_dhparam /etc/ssl/certs/dhparam.pem;

After this adjustment the website gets an A-score, if trust issues are not considered.

Using Let’s Encrypt for generating the certificate

To overcome the self-sign limitation, Let’s encrypt can be used. This service automatically signs certificates after an (also automatic) check if you are the domain owner. This works via a command line tool called certbot which initiates a certificate request, creates a file on your web server and if Let’s encrypt can retrieve and validate this file from your web server, it will issue the certificate and certbot will store it on your web server.

Since I make heavy use of docker on my server, I also put certbot into a custom docker image instead of installing it directly to the system. This can be done by creating a simple docker image. I specified certbot as entry point of the image:

FROM debian:latest
MAINTAINER Stefan Koch

RUN echo "deb http://ftp.debian.org/debian jessie-backports main" >> /etc/apt/sources.list \
    && apt-get update \
    && apt-get install -y certbot -t jessie-backports \
    && mkdir -p /etc/letsencrypt/live/sprakit.com

ENTRYPOINT ["certbot"]

The certificates and the static files for the website remain on the host, so those directories have to be mounted when starting the container.

docker run -v /home/brati/sprakit/static-website:/var/www/letsencrypt -v /etc/letsencrypt:/etc/letsencrypt --rm sprakit-certbot certonly --webroot -w /var/www/letsencrypt -d sprakit.com

To renew certificates you just have to launch a similar container again with the renew argument:

docker run -v /home/brati/sprakit/static-website:/var/www/letsencrypt -v /etc/letsencrypt:/etc/letsencrypt --rm sprakit-certbot renew

As a last step, you simply have to update the nginx configuration, and then the page should be served with a trusted SSL certificate:

[...]
ssl_certificate /etc/letsencrypt/live/sprakit.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sprakit.com/privkey.pem;
[...]