Migrating from Ghost 0.x to Ghost 1.x

It's been a while since I updated my blog's (0.x) version of Ghost and Ghost 1.x is a major upgrade with breaking changes and no automated migration path.

I also wanted to host a static site at https://robferguson.org and move my blog from http://robferguson.org to https://robferguson.org/blog.

In this post, I'll walk you through the steps I followed when migrating from Ghost 0.x to Ghost 1.x.

Prerequisites

When self hosting Ghost 1.x, the following stack is recommended:

  • Ubuntu 16.04
  • MySQL
  • Nginx
  • Systemd
  • Node.js v6 installed via NodeSource
  • At least 1GB memory (swap can be used)
  • A non-root user for running Ghost commands

Choose a hosting provider

I've been using DigitalOcean for several years, they're inexpensive and they make it easy to provision a VPS on which Ghost can be installed. I chose an entry level Droplet for $5 USD per month with Ubuntu 16.04 (x64), 512 MB of memory and 20 GB of storage:

Setup Ubuntu 16.04

I followed the steps in this post to setup Ubuntu 16.04. I chose an entry level Droplet with 512 MB of memory so I followed the steps in this post to setup a 4G swap file.

Install Nginx, MySQL, Node.js and Ghost

I followed the steps in the Ghost doc's Install & Setup section to install Nginx, MySQL, Node.js and Ghost (using the ghost-cli).

This is what my website's directory structure looks like:

├── /var
    └── /www
        └── /robferguson.org
            └── /html
                ├── index.html (static site landing page)
                ...
        └── /ghost
            └── /blog
                └── /content
                    └── /apps
                    └── /data
                    └── /images
                    └── /logs
                    └── /themes
            └── /current (-> /var/www/ghost/versions/1.6.1)
            └── /system
            └── /versions
            ├── config.production.json
            ├── robots.txt

config.production.json

{
  "url": "http://robferguson/blog",
  "server": {
    "port": 2368,
    "host": "127.0.0.1"
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost",
      "password": "PASSWORD",
      "database": "ghost"
    }
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/www/ghost/blog/content"
  }
}

Note: You can start/stop Ghost using the following commands:

service ghost_localhost start
service ghost_localhost stop

Nginx

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

  server_name www.robferguson.org robferguson.org;

  # redirect http www and http non-www to https non-www
  # return 301 https://robferguson.org/blog$request_uri;
  rewrite ^ https://robferguson.org/blog$request_uri? permanent;
}

server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;
  gzip off;
  
  server_name www.robferguson.org;

  include snippets/ssl.conf;

  # redirect https www to https non-www
  return 301 https://robferguson.org$request_uri;
}

server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;
  gzip off;

  root /var/www/robferguson.org/html;
  index index.html;

  server_name robferguson.org;

  include snippets/ssl.conf;

  location / {
    try_files $uri.html $uri $uri/ /index.html;
  }

  location ^~ /blog {
    proxy_set_header HOST $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-NginX-Proxy true;

    proxy_pass       http://127.0.0.1:2368;
    proxy_redirect   off;
  }

  location ~ ^/(robots.txt) {
    root /var/www/ghost;
  }
}

Note: You can start/stop/reload Nginx using the following commands:

sudo systemctl stop nginx
sudo systemctl start nginx
sudo systemctl reload nginx

ssl.conf

ssl_certificate     /etc/nginx/ssl/robferguson_org.crt;
ssl_certificate_key /etc/nginx/ssl/robferguson_org.key;
# sudo openssl dhparam 4096 -out /etc/ssl/dhparam.pem 
ssl_dhparam         /etc/ssl/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache   shared:SSL:10m;
ssl_session_tickets off;

# Modern configuration
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;

# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

I wanted to achieve an A+ on Qualys SSL Labs SSL Report:

So I followed the recommendations in these posts:

robots.txt

User-agent: *
Sitemap: https://robferguson.org/blog/sitemap.xml

Migrating to Ghost 1.x

I followed these steps from the Ghost doc's Migrating to 1.0.0 section:

  • Backup your content using the JSON exporter
  • Use the Ghost Importer to import existing content

Copy your images to your new site

I backed up the images from my previous Ghost version:

cd /var/www/ghost/content
sudo zip -r images.zip images

And, copied them to my new Droplet using scp:

scp rob@128.199.238.4:/var/www/ghost/content/images.zip /var/www/ghost/blog/content
sudo unzip images.zip

Note: The Ghost user must own the content directory:

sudo chown -R ghost:ghost /var/www/ghost

Upload your theme

I purchased a new theme and uploaded it using Ghost's Settings => Design page:

If your blog uses Disqus, don't forget to set your disqus_shortname:

var disqus_shortname = 'my-blog';

Note: You should find it in your theme's comments.hbs file (e.g., /var/www/ghost/blog/content/themes/[theme-name]/partials/comments.hbs).

Migrating your Disqus comments

I used the Disqus url-mapper to migrate my blog's comments.

I moved my blog from http://robferguson.org to https://robferguson.org/blog so I also needed to update my published Stories image links from /content to /blog/content.

Server Hardening

Ubuntu 16.04

Its always a good idea to check for updates, so let's open a terminal session and run the following commands:

sudo apt update
sudo apt upgrade
sudo apt-get check
sudo apt-get autoclean

Now, run the following commands:

sudo apt-get install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades

You'll still be notified about "normal" updates, such as those that contain bugfixes, but security updates will be automagically installed.

MySQL

To harden MySQL run the following command:

sudo mysql_secure_installation

Then follow the prompts.

Server Auditing

Lynis is a security auditing tool for Linux, to install Lynis run the following commands:

cd /usr/local
sudo git clone https://github.com/CISOfy/lynis

To run Lynis:

cd /usr/local/lynis
sudo ./lynis audit system

You should see output like:

Lynis will perform an in-depth security scan, its primary goal is to test security defenses and provide tips for further system hardening. It will also scan for general system information, vulnerable software packages, and possible configuration issues.

References: