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:
- cipherli [dot] st: Strong Ciphers for Apache, nginx and Lighttpd
- Mozilla: Mozilla SSL Configuration Generator
- Dan Palmer: Achieving Full Marks on Qualys SSL Labs
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.
Migrating your Stories image links
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:
- Ghost Docs: A guide to self hosting
- Ghost Docs: How to install Ghost on your production server
- Ghost Docs: A guide to getting your site updated to Ghost 1.0.0
- Levi Bostian: Move Ghost blog to a subdirectory
- cipherli [dot] st: Strong Ciphers for Apache, nginx and Lighttpd
- Mozilla: Mozilla SSL Configuration Generator
- Dan Palmer: Achieving Full Marks on Qualys SSL Labs
- GitHub: Lynis the Security auditing tool for Linux, macOS, and UNIX-based systems