17 May 2016

Nginx HTTPS with Let's Encrypt and Redmine

Nginx HTTPS with Let's Encrypt and Redmine

These instructions are based on Ubuntu and use the Let's Encrypt Certificate Authority. With Let's Encrypt, self-signed certificates on a public Linux server should be ancient history

These instructions assume the following;
  • Ubuntu 14.04 LTS - a mature Ubuntu version with long term support 
  • Nginx - the web server within which Redmine runs 
  • A running Redmine system. Details of how to setup Redmine on Nginx can be found here

Getting the certificates from Let's Encrypt

As the Let's Encrypt system has just left beta and is still new, using their instructions are best. For example, the certbot command was introduced this week, replacing the "old" letsencrypt-auto command. However, here is how things work as of today.

Install git and download the Let's Encrypt client. On first run, the client will install dependencies and update itself. 
apt-get install git
git clone https://github.com/certbot/certbot
cd certbot
# Running for the first time will install needed dependencies.
./certbot-auto --help
After everything is installed, the certificate can be requested and downloaded all in one step. Because the Let's Encrypt client uses port 80 for verification of ownership, anything running on that port needs to be temporarily turned off -- in this case nginx.
service nginx stop
./certbot-auto certonly --standalone -d redmine.yourdomain.com
service nginx start
Note where the certificates are installed. For example:


As said, these instructions may change at any time. What follows is the standard method for setting up SSL in general for nginx and Redmine. 

Set up the certificates

Generate a set of DH parameters for the Diffie-Hellman handshake. Please don't ask what this is for... just know it is a good thing. 
mkdir /etc/nginx/ssl
chmod 700 /etc/nginx/ssl
openssl dhparam 2048 -out /etc/nginx/ssl/dh2048.pem
In /etc/nginx/sites-available create a new file or update the "default" file.

Modify the listen 443 ssl entry.   
server_name redmine.yourdomain.com;
ssl_dhparam    /etc/nginx/ssl/dh2048.pem;
ssl_certificate    /etc/letsencrypt/live/redmine.yourdomain.com/fullchain.pem;
ssl_certificate_key    /etc/letsencrypt/live/redmine.yourdomain.com/privkey.pem; 
NOTE: The ssl_certificate should be set to the fullchain.pem, which includes both the server certificate and the intermediate CA certificates. Setting this just to the cert.pem will work in Chrome, but give a "SEC ERROR UNKNOWN ISSUER" error in Firefox.

Add the redmine details (same as used for port 80). 

root /var/data/redmine/public/;
passenger_enabled on;
client_max_body_size    10m;
Comment out or delete any references to 404, if they exist.
#passenger_spawn_method direct;
#location / {
#       try_files $uri $uri/ =404;
If this file was newly created, instead of the "default" file, add a soft link to the new file in /etc/nginx/sites-enabled.
Restart nginx and everything should come up on port 443 with a valid certificate. 

Lock down the SSL installation 

Now that the site is functional, the configuration should be locked down.
# limit HTTPS to the most recent protocol
ssl_protocols     TLSv1.2;
# define the list of ciphers used
ssl_prefer_server_ciphers on;
# this list is always in flux, but the list below works at time of writing
#set the cache session, 10 minutes is the minimum
ssl_session_cache shared:SSL:10m;
In the end, the the file should look something like this: 

server {
        listen 443 ssl;
        server_name redmine.yourdomain.com;
        ssl_dhparam    /etc/nginx/easy-rsa/keys/dh2048.pem;
        ssl_certificate    /etc/letsencrypt/live/redmine.yourdomain.com/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/redmine.yourdomain.com/privkey.pem;
        ssl_protocols    TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers    EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
        ssl_session_cache shared:SSL:10m;
        root /var/data/redmine/public/;
        passenger_enabled on;
        client_max_body_size    10m;
        #passenger_spawn_method direct;
        #location / {
        #       try_files $uri $uri/ =404;

Restart nginx and if desired, run a certificate security test against the system. 

Figure: Qualsys scan

Automated renewal

Because the Let's Encrypt philosophy is full automation, the certificates need to be renewed every 90 days. In cron, setup a renewal job to be run every week. For example, at 3:05 AM every Saturday: 
05 03 * * 6 /yourpath/cert-renew.sh
This job needs root privileges. Once the certificate gets into the time window where it can be renewed, cron will do the update "magically".
install-path/certbot-auto renew --standalone --pre-hook "service nginx stop" --post-hook "service nginx start"
This can be tested by doing a dry-run
install-path/certbot/certbot-auto renew --dry-run --standalone --pre-hook "service nginx stop" --post-hook "service nginx start"

Links of interest 

Renewal script for Let's Encrypt