This post will cover manual installation a new Ghost v.1.21.2 application on an existing Ubuntu server running Apache, MySQL and NodeJS.

I've been a fan of the Ghost blogging platform since it was first released a few years ago (as can be seen from this blog and some posts I've written about it before). I recently decided to revive my personal blog as well as this one, but there have been some significant changes since the 1.0+ release. The biggest change is that Ghost now only officially supports installation via a CLI tool by a super-user, and only supports a MySQL database and Nginx web server stack. If you're creating a new dedicated server instance, then this isn't much of a problem as you can just spin up a new one on a host like Digital Ocean (hosting provider recommended by both Ghost and myself) and install the required stack before running the CLI as root. If you just want to run a new blog on an existing server with a different server stack, then this becomes a bit of an issue.

Luckily, Ghost is also usable as an NPM package that can be used in a Node application, and a few people have written a post about how this can be done, although with one minor mistake in the content path setting. Below, I'll list all the steps I've taken to install Ghost v.1.21.2 on a Digital Ocean Ubuntu 16.04.3 LTS instance already running Apache 2.4.x and MySQL 5.7.x. This guide also assumes you have a working installation of Node(v6.12.2) and NPM(v3.10.10).


Installing Ghost

For Ghost, like my other Node applications, I'm using a non-root user to create/modify files and run the applications. You could choose to do everything as root, but this is generally a bad idea. If you don't already have such a user ready, then create a new user. Logged in as the root user, type the following commands:

useradd -d /home/USER -m -s /bin/bash USER
passwd USER

where USER is the username you wish to use. The first command will add a new user and the second command will prompt you for a password (choose a strong password) for the new user. After you're done, log in as that user (su USER) and the rest of the guide should be done as that user unless otherwise specified.

Choose a directory name (GHOSTDIR for this guide) to be created under an appropriate location (can be under the USER's home directory, your main websites directory or wherever you store your Node apps on the server) to store your new ghost installation. Then run the following commands:

mkdir GHOSTDIR
cd GHOSTDIR
npm init
npm i ghost@latest --save

This will create the directory, initialize a new node application inside that directory, install the ghost package(v1.21.2) and add it to the "package.json" description file. You can just hit enter and accept defaults when running npm init or enter real information if desired.

Ghost requires an existing MySQL server and you should create a new user and database in MySQL with limited permissions instead of using the root MySQL user. First log into MySQL as your admin/root user:

mysql -uMYSQLROOTUSER -pMYSQLROOTPASSWORD

Then, once inside the MySQL CLI:

create database GHOSTDBNAME;
create user 'GHOSTDBUSER'@'localhost' identified by 'GHOSTDBUSER_PASSWORD';
grant all privileges on GHOSTDBNAME.* to 'GHOSTDBUSER'@'localhost';
flush privileges;
quit;

replacing the capitalized placeholders with your chosen usernames and passwords. Ghost also requires another application named knex-migrator to initialize it's own database structure, so let's install that now:

npm install -g knex-migrator

If you run cat package.json you should see something like:

{
  "name": "GHOSTDIR",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ghost": "^1.21.2"
  }
} 

depending on the values entered during the npm init dialog.

Ghost requires a configuration file named "config.production,json", and you can copy over a default one from node_modules/ghost/core/server/config/env/config.production.json :

cp node_modules/ghost/core/server/config/env/config.production.json config.production.json

The default configuration file will look like this:

{
    "database": {
        "client": "mysql",
        "connection": {
            "host"     : "127.0.0.1",
            "user"     : "root",
            "password" : "",
            "database" : "ghost"
        }
    },
    "paths": {
        "contentPath": "content/"
    },
    "logging": {
        "level": "info",
        "rotation": {
            "enabled": true
        },
        "transports": ["file", "stdout"]
    }
}

Edit this file, add an entry for your site's URL and server config, and update the database config section with the name, user and password you chose in the MySQL CLI. For the server config, you'll also have to choose a unique port number for this Node application instance, something like 4200 or any other available port. If you're not sure what ports are already in use on your server, run netstat -tuplen to see.

Note: You must use the absolute path to your copied content directory, otherwise Ghost will continue to read and write files from the node_modules/ghost/content subdirectory instead, which would be overwritten if you upgrade the NPM package, losing all your uploads and theme changes. Make sure to remove the trailing slash on the absolute path as this can cause problems as well.

Your server config file should now look like this:

{
    "url": "GHOST_URL",
    "server": {
        "host": "127.0.0.1",
        "port": GHOST_PORT
    },
    "database": {
        "client": "mysql",
        "connection": {
            "host"     : "127.0.0.1",
            "user"     : "GHOSTDBUSER",
            "password" : "GHOSTDBUSER_PASSWORD",
            "database" : "GHOSTDBNAME"
        }
    },
    "auth": {
        "type": "password"
    },
    "paths": {
        "contentPath": "/ABSOLUTE/PATH/TO/GHOST/content"
    },
    "logging": {
        "level": "info",
        "rotation": {
            "enabled": true
        },
        "transports": ["file", "stdout"]
    }
}

To use Ghost as an NPM module, create an "index.js" and enter the following:

var ghost = require('ghost');
var path = require('path');
ghost().then(function (ghostServer) {
    ghostServer.start();
});

Ghost will need a local content directory for uploading images and modifying themes. Create a local copy from the Ghost NPM package using the following command:

cp -r node_modules/ghost/content .

Make sure the copied content directory matches the contentPath variable you set in the config file.

Once the files have been copied, you'll need to initiate the Ghost database structure by issuing the following command:

NODE_ENV=production knex-migrator init --mgpath node_modules/ghost

Which should output the following if successful:

[2018-02-18 05:36:24] INFO Creating table: posts
[2018-02-18 05:36:24] INFO Creating table: users
[2018-02-18 05:36:24] INFO Creating table: roles
[2018-02-18 05:36:24] INFO Creating table: roles_users
[2018-02-18 05:36:24] INFO Creating table: permissions
[2018-02-18 05:36:24] INFO Creating table: permissions_users
[2018-02-18 05:36:24] INFO Creating table: permissions_roles
[2018-02-18 05:36:25] INFO Creating table: permissions_apps
[2018-02-18 05:36:25] INFO Creating table: settings
[2018-02-18 05:36:25] INFO Creating table: tags
[2018-02-18 05:36:25] INFO Creating table: posts_tags
[2018-02-18 05:36:25] INFO Creating table: apps
[2018-02-18 05:36:25] INFO Creating table: app_settings
[2018-02-18 05:36:25] INFO Creating table: app_fields
[2018-02-18 05:36:25] INFO Creating table: clients
[2018-02-18 05:36:25] INFO Creating table: client_trusted_domains
[2018-02-18 05:36:25] INFO Creating table: accesstokens
[2018-02-18 05:36:25] INFO Creating table: refreshtokens
[2018-02-18 05:36:25] INFO Creating table: subscribers
[2018-02-18 05:36:25] INFO Creating table: invites
[2018-02-18 05:36:26] INFO Creating table: brute
[2018-02-18 05:36:26] INFO Creating table: webhooks
[2018-02-18 05:36:26] INFO Model: Post
[2018-02-18 05:36:26] INFO Model: Tag
[2018-02-18 05:36:26] INFO Model: Client
[2018-02-18 05:36:26] INFO Model: Role
[2018-02-18 05:36:26] INFO Model: Permission
[2018-02-18 05:36:27] INFO Model: User
[2018-02-18 05:36:27] INFO Relation: Role to Permission
[2018-02-18 05:36:28] INFO Relation: Post to Tag
[2018-02-18 05:36:28] INFO Relation: User to Role
[2018-02-18 00:36:28] INFO Finished database init!

You should now be able to start the new Ghost installation using the following command:

NODE_ENV=production node index.js

If everything is working normally, it should display something like the following:

[2018-02-18 05:38:02] WARN Theme's file locales/en.json not found.
[2018-02-18 05:38:03] INFO Ghost is running in production...
[2018-02-18 05:38:03] INFO Your blog is now available on https://GHOST_URL/
[2018-02-18 05:38:03] INFO Ctrl+C to shut down

To have apache proxy incoming web traffic to your new Ghost application, use something similar to the following in your site config file for regular port 80 traffic:

<VirtualHost *:80>
    ServerName GHOST_URL  
    ErrorLog ${APACHE_LOG_DIR}/error-GHOST_URL.log
    CustomLog {APACHE_LOG_DIR}/GHOST_URL.log combined
    <Proxy http://127.0.0.1:GHOST_PORT/*>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:GHOST_PORT/ Keepalive=On
    ProxyPassReverse / http://127.0.0.1:GHOST_PORT/
</VirtualHost>

If you have an SSL cert ready for your URL, then use something like the following apache site config to redirect all traffic to port 443 instead:

<VirtualHost *:80>
    ServerName GHOST_URL
    Redirect / https://GHOST_URL/
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
    RequestHeader set X-Forwarded-Proto "https"
    ServerName GHOST_URL
    ErrorLog ${APACHE_LOG_DIR}/error-GHOST_URL.log
    CustomLog ${APACHE_LOG_DIR}/access-GHOST_URL.log combined
    <Proxy http://127.0.0.1:GHOST_PORT/*>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:GHOST_PORT/ Keepalive=On
    ProxyPassReverse / http://127.0.0.1:GHOST_PORT/
    SSLEngine on
    SSLProtocol all -SSLv2
    SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA
    SSLCertificateFile /etc/ssl/live/GHOST_URL/fullchain.pem
    SSLCertificateKeyFile /etc/ssl/live/GHOST_URL/privkey.pem
    Include /etc/ssl/options-ssl-apache.conf
</VirtualHost>
</IfModule></pre>

Point your browser to your GHOST_URL address, and you should see the default Ghost site up and running. If you'd like to have this site startup and shutdown with your server, you can use a daemon like PM2 and configure that next.

Ghost Startup Screen
Ghost startup screen

That's it, you have Ghost v 1.21.2 up and running on your Apache-based server, and can start setting up your blog settings and posts. Take a look at my next post for tips on adding features like Google Analytics, code highlighting with Prism.js, Disqus comments and Mailchimp subscription integration.