Setting Up Ghost, the Open-Source Blogging Platform

In this post, learn how to install Ghost, configure email with Mandrill, version track it using Git, deploy it using NGINX, and keep it running with PM2.

This is a comprehensive guide, using language tailored for beginners. We will produce a more concise guide for developers in a subsequent post.


Let's take a moment to get to know the main players:

  1. Ghost - an open source publishing platform, similar to WordPress
  2. Mandrill - a transactional email service, which means it allows you to send emails from your code
  3. Git - the most popular version control system, which means it keeps a version history of your code, so you can see who did what, and revert back the changes if things go wrong.
  4. NGINX - an HTTP and reverse proxy server. Similar to Apache HTTP Server.
  5. PM2 - a production process manager, which means it will monitor your processes and restart any that crashes. This ensures your app is up and running, even when you're away or asleep!

Install Dependencies

Ghost and PM2 both run on Node. So we'd need to install Node and Git on both our development and production environments. We also need to install nginx on our production server.

We assume you're using Ubuntu in both environments.

PRO TIP - Before installing new software using the apt-get package manager, make it a habit to run sudo apt-get update first, which will ensure our local package index is up-to-date.

Development & Production
$ apt-get install git
$ curl -sL | sudo -E bash -
$ apt-get install -y nodejs
$ apt-get install nginx
$ npm install pm2 -g

Install Ghost

On your development environment, download the source file. Extract the archive and move the entire directory to where you normally keep your projects.

Then cd into your directory and run:

$ npm install --production

This might take a minute or two to download and install any dependencies specified in the package.json file.

Then, simply run:

$ npm start
Ghost is running in development... 
Listening on 
Url configured as: http://localhost:2368 

On your development environment, make sure you run npm start without --production, otherwise, you'll have to restart ghost every time in order to observe your changes.

If you haven't changed the settings, you should be able to see your blog at http://localhost:2368/

You have a blogging platform installed and running in 3 steps!

Setting Up a Local User

http://localhost:2368/ghost/ is the URL for your admin panel. The first time you visit this it'll prompt you to create a new admin user. Follow the on-screen instructions.

And you should be greeted with this administrative panel.

Now, don't start writing now, because this is still your development environment. So let's go ahead and deploy it.


Take a look inside the config.js file and you'll find Ghost allows you to set different environment variables.

Change production.url to the URL of you blog. This includes the protocol as well as the domain name; i.e. instead of

Use your production server's IP address if you do not have a domain name.

The production.mail property should contain an object that carries our SMTP server configurations. Since it's a massive hassle to set up your own mail server, we'll use Mandrill instead.

Configuring Email with Mandrill

At the moment, all setting up emails will do is allowing you to reset forgotten passwords. But it's a good (and easy) thing to set up anyways.

Sign Up for a free Mandrill account. Then follow the instructions to set up your sending domain. This is so that the recipient's mail server knows Mandrill is authorized to send emails with that domain.

For a more detailed walk-through, see Configuring SMTP with Mandrill on DigitalOcean.

After you've verified your domain, use the provided credentials to construct the production.mail object in the config.js file. It might look something like this:

mail: {
    transport: 'SMTP',
    options: {
        service: 'Mandrill',
        host: '',
        port: 587,
        auth: {
            user: '',
            pass: '<your-api-key>'

By default, emails will be sent with a From header of Blog Title <>. So if you're using a subdomain for your blog, like we are (, then your emails will be sent from, which is not what you want.

To fix this, specify the name and email in the production.mail.from property. Ours looks like this: from: '"The Brewing Press" <>'

Version Tracking with Git

So we have our code ready to deploy. What we'll do now is to commit our code to a local development repository, push it to a remote repository, and pull the code to our production server. To do this, we use Git.


On your development environment, cd into your Ghost directory and run:

$ git init
Initialized empty Git repository in /home/daniel/projects/brewing-press/.git/

This creates an empty, local, repository. Next, we commit the initial sets of files into history. But wait! We don't want to track the content. So we need to add a .gitignore file with the following content:


Next, add the files and commit them into history.

$ git add -A
$ git commit -m "Initial commit with Ghost version 0.7.5"
Adding Remote Repository

Next, create a new remote repository on BitBucket or GitHub and add it as a remote to your local repository.

$ git remote add origin https://<username><owner>/<repository-name>.git

Now push the code onto the remote repository.

$ git push -u origin --all
$ git push -u origin --tags
Production Server

Access your production environment and git clone the repository to an appropriate location.

$ git clone https://<username><repo-owner>/blog.git

Now you have the code on your production server.

Installing and Running Ghost in Production

Similar to what you did locally, enter the Ghost directory and run:

$ npm install --production
$ npm start --production

Make sure you add --production after npm start.

Now, if you have not set up any firewalls (which you should), you'd be able to access the blog with <your-domain>:2368.

This is not ideal! We want people to visit our blog using blog.<your-domain>. For this, we'll use NGINX to act as a reverse proxy. While at it, we'll use it to cache some static files also.

Setting Up NGINX

Next we need to set up a server block (akin to 'VirtualHost' if you're used to Apache HTTP Server). Whenever a request is sent to the NGINX server, it checks each server blocks' server_name and listen directives to determine what to do with the request.

In our case, we want NGINX to listen for and forward the request to, which is where our Ghost blog is running at.

Server blocks are stored in the /etc/nginx/sites-available directory. So create a new file and paste in the following:

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


    location ~ ^/(assets/|images/|img/|javascript/|js/|css/|stylesheets/|flash/|media/|static/|robots.txt|humans.txt|favicon.ico) {
        root /srv/www/blog/content/themes/brew;
        access_log off;
        expires 1h;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_http_version 1.1;

Change the value for server_name to your own domain. If this is the only server block NGINX is serving,change the listen directives to:

listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

Note that we've also set up NGINX to serve and cache static files such as assets, images, CSS files and many more. This is done inside the location ~ block. Caching relieves unnecessary stress on our server.

Change the value for root to the directory of where your theme is. (Yes, this does mean every time you change your theme, you'll have to update this value, but the benefits of caching and the infrequency of changing theme means this is still a good idea)

Be careful, however, to not set the expires property too long during development, as you want changes reflected quickly after deployment. We'd recommend 1h at the beginning, and once development has finished, change it to 1d or 7d.

Enabling the Server Block

Server blocks are defined in /etc/nginx/sites-available, but are only activated when they're inside /etc/nginx/sites-enabled. So next, we need to symlink our server block into the /etc/nginx/sites-enabled directory.

$ ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/

Next, cd into /etc/nginx/sites-enabled/. If you see a default symlink there, delete it.

Now, everything should be fine. Just to make sure, run:

$ nginx -t

This tests the configuration files to ensure everything is fine. If it is fine, reload NGINX.

$ nginx -s reload

Now, given you've set up your DNS properly, going to your blog's domain should now show your Ghost blog!

If it doesn't show, try starting the server using service nginx restart or /etc/init.d/nginx restart

Keeping Ghost Running with PM2

Right now, if you exit your production environment, Ghost will stop running and you'll see an unfriendly 502 Bad Gateway error.

As a quick fix, you can start a screen and run your app there, which will persist even after you log out.

$ screen

But if your app crashes for whatever reason, it doesn't restart it for you. Enter PM2.

PM2 is a process manager, which means it will run your Ghost app even after you log off your server, and it will restart the app should it crash.

There are other process managers out there, such as supervisor and forever, but I find PM2 to be the most feature-rich.

Now, instead of running

$ npm start --production

We run

$ NODE_ENV=production pm2 start index.js --name "Ghost"

To make sure it's running and no errors occurred, run:

$ pm2 status

If you placed your files in /srv/ or /var/www, make sure you run pm2 as root, otherwise it will return an error.

Keeping PM2 running

PM2 will keep your app running when it crashes, but what if your server crashes? What keeps PM2 running?

To ensure pm2 starts up after a system reboot, simply run pm2 startup as the root user.

You can also run pm2 startup as a non-root user, pm2 will churn out a command that you'll then need to run as root.

$ pm2 startup
[PM2] You have to run this command as root. Execute the following command:
  sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u blog --hp /home/blog"

Updating Your Code

If you make changes to the code (e.g. changing theme), git add and git commit your changes, git push them to the remote repository, and then git pull it.

Then to reload your code without any downtime, run:

$ pm2 reload <name>

Where <name> is the 'App name' in the table shown when you run pm2 status.

And we're done!

Daniel Li

Full-stack Web Developer in Hong Kong. Founder of Brew.

Hong Kong