Installing Ghost on Azure Websites and forcing SSL with a Custom Certificate

Once I had decided that I was going to host my new Ghost blog myself, I thought that it would be cool if I could host it in Microsoft Azure. I had various other options available to me, including Shared and VPS accounts with Arvixe [*]1 that I've already paid for and use for other things, but I was curious to see how straightforward it would be to get it working in Azure. As I mentioned in my introductory post, my only real requirement (other than being able to get it up and running before the end of 2014) was that I should be able to enforce encrypted communication over SSL. It turns out that wasn't quite as easy as I'd hoped, but I got it working in the end (look in the address bar now. Can you see the padlock?) and this is how I did it.

Overview

In this tutorial we are going to setup Ghost in Azure using a free SendGrid account for sending email. We'll sign up for this account from within Azure itself as this gives us a significantly larger quota of free emails (25,000 per month instead of 12,000). We'll use a custom domain name of https://ghost-demo.tomssl.com and we'll enforce the use of SSL for all traffic using a custom certificate. To achieve this we need to edit three files and, rather than changing them locally and then deploying them, we're going to live dangerously and edit them online using Visual Studio Online "Monaco", which is part of Azure. I would never advocate doing this except in exceptional circumstances, but it's quite interesting and so I present the method here.

SSL Certificates

If you want to obtain a completely free trusted Class 1 SSL certificate then I can thoroughly recommend StartSSL. I have Class 2 validation with StartSSL and am a Web of Trust Notary with them which means I can perform identity validation. My identity card is here.

Before we start I should mention that custom domains and SSL certificates are not available in the Free edition of Azure Websites. Indeed, since installing SSL certificates costs extra on the Basic Tier, you are probably better off using the Standard Tier if you want custom domains and custom SSL certificates. Check out the pricing information and decide for yourself.

I should also mention that going over all of the things Azure has to offer is way beyond the scope of this article, although even if you've never used Azure before there should be enough information here to get you started. But this is emphatically not a general tutorial on Azure. If you want one of those, this one from Microsoft might be a good place to start.

Azure Administration

There are two main ways to administer your Azure account through the web using a browser: the standard Azure Management Portal at https://manage.windowsazure.com and the new Azure Preview Portal at https://portal.azure.com.

It's also possible to administer Azure using PowerShell, but we're not going to do that here.

The new portal is great and you should use it where possible. Read more about it here. Having said that, right now (5th January 2015) not everything from the old portal is available in the new portal. SendGrid is one of those things.

Creating a free SendGrid account

Okay, let's get started. First create a free SendGrid account from within the Azure Marketplace in the old Azure Management Portal. Of all the free bulk email sending accounts I've seen, the offering from SendGrid within Azure seems by far the most generous with 25,000 free emails a month, which is just over double the standard 400/day offering from the SendGrid website.

Click NEW -> MARKETPLACE (PREVIEW) and scroll through until you find SendGrid. Choose the free option.

SendGrid SignUp in Azure Marketplace

Once you've done that select your new SendGrid account and grab the credentials by clicking on the CONNECTION INFO button at the bottom of the screen. You only need the username and password.

Installing Ghost

Now go to the Preview Portal (we're going to stay in here from now on) and click on the green plus at the bottom of the screen and select Everything and search for ghost, like this:

Create Ghost in Azure Marketplace

Choose Ghost by Ghost and click Create. Choose a URL and enter your SendGrid credentials into the WEB APP SETTINGS, like this and then click OK:

Create Ghost in Azure Marketplace Set Credentials

Now it will return you to the main portal and do this for a minute or two.

Creating Ghost in Azure Marketplace

Eventually you will be informed that your new website is up and running and you will be able to browse to it.

Ghost in Azure Created

You may need to wait a few minutes or try refreshing a few times whilst everything starts up (remember this is running under node.js inside IIS), but eventually you will see a screen like this.

Ghost Running in Azure

Using a custom domain and forcing SSL communication

In order to make Ghost work properly with a custom domain and to force communication over SSL you need to do three or four more things:

  • Set a custom domain in the Azure portal;
  • Add the DNS records;
  • Optionally apply your own SSL certificate or just use the wildcard *.azurewebsites.net certificate provided by Microsoft and live with certificate errors;
  • Tweak three files from the Ghost installation (more on that later);

Setting a custom domain in the Azure Portal

Select your website and then select SETTINGS -> Custom domains and SSL.

When you enter your custom domain Azure tries to validate it immediately which results in a genuinely helpful error message like this:

Azure Custom Domain Error Message

Setting up the DNS records

I use the free version of CloudFlare for nearly all of my DNS hosting as it has support for some of the more obscure types of DNS record, including SRV and LOC. CloudFlare is primarily meant to be used for web acceleration. As their website says:

A, AAAA and CNAME records can have their traffic routed through the CloudFlare system. Click the cloud next to each record to toggle CloudFlare on or off.

But I often disable that functionality and just use them as a central place to host my DNS records for free.

Create the missing record(s) and you will be rewarded with a green tick and you can click Save. I created these records for the tomssl.com domain (you can see that I have not enabled CloudFlare acceleration):

Ghost in Azure DNS Records

At this point, if you have an SSL certificate stored as a .pfx file you can upload it and provide the password. Then it will be available in the SSL bindings dropdowns as show below:

Set Custom Domain in Azure Portal

Note: Unless you particularly need to support legacy browsers, you should probably opt for SNI SSL as it is far cheaper than IP based SSL (you can have 5 SNI certificates and 1 IP bound certificate for free in the Standard Tier).

Fixing the Ghost Installation

Now you have sorted out your custom domain and SSL certificate, you need to tell Ghost about them by tweaking those three files I alluded to earlier.

  • wwwroot\web.config - this is a standard IIS web.config file. It will contain our rewrite rules to enforce SSL and make sure we are using our custom domain.
  • wwwroot\config.js - this file contains your custom domain.
  • wwwroot\core\server\middleware\index.js - This is part of the Ghost installation. I should contact the Ghost team about changing this for Azure.

There are lots of ways you could change these files and the normal procedure would be to edit them offline and then deploy them, but let's throw caution to the wind and edit them online. Please note that this is terrible advice that could cost you your job. Never edit live code like this. Well, maybe just this once since our site is not yet live. Ordinarily you might deploy your changed files using SFTP or by deploying directly from source control. Here is a fairly comprehensive list of ways to deploy to Azure.

Installing Visual Studio Online "Monaco"

Another really cool feature about Azure is Visual Studio Online. This is a free site extension which lets you edit your files online. It's installed by choosing the website and scrolling to the bottom and choosing Extensions like this:

Add Azure Site Extension

Now Add and choose Visual Studio Online.

Add Azure Site Extension Visual Studio Online

Once this has installed it will appear in the Extensions blade (each vertical segment in the portal is called a blade) and you can choose Visual Studio Online and then Browse.

Ghost in Azure VSO Browse

This will open a new window and you will notice that the URL is derived from your website URL, so you can navigate there directly if you like.

e.g. if your website is at http://mywebsite.azurewebsites.net then your Visual Studio Online is at https://mywebsite.scm.azurewebsites.net/dev once you have installed the extension.

Editing wwwroot\web.config

Browse to Visual Studio Online and you will see something like this. Click on web.config in the list of files on the left hand side.

Ghost in Azure Visual Studio Online Quick Start

Change the web.config file like this:

<?xml version="1.0" encoding="utf-8"?>  
<configuration>  
  <system.webServer>
    <httpErrors existingResponse="PassThrough" />
    <handlers>
      <add name="iisnode" path="index.js" verb="*" modules="iisnode"/>
    </handlers>
    <rewrite>
      <rules>
        <rule name="ConvertToLowerCase" stopProcessing="true">
          <match url=".*[A-Z].*" ignoreCase="false" />
          <action type="Redirect" url="{ToLower:{R:0}}" redirectType="Permanent" />
        </rule>
        <rule name="ForceSSL" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="^OFF$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
        </rule>
        <rule name="CanonicalHostName" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTP_HOST}" negate="true" pattern="^ghost-demo.tomssl\.com$" />
          </conditions>
          <action type="Redirect" url="https://ghost-demo.tomssl.com/{R:1}" redirectType="Permanent" />
        </rule>
        <!-- These next two rules are related to node.js -->
        <rule name="StaticContent">
          <action type="Rewrite" url="public{REQUEST_URI}"/>
        </rule>
        <rule name="DynamicContent">
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
          </conditions>
          <action type="Rewrite" url="index.js"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>  

We have added a bunch of IIS URL Rewrite rules and tweaked one that was already there.

  • ConvertToLowerCase - this makes sure the entire url is in lower case.
  • ForceSSL - permanently redirects requests from http to https.
  • CanonicalHostName - makes sure that all requests come in to our custom domain and not via the .azurewebsites.net domain.

For a great resource explaining URL rewriting in IIS see Ruslan Yakushev's great blog post 10 URL Rewriting Tips and Tricks.

At this point the blog will work but some of the links will be wrong because part of a standard Ghost installation is editing the config.js file to take the custom url.

Editing wwwroot\config.js

Edit the relevant part(s) of config.js with your custom domain. The url you set in here will be used internally by Ghost to create all of the links when the site is rendered. There are sections for development and production settings. Change both. Here is the production section.

    // ### Production
    // When running Ghost in the wild, use the production environment
    // Configure your URL and mail settings here
    production: {
        url: 'https://ghost-demo.tomssl.com', // we have changed this line
        mail: {
            transport: 'SMTP',
            options: {
                service: 'SendGrid',
                auth: {
                    user: 'azure_aloadofrandomcharacters@azure.com',
                    pass: 'secretpassword'
                }
            }
        },
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(__dirname, '/content/data/ghost.db')
            },
            debug: false
        },
        server: {
            // Host to be passed to node's `net.Server#listen()`
            host: '127.0.0.1',
            // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
            port: process.env.PORT
        },
        forceAdminSSL: false // causes a redirect-loop on azure, use urlrewrite instead
    },

Now all of the links in your blog will point to something starting with https://ghost-demo.tomssl.com/.

Unfortunately, you will now have broken your installation and none of the pages will load as you will get a redirect loop.

Clearly editing the config.js file broke something. Did you notice the clue that forcing SSL might not be completely straightforward in Azure? Maybe setting https in the config.js file is somehow causing the same issue.

forceAdminSSL: false // causes a redirect-loop on azure, use urlrewrite instead  

To fix this I had a quick look through the Ghost source code for anything relating to forceAdminSSL.

Editing wwwroot\core\server\middleware\index.js

Since we are handling all of our SSL redirection in IIS via the web.config file prior to reaching the node.js application, we need to prevent the application from doing any further redirects.

Locate the function isSSLrequired (somewhere around line 166 in index.js). This is definitely the culprit as it examines the url set in config.js for https: as predicted. Let's change the function so that it always returns false.

UPDATE: In later versions of Ghost, this function has moved (e.g. it's in wwwroot\core\server\middleware\check-ssl.js in v0.7.1). Your best bet is to search the middleware source code for isSSLrequired.

function isSSLrequired(isAdmin) {  
/* - Prevent redirect loop by handling SSL exclusively in web.config
    var forceSSL = url.parse(config.url).protocol === 'https:' ? true : false,
        forceAdminSSL = (isAdmin && config.forceAdminSSL);
    if (forceSSL || forceAdminSSL) {
        return true;
    }
*/
    return false;
}

Ghost in Azure Fix index.js

Now we just need to restart the website. We can either restart the whole website or we can just restart node.js.

To restart node.js hit Ctrl-Shift-C to open the console and type npm start and hit enter.

Ghost in Azure Restart node.js

Done. Now your blog will force the use of https everywhere.

Further thoughts

I may write a brief follow-up post explaining how to update to the latest version of Ghost and how to add custom themes. In the mean time, here are some hints.

  • Grab the latest version of Ghost. Merge your code changes.
  • Update using Felix Rieseberg's open source Ghost Updater.
  • Choose a theme and customise it with Code syntax highlighting and add Disqus comments.
  • Upload your theme. Can now choose it in drop down.

Conclusion

Azure Websites (sometimes formerly referred to as WAWS) is incredibly powerful and a great place to host a Ghost blog. If you want to force a secure, encrypted HTTPS connection - and according to just about everybody, including Google who have given it a search ranking boost since last August, you should - then there is a little bit of work to do. But as you can see, it's quite straightforward and you only really need to make a couple of very simple changes to three files. And yes, the irony of that Google blog post not being available over https has not eluded me.

  1. In the interest of transparency, if you see a link with an asterisk in square brackets after it like this Arvixe [*] then it's an affiliate link. If you click it and then buy something I might make a tiny amount of money. But you'll always know in advance thanks to the [*].