Configuring Ghost and Let's Encrypt SSL with a www redirect

note: this is a pretty old post, and both Ghost and Acme.sh have changed quite a bit! This guide is now probably the best resource on configuring LE SSL with Ghost in the year of our lord 2020: https://ghost.org/docs/api/v3/ghost-cli/knowledgebase/#ssl-for-additional-domains

This tutorial assumes that you want to self-host your Ghost site under the root address of your domain, i.e. https://somedomain.com, and redirect all other domain permutations to this address. So, rather than resolve the non-SSL version of the root, the non-SSL wwww version or a https://www version—you want to 301 everything to a single canonical address. I wrote these instructions using Ghost 1.15.0, and Ghost CLI 1.2.1.

First, make sure that your domain is pointed to the correct nameservers, and that you have both an '@' and a 'www' record for your domain. It might be helpful to run the host shell command to verify that your domain is networked correctly to your server.

Next, set up your ghost site at the primary address you plan to use. If you want your site to live at https://lemontree.com, run the Ghost CLI installer and tell it the new install belongs at https://lemontree.com. The Ghost CLI will volunteer to set up a Let's Encrypt SSL cert for you at this address, and it will save you a lot of time to accept this offer. It's also easiest to allow Ghost to configure nginx.

The CLI will install a shell script called acme.sh in order to ping the Let's Encrypt service to issue certificates for the exact install domain.

Now, you should have a functional site, live on the internet. Go ahead and curl the URL you gave the Ghost installed.

curl http://lemontree.com no https will produce a Moved Permanently. Redirecting... message.
curl https://lemontree.com will return the full body of your Ghost homepage.

So far, so good. On to the next two domains, http://www.lemontree.com and https://www.lemontree.com. This step requires a little more legwork. curl http://www.lemontree.com should give you the default nginx install page, and curl https://www.lemontree.com with the https will result in an SSL error. This is because the SSL cert issued by Let's Encrypt covers only the non-www domain. You need to issue a second cert specifically for https://www.lemontree.com, even though we are planning to redirect this traffic. A browser pointed to a https address will confirm SSL certificate details immediately, before processing a redirect request.

In order to issue a certificate for our www domain, we need to configure nginx to respond to the LE verification request—this is what associates our certificate to our domain.

Navigate to the nginx config files Ghost setup for you. They live in /var/www/[lemontree]/system/files, where [lemontree] is the name of your install. Open up the non-ssl config file, lemontree.com. You should see an existing server block that redirects traffic from the non-https address to an SSL scheme. Add a new block below to accept requests from the LE verification request, and ensure this block redirects all other traffic to your primary SSL domain:

server {
   listen 80;
   listen [::]:80;

   server_name www.lemontree.com lemontree.com;
   root /var/www/lemontree;

    location ~ /.well-known {
        allow all;
    }
    
    location / {
        return 301 https://lemontree.com$request_uri;
    }

    client_max_body_size 50m;
}

By default, /var/www/lemontree.com will be the root dir of your Ghost install, though this may vary.

It's a nice idea to run a test on your nginx config files each time you edit them, before restarting nginx. sudo nginx -t will confirm a valid config. Assuming there are no syntax errors, sudo service nginx restart will gracefully reload your nginx config.

Remember that script acme.sh? Navigate to the directory where that script lives: /home/[user]/.acme.sh/, where [user] is the user account that ran the Ghost install commands.

Now, issue a certificate through acme.sh for your https://www address:

acme.sh --issue -d www.lemontree.com -w /var/www/lemontree.com

/var/www/lemontree.com needs to match the dir you specified in the nginx conf.

After acme.sh runs, you should see a message with the location of your new certificate files. Take note of these locations! They will look something like this:

Your cert is in  /home/serveruser/.acme.sh/www.lemontree/www.lemontree.cer
Your cert key is in  /home/serveruser/.acme.sh/www.lemontree/www.lemontree.key
The intermediate CA cert is in  /home/serveruser/.acme.sh/www.lemontree/ca.cer
And the full chain certs is there:  /home/serveruser/.acme.sh/www.lemontree/fullchain.cer

Next, we want to configure nginx to use these newly issued www cert files. I didn't have any luck using the acme.sh automated install command. Here's how I manually added the certificates. Head back over to the nginx config files Ghost created, in /var/www/[lemontree]/system/files. There will be an existing SSL config called lemontree.com-ssl.conf. Copy this to a new file, www.lemontree.com-ssl.conf. Edit the servername to match the www version of your domain. Comment out the line root /var/www/lemontree/system/nginx-root;—we're not going to serve any content from this address. Under the server_name line, add a 301 redirect: return 301 $scheme://lemontree.com$request_uri;. Finally, there will be two lines that begin ssl_certificate and ssl_certificate_key respectively. Edit the paths after these keywords to match the path acme.sh returned. (Should be as simple as adding www ahead of your domain.)

Now we need to add symlinks from the nginx dir to the new www conf file in your Ghost dir. Head over to the dir /etc/nginx/sites-available and add a symlink to Ghost:

sudo ln -s /var/www/lemontree/system/files/www.lemontree.com-ssl.conf www.lemontree.com-ssl.conf

Finally, link sites-enabled to sites-available: sudo ln -s ../sites-available/www.lemontree.com-ssl.conf www.lemontree.com-ssl.conf

Run one last sudo nginx -t test and restart nginx. Now, when you curl your www addresses, both the https and non-https variants should return a 301 redirect to the canonical, non-www https address. Congrats!