HTTPS on WebFaction using Let's Encrypt

Posted on 25 December 2016

Switching to HTTPS is in the news at the moment, and will eventually affect page rank. There’s no reason not to enable HTTPS now that Let’s Encrypt provides certificates free of charge.

On my shared hosting on WebFaction, it was pretty painless:

  1. Install acme.sh.
  2. Generate certificates.
  3. Upload certificates in the WebFaction control panel.
  4. Set up an auto-renewal script that uses the WebFaction API.

1. Install acme.sh

I ran the following on a Bash prompt to install acme.sh:

curl https://get.acme.sh | sh

This installs the acme.sh script in ~/.acme.sh/. It will eventually create certs in this directory too. It also installs a cron job for renewing the cert. More on that later.

2. Generate certificates

I’m just generating a cert for one specific subdomain (blog.rarepebble.com). It’s possible to share certs across domains, but it’s also easy to have one for each.

acme.sh --issue -d blog.rarepebble.com -w ~/webapps/my_website

The ~/webapps/my_website directory is where the website is served from. This will generate certs for the domain under the ~/.acme.sh/ directory.

3. Configure the certs in the WebFaction control panel

In the WebFaction control panel, I selected the Domains/Websites tab, then the SSL Certificates sub-tab, and selected Add SSL certificate. I named my cert entry rarepebble_blog_cert. The certs needed in the form can be found under the ~/.acme.sh/ directory and are the .cer, .key and ca.cer respectively.

After setting up the cert record, I edited my website record and toggled it to “Encrypted website (https)”. I selected my recently created cert record from the dropdown box. (Confusingly, if the cert isn’t created first, the box doesn’t show up at all!)

At this point my site loads with https://! Fortunately Hakyll builds my site with relative URLs everywhere so there’s no fixing of internal links to be done.

At this point I shoved my domain into the Qualys Lab tool and got a gratifying green-tinted “A” rating.

4. Set up an auto-renewal script that uses the WebFaction API.

Unfortunately in three months my site will stop working unless I remember to re-upload my certs to WebFaction. (Acme.sh will have regenerated them but WebFaction won’t know about it.) This won’t do…

This is the script that I installed as a replacement for the cron entry created by the acme.sh installer. It calls acme.sh, then uses the WebFaction API to update the cert if it’s been renewed, using the cert name that I chose on the control panel:

#!/usr/local/bin/python2.7

"""
Wraps the "acme cron" command and updates WebFaction if ACME yields a new cert.
Install as a cron job in place of "acme cron".
"""

import sys
import subprocess
import xmlrpclib
from os.path import join

user = "<webfaction username>"
pw = "<webfaction password>"
acme_dir_path = '/home/' + user + '/.acme.sh'

# Folder/domain inside ~/.acme.sh
domain = 'blog.rarepebble.com'

# The name given in the Webfaction control panel
wf_cert_name = 'rarepebble_blog_cert'

# Run the acme cron command
proc = subprocess.Popen(['.acme.sh/acme.sh', 'cron'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()

if err:
    sys.exit(err)
    
if 'Cert success.' in out:
    
    # Load cert text
    with open(join(acme_dir_path, domain, domain + ".cer")) as f:
        cert_text = f.read()
    with open(join(acme_dir_path, domain, domain + ".key")) as f:
        key_text = f.read()
    with open(join(acme_dir_path, domain, "ca.cer")) as f:
        ca_text = f.read()
    
    # Send certs to API
    server = xmlrpclib.ServerProxy('https://api.webfaction.com/', allow_none=True)
    session, account = server.login(user, pw, None, 2)
    server.update_certificate(session, wf_cert_name, cert_text, key_text, ca_text)

else:
    print "Cert not updated: Nothing to do."

(Optionally) 5. Redirect HTTP traffic.

This is just a case of creating a CGI website for the domain using HTTP, with nothing but an .htaccess containing the following:

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-SSL} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

The WebFaction docs have all the steps.

Helpful Resources

I owe a debt to WebFaction user nik for his Python script posted on the WebFaction thread, which was the starting point for mine. Also to cpbotha for describing the first two steps so concisely.