Hosting a site on GitHub Pages with custom Domain, free SSL and CDN Caching

OK so this is part note to self and a post explaining how I setup this site with the following:

  • Hosting of static content with GitHub Pages
  • Custom domain mapping
  • Cloudflare caching and enforced SSL

The result is a lightning quick and secure site that requires zero system administration or maintenance which was one of the critical things I was looking for. Static content is generated from a ghost website that I've got replicated across my OneDrive and then a simple git push publishes it to the web when I'm ready.

Tools used

  • ghost blogging platform (runs on Node.js)
  • buster python library for generating static content from ghost
  • Account at GitHub
  • git for version control and interfacing with GitHub
  • An account at Cloudflare where I use a free account for DNS and page rules to enforce SSL + drive a permanent cache

Process

I'll assume that you already have a personal domain and can go and create a GitHub and Cloudflare account. Setting up CloudFlare to run the DNS for this domain is outside of the scope of this article but it's pretty comprehensively documented in their Getting Started guide. I will, however, show some screenshots of how I've got things configured where relevant to the article.

As my OS of choice is Linux (where possible) / MacOS (on my laptop) these instructions will be heavily skewed in the terminal towards those platforms.

Download pre-requisites

There are two key technologies that will be required for running ghost and buster, Node.js and Python.

As a matter of good practice I try to, wherever possible, use virtual environments (venvs) in order to separate the code requirements from other system packages and projects.

Used for Venvs
Node.js
Ghost blogging platform
Built using nvm. Note that Ghost only supports certain versions of Node, hence having a virtualenv is very helpful.
Python
Buster static site generator
Built using virtualenv

Final folder structure

For reference and orientation I have the following as my final folder structure:

~/OneDrive/
    |- Blog (synced with OneDrive)
        |- ghost  <-- blogging platform + drafts
        \- myname.github.io  <-- github repo for live site
            |- build-site.sh  <-- create script
            |- CNAME  <-- used for mapping custom domain
            |- index.html
            \- ...

~/Virtualenvs
    |- buster  <-- python virtualenv
    \- ...

Buster

Head across to python.org if you don't already have Python installed locally where there are operating system specific instructions for Windows, Linux/UNIX, Mac OS X and Other. I'm using Python2.7 so YMMV if you want to use Python3.

Once installed, we are going to use the pip package manager to install the virtualenv package. As of version 2.7.9 of Python pip is installed by default so you don't need to do anything here.

virtualenv -p /usr/bin/python2.7 ~/Virtualenvs/buster  
source ~/Virtualenvs/buster/bin/activate  
pip install --upgrade pip buster  

If on MacOS you'll need to install wget using homebrew.

brew install wget

Buster is now installed and setup.

Ghost

Although not supported officially, I'm going to install NVM using homebrew to manage my node instances. This is just easier all around from a maintenance perspective and, based on my testing, works just fine.

brew install nvm  

As, at time of checking, Ghost only supported certain versions of Node (source) I'm going to install v6.9 which is the Long Term Support (LTS) version.

nvm install --lts  

With a Node version now setup we're going to download ghost and unzip it into our build directory. We can then npm install and npm start the application.

This build directory is essentially going to be our local development application, replicating the main website and also storing any drafts we produce.

# define and create the build location
build=~/OneDrive/Blog/ghost  
mkdir -p $build

# download ghost latest (0.11.3) and unzip
wget -O /tmp/ghost.zip https://github.com/TryGhost/Ghost/releases/download/0.11.3/Ghost-0.11.3.zip  
unzip /tmp/ghost.zip -d $build

# install and run application
cd $build  
npm install  
npm start  

This will start a local instance of your ghost blog on your loopback address of http://127.0.0.1:2368 with an admin interface at /ghost. You can then go and edit some content and add a test post.

Setup a github repo

Using our github account, create a repository that matches the syntax <username>.github.io. For instance, mine is saracen9.github.io. If in doubt, consult the guide that they provide.

Once created, we're going to clone this down to our local machine so that we can start adding the static output that gets generated by buster.

cd ~/Blog  
git clone https://github.com/<username>/<username>.github.io  

Add and commit content

Right now we have an empty repository that, when populated, can be found online at http://username.github.io.

Before we do anything else, let's add the content from our ghost site as-is and check this workflow is OK.

# define live location
live=~/Blog/<username>.github.io

# now change into this directory and initialise
cd $live  
buster setup  

This final command will then configure buster by asking for your repo address, which will be something like https://github.com/username/username.github.io.git. You can get this from your repository webpage just in the address bar of your browser.

Once configured, run buster generate in order to output a folder static in the current directory containing all of the web content for your site.

Your repository will now look as follows:

yourname.github.io  
    |- static  <-- destroyed and re-created constantly
        |- index.html
        |- ...

This /static folder is destroyed and re-created everytime you run the buster generate command. Let's commit this and push to GitHub.

git add .  
git commit -m 'first commit of buster output'  
git push origin master  

Your online repository now contains the static web content generated by buster.

Oh no...

OK, so when you navigate to http://username.github.io you discover that nothing is displayed?

The reason for this is that everything is output by buster into the /static folder. As such, hitting the root / doesn't generate anything. You can append /static to the address and everything will display but this is pretty pants...

For the moment, let's manually create an index.html that forwards the user automatically to the static content (we can't use url_rewrite or equivalent). I've already created one so just pull this down into the root.

cd $live  
wget -O index.html "https://goo.gl/zG7TF6"  
git add .  
git commit -m 'add custom forwarding index.html'  
git push origin master  

This will ensure anytime you hit the repository you automatically get forwarded to /static. We will put a better solution in place later but this will keep things clean for now.

Add your custom domain

Cloudflare DNS

Assuming you've now added Cloudflare as your DNS for the your custom domain we will now go in and point the A and CNAME records at the GitHub servers. See guide for more details if you get stuck on this.

As per GitHub Pages the IPs on which your content can be reached are:

  • 192.30.252.153
  • 192.30.252.154

With the CNAME Flattening that's now available we don't really need these anymore but it's worth noting (previously you'd need to map an A record to the IP). Head across to Cloudflare and your DNS settings at https://www.cloudflare.com/a/dns/mydomain.com

We need to create/modify two CNAME records here, one for www and one for the root (i.e. mydomain.com without the www). We will use the root as opposed to www as the primary method for accessing the site, but you could always change this around.

Record Alias of
mydomain.com username.github.io
www mydomain.com

As can be seen we've mapped the root through to the github pages url. Then, anytime someone requests www.mydomain.com they will be mapped through to wherever the root points.

This is quite important as you only get three page rules for free on Cloudflare and we will need all of them for a single CNAME. Hence having both root and www mapped to the same place allows this.

GitHub CNAME

We need to add a custom CNAME file to the github repo in order for our DNS settings to be honoured. Given we just set the root to point here let's add that record.

cd $live  
echo mydomain.com > CNAME  
git add .  
git commit -m 'add CNAME'  
git push origin master  

Navigating through to http://mydomain.com should now take you through to the site we just pushed.

Enable SSL and Caching

Using the aforementioned page rules offered by CloudFlare we can now do the following:

  • Force SSL for mydomain.com
  • Redirect any www to root
  • Cache everything behind mydomain.com so it loads faster after the first visit

Some of this replicates what you'd expect if you were running your own webserver like nginx and was a nice bonus to find. As an example, comparing load times of an example post before and after gives a +25% improvement.

Comparison of load speed with cache

Navigate to the page rules section for your domain in the CloudFlare control panel and create the following:

Page Rules Screenshot 1. Forwarding rule for https://www.mydomain.com to https://mydomain.com. This should be a 301 (permanent).
2. Always use https rule for http://mydomain.com/*
3. Cache Level rule Cache Everything for https://jamesveitch.com/*

NB: Make sure they are ordered as above once you're finished.

This forces all traffic to the root of the domain, converts all http to https and then caches it.

You then need to go to the crypto tab and ensure you have SSL set to Full for the site (not the strict option).

Progress so far

You should now be able to navigate to http://mydomain.com and be automatically redirected to a verified https://mydomain.com reflecting the content of your GitHub repo.

There are still though a couple of gotchas with this setup:

  • content resides in an ugly /static sub-folder
  • as we're running ghost in development mode (which is default when you run npm start) the robots.txt, requests, urls and template variables in the themes refer to localhost

Enter the build script

To fix the sub-folder and robots.txt issues I created a small bash script which will perform the following:

  • generate static content with buster
  • overwrite site root folder with contents of /static whilst keeping key files intact
  • edit the robots.txt file to point to our actual site
  • commit and push GitHub with a timestamp comment

To setup this in our current project.

cd $live  
wget -O build-site.sh "https://goo.gl/Ie0VBA"  
chmod +x build-site.sh  

You can now run build-site.sh from the live repository root to generate and deploy.

Conclusion

After following the above you should have a functional ghost website, hosted for free on GitHub pages, with forced SSL on all pages and CloudFlare cacheing content to deliver via their CDN.

References

James Veitch

Read more posts by this author.