Securing an nginx hosted website with SSL
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/eliteinformatiker/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/eliteinformatiker/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;
[...]