Setting up SSL with Jekyll using LetsEncrypt

LetsEncrypt is a CA which allows you to register for free SSL certificates for use on your website. The following are the steps I took to register for a SSL certificate for my current website.

Step 1: Getting letsencrypt

The first step is to clone the letsencrypt repo from github. It includes the client which you will need to generate the certificates:

1 cd letsencrypt
2 
3 sudo ./letsencrypt-auto certonly -a manual --rsa-key-size 4096 -d mysite.com -d www.mysite.com

The above command essentially reads to generate SSL for my domains cheeyeo.uk and www.cheeyeo.uk with a rsa key size of 4096. The certonly flag means to use only the “standalone” or “webroot” plugins to request for a certificate. The letsencrypt client can integrate with Apache and Nginx servers directly but that is outside the scope of the current article.

The sudo flag is required as it writes to certain system directories such as /etc/letsencrypt. Without it, I kept receiving python missing module errors and running as root seems to resolve it.

Once the above completes, you will be prompted with a screen asking you for permission to log your IP address.

Once confirmed, you will be presented with instructions on creating a specific url on your server that needs to return a nounce or token.

This is usually in the form of:

1 Make sure your web server displays the following content at
2 http://www.yourdomain.com/.well-known/acme-challenge/weoEFKS-aasksdSKCKEIFIXCNKSKQwa3d35ds30_sDKIS before continuing:
3 
4 weoEFKS-aasksdSKCKEIFIXCNKSKQwa3d35ds30_sDKIS.Rso39djaklj3sdlkjckxmsne3a
5 Content-Type header MUST be set to text/plain.

(The above is just an example and you will see a different output!)

For Jekyll, since we are serving static resources from the _site directory, this means that the folder will always be wiped on each deploy.

To workaround this, I added the keep_files directive to my _config.yml file:

1 # _config.yml
2 ....
3 
4 keep_files: [
5   '.well-known/acme-challenge'
6 ]
7 
8 ....

Then we create the directories and the required file:

1 cd _site/.well-known/acme-challenge
2 
3 echo -n "weoEFKS-aasksdSKCKEIFIXCNKSKQwa3d35ds30_sDKIS.Rso39djaklj3sdlkjckxmsne3a" > "weoEFKS-aasksdSKCKEIFIXCNKSKQwa3d35ds30_sDKIS"

Next, I added the _site/.well-known subdirectory into a git commit, ready to be pushed to the Heroku server. Once that is done, check that you can access it manuall by visiting the url above directly.

Please also note that you need to repeat the process above for each domain you listed in the -d flag. For example, I needed to do the deploy twice because I added 2 domains: ‘mysite.com’ and ‘www.mysite.com’.

Assuming you don’t run into any glitches, you have completed step 1 of the process.

Your certificates will be downloaded and stored on your local filesystem under /etc/letsencrypt/live/mysite.com. There should be 4 files namely: cert.pem; chain.pem; fullchain.pem; privkey.pem

I copied them into my jekyll folder and gitignore it as we need them for step 2.

Step 2: Setting up SSL on Heroku

You need to add the SSL addon to use SSL and add the certs from step 1:

1 heroku addons:create ssl:endpoint
2 
3 heroku certs:add ssl/cert.pem ssl/privkey.pem ssl/chain.pem ssl/fullchain.pem

To check that its all setup properly:

1 heroku certs
2 
3 heroku certs:info

If it all goes well, you should see a dedicated sslenpoint with an ending domain of herokussl.com which you need for step 3. Also, check that the heroku certs:info return Trusted field as True before proceeding further.

Step 3: Configuring DNS

Next we need to update the CNAME record of your DNS settings to point to the new heroku ssl endpoint above. In my case it took several hours for the changes to propagate.

Once done you should see something like the following:

1 host www.mysite.com
2 
3 mysite.herokussl.com is an alias for xxxxxxxx

To check that the ssl is successfully deployed, run the following:

1 curl -kvI www.mysite.com
2 
3 * Connected to www.mysite.com (XXXXXX) port 443 (#0)
4 * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
5 * Server certificate: mysite.com
6 * Server certificate: Let's Encrypt Authority X1
7 * Server certificate: DST Root CA X3

Step 4: Jekyll and heroku specific issues

The above setup works for me up until the point when I started getting Mixed content warnings in the chrome browser when visiting individual posts pages. Make sure that if you are using the site.baseurl call to generate the post url in _config.yml, update it to your full https version.

The other issue is redirection from non-ssl to ssl links. Since I’m only serving static pages using Rack, links to the non-ssl pages still renders the non-ssl versions of the site.

To fix this, I added a 301 permanent redirect from my DNS console.

That only managed to fix the issue of root request of mysite.com to be redirected to https://www.mysite.com. Calls to www.mysite.com still reaches the non-ssl version.

To fix this, I used the rack-ssl-enforcer gem. By requiring it into the config.ru file and redeploying it, all requests for the root domain or non-ssl gets redirected to the ssl version of the site.

Other recommendations from other users with similar issues as mine include using an nginx buildpack to serve as a proxy but that seems a lot of work in my use case. Perhaps as another blog post in the future.

Success at last!

Conclusion

LetsEncrypt is great and I would defintely suggest taking it for a test run. Note that the certificates are only valid for 3 months maximum. This is a security feature and you can read more about it here.

For a more detailed read on setting it up on OS X, read about it here