Setting up SSL for Ghost with LetsEncrypt and Certbot over NGINX

With Chrome heralding the triumphant end of HTTP as we know it, I thought I should at least get with the program and get the blog a valid certificate. Luckily nowadays the process is extremely painless and will take you less than 10 minutes. The Ghost blogging platform has instructions on how to set up SSL for self-hosted sites, but there were some gotchas I ran into and decided to document a step-by-step process for anyone else who might need this in the future. Note that this does assume you have shell access to your hosting service.

Getting Started

My server is running on a DigitalOcean droplet on Ubuntu 14.04.4 LTS, but any distro configuration should be easily adaptable. Ghost also uses NGINX to serve requests and most of the configuration files and paths should be in standard locations. However if they are not, I will include hints on where you should be looking to find what you are looking for.

We are going to be getting our certificates provided by LetsEncrypt, an incredible org that provides an open and free Certificate Authority for anybody to use. Using Certbot will simplify this process of registering and obtaining certs and will also be used to automatically renew them as well.

Installing Certbot

Certbot is maintained in a PPA, the following commands should be easy enough to install it:

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx 

Setting up NGINX

Before going any further, we need to set up our NGINX installation to serve our well-known directory so that we can validate our acme-challenge. Otherwise, when we try to run certbot we will get an error because Ghost does not serve up files from the default root folder at /var/www/ghost. This is the path that we will be using, however to find your correct Ghost installation root, it will be the folder that contains your content, core, config.js and index.js files as well the npm module and packaging apparatus.

Find your NGINX conf file that manages the Ghost service. For me, this was found at /etc/nginx/sites-enabled/ghost. In general, you will know it is the proper configuration since it will contain a reference to your domain name and look something like this:

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name cplusperks.com # Replace with your domain

    root /usr/share/nginx/html;
    index index.html index.htm;

    client_max_body_size 10G;

    location / {
        proxy_pass http://localhost:2368;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    } 

    location ~ ^/.well-known {
        root /var/www/ghost;
    }
}

You will notice an additional location setting added to our configuration that looks like:

    location ~ ^/.well-known {
        root /var/www/ghost; # Your path should be here
    }

This will allow LetsEncrypt to validate our certificates when we register them. By default this route would not be served and return a 404, however this will tell NGINX to serve requests looking for our well-known folder where to look.

Make sure to refresh our NGINX instance by running sudo service nginx restart!

Running Certbot

Even though Certbot now comes with an NGINX plugin, I didn't want to chance automated configuration file rewrite and rules breaking something. Instead we will request the certificates and manually update our NGINX conf again to enable SSL. Run the following command (replacing paths and domains with yours):

sudo certbot --webroot certonly -w /var/www/ghost -d cplusperks.com -d www.cplusperks.com

Follow the onscreen instructions. Eventually LetsEncrypt will place your certificates in the following location: /etc/letsencrypt/live/<yourdomain>.

Reconfiguring NGINX

We now need to re-edit our conf file to use our freshly provided certs and to redirect HTTP traffic requests over SSL. They are a couple ways to do this, but the following configuration should contain the basics of what settings need to be turned on and set (and should work out of the box).

upstream ghost {  
    server localhost:2368;
}

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name cplusperks.com www.cplusperks.com; # Replace with your domain

    rewrite ^ https://$http_host$request_uri? permanent;
}

server {

    listen 443 ssl default_server;
    ssl_certificate /etc/letsencrypt/live/cplusperks.com/fullchain.pem; # Replace with your domain
    ssl_certificate_key /etc/letsencrypt/live/cplusperks.com/privkey.pem; # Replace with your domain

    server_name cplusperks.com www.cplusperks.com; # Replace with your domain


    root /usr/share/nginx/html;
    index index.html index.htm;

    client_max_body_size 10G;

    location / {
        #proxy_pass http://localhost:2368;
        proxy_pass http://ghost;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    } 

    location ~ ^/.well-known {
        root /var/www/ghost;
    }

    fastcgi_param HTTPS on;
    fastcgi_param HTTP_SCHEME https;

}

Throw a sudo service restart nginx and you should see the reassuring green SSL indicator in your url (although to be fair, you can't always trust what you see)

Auto-renewal Cron Job

Of course one thing to keep in mind once this is all setup is that certificates need to be periodically refreshed. Luckily cerbot places an automatic cronjob for us at /etc/cron.d/certbot. We have to make sure we edit it so that our NGINX server is reloaded to pick up the refreshed cert, so lets add some pre and post hooks.

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --pre-hook "service nginx stop" --post-hook "service nginx start"  

Congrats!

That was pretty painless wasn't it?

Show Comments