Deploy Ghost on Debian with nginx and PM2


With the release of Ghost 1.0, this guide is now deprecated. Please check out the official documentation on how to install the latest version.


This is a step by step guide on how to deploy Ghost on Debian (Jessie) using nginx as a reverse-proxy, and PM2 as our Node.js process manager. This guide is part of the Deploy Ghost series and is split into three parts - Deploying, Securing, and Optimising. This is part one and once we’re done, we will have a fully functional Ghost blog (running over HTTP).

Before you start typing into your console, please take the time to research and understand what it is that you’re actually executing on your server. This process worked for me, and might change as the applications have new patches and versions rolled out.


If you want to have your own Ghost server, but don’t want to manage it, the Ghost Foundation offer a hosted solution. If you want to get your hands dirty and help out, they’re happy for anyone to contribute.

Get Utilities

I’m assuming we’re running as root for the time being. Once we create another user, that’s when we’ll start executing commands as them. We’ll start by getting some utility software that we can use later on.

1
2
3
apt-get update
apt-get install curl
apt-get install zip

Install and Prepare nginx

The Debian nginx repository tends to lag behind in release versions. At the time of writing, it has version 1.6.2, which is missing some security features we want later on. To get the latest version, we need to add in a new source for nginx and get their GPG key.

So log in as root and let’s edit our sources list.

1
nano /etc/apt/sources.list

We want to add the below to the bottom of the sources.list file.

1
2
3
# nginx
deb http://nginx.org/packages/mainline/debian/ jessie nginx
deb-src http://nginx.org/packages/mainline/debian/ jessie nginx

Now we need to grab the GPG key and add it. Once it’s been added, we can delete the key.

1
2
curl -O https://nginx.org/keys/nginx_signing.key && apt-key add ./nginx_signing.key
rm nginx_signing.key

Update our repositories so we can download and install nginx.

1
2
apt-get update
apt-get install nginx

We need to setup nginx so that we can easily administer it later. To do that, we’ll make /var/www/ our webroot. We will also use /etc/nginx/sites-available to store our individual configuration files, and create a symlink to /etc/nginx/sites-enabled when we’re reading for them to go live.

1
2
3
4
5
cd /var
mkdir www
cd /etc/nginx
mkdir sites-available
mkdir sites-enabled

In order for nginx to know about our sites-enabled configuration files, we have to tell it to include any configuration files it finds in there. We make this change in the nginx.conf file.

1
nano nginx.conf

Add the below string in to the end just after include /etc/nginx/conf.d/*.conf; and save the file.

1
include /etc/nginx/sites-enabled/*;

Now we can remove the default configuration file.

1
2
cd conf.d
rm default.conf

Add a user

We want a dedicated user to run Ghost and the associated processes, so let’s go ahead and create a user called ghost and give it sudo access.

1
2
adduser ghost
adduser ghost sudo

Log out of root and log into ghost. Going forward we’ll be working from the user ghost.

Install Node.js

We want to install Node.js with NVM so we don’t need to execute things as root (or with sudo), it also makes managing Node.js that much easier. We’ll download and install it. This command will get NVM version 0.32.1, there might be a new one, so check over here.

1
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash

In order for NVM to take effect, we need to log off and log in again. If you don’t, you will get an error.

As of writing, Ghost supports Node V4 so we can use the Argon LTS to get the latest in V4. We then want to make it our default Node version.

1
2
nvm install --lts=argon
nvm alias default node

Once it’s installed, we can verify the Node and NPM versions with node -v and npm -v.

Install Ghost

To install Ghost we want to get it and extract it to it’s own directory.

1
2
3
4
curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip
mkdir ghost
unzip ghost.zip -d /home/ghost/ghost
cd ghost

Now we need to execute Ghost so it can perform the first run setup.

1
2
npm install --production
npm start --production

When we execute npm start --production, we will need to press ctrl + c to get our console control back. This will shutdown our blog, which is good for now.

Now we need to move our Ghost directory somewhere that nginx can deal with. Since we’re using /var/www/ as our web root, we’ll move it there. We’re done with ghost.zip so we can delete it too.

1
2
3
cd ../
sudo mv /home/ghost/ghost/ /var/www/
rm ghost.zip

Configure Ghost

A simple change to update our URL and Mail settings inside Ghost is needed. We’ll need to change directory to our Ghost install and alter the url and mail settings inside config.js.

1
2
cd /var/www/ghost
nano config.js

We want to change url: 'http://my-ghost-blog.com', to whatever your URL is. I will use example.com. The mail settings should also be changed - there’s a few ways to do it so read the Ghost mail configuration page.

Below is a small extract of what config.js will look like once we’re done.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
config = {
  // ### Production
  // When running Ghost in the wild, use the production environment.
  // Configure your URL and mail settings here
  production: {
    url: 'http://example.com',
    mail: {
      transport: `SMTP`,
      options: {
        service: `Gmail`,
        auth: {
          user: '[email protected]',
          pass: 'yourpassword'
        }
      }
    },
    database: {
      // More content.
    },
  },
  // More content.
}

Configure ‘ghost’ ngnix File

We can create our Ghost configuration file in sites-available, this will be used to let nginx know about Ghost. We will make any nginx changes that relate to Ghost in this file. We’ll call the configuration file ghost.conf.

1
2
3
cd /etc/nginx/
sudo touch /etc/nginx/sites-available/ghost.conf
sudo nano /etc/nginx/sites-available/ghost.conf

Inside our freshly created configuration file, we want to add in the below content and save it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
    listen 80 default_server;
    root /var/www/ghost/;
    server_name example.com www.example.com;
    location / {
        proxy_set_header   Host      $http_host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_pass         http://127.0.0.1:2368;
    }
}

Now we want to create a symlink to enable Ghost inside nginx.

1
sudo ln -s /etc/nginx/sites-available/ghost.conf /etc/nginx/sites-enabled/ghost.conf

In order for our changes to take effect, we need to parse our file to check for errors, and if it’s good then we want to reload nginx.

1
sudo nginx -t && sudo nginx -s reload

If you try and hit your blog now, you’ll be provided with a 502 Bad Gateway error. Our Ghost blog isn’t actually running just yet, so there’s nothing there.

Keep Ghost running with PM2

We’ll use PM2 to make sure Ghost starts up again if the server restarts. PM2 also makes it easier to manage the Ghost process and let’s us easily manage other Node processes.

1
2
3
4
5
cd /var/www/ghost
npm install pm2 -g
NODE_ENV=production pm2 start index.js --name="GhostBlog"
pm2 save
pm2 startup debian

After we execute pm2 startup debian, we’ll get an output saying you need to execute another command. You’ll need to type it in exactly as it says and execute it otherwise Ghost won’t start when the server restarts.

Ghost is Deployed

We’ve now got a functioning blog, but it’s seriously insecure. If you log in to the Ghost admin panel, those credentials are sent in plain text. That’s really bad. Part two, Securing Ghost will cover that particular issue.

Series Links

References