Installing Ghost on Ubuntu, Nginx and MySQL

Update November, 21 2015

These instructions are years out of date, and far less secure than they should be. If you want to self-host Ghost you should be finding another tutorial


I have spent the last few hours getting my new copy of Ghost installed and working on a fresh Ubuntu 13.04 server with nginx and forever. Someone out there might find this information useful, so here it is.

Step 1 - Installing Ubuntu

This step is really best answered by the hundreds of pages out there that explain it. I will assume that this step is already complete and move on to the important stuff.

Step 2 - Securing your installation

This is another step I feel is best explained by someone who has already done the big work of putting it together, I have taken the relevant bits from this post:

How to Secure an Ubuntu 12.04 LTS Server - Part 1 The Basics

The article is titled for 12.04, but it works perfectly fine on 13.04. Nothing related to that article has changed.

Here is all of the information from that article that you need to know for a simple server that will only be hosting Ghost.

  1. SSH - Disable root login and change port
  2. Install and configure Firewall - ufw
  3. Secure shared memory - fstab
  4. Protect su by limiting access only to admin group
  5. Harden network with sysctl settings
  6. Scan logs and ban suspicious hosts - Fail2Ban

SSH Hardening - disable root login and change port

The easiest way to secure SSH is to disable root login and change the SSH port to something different than the standard port 22. After changing the port we will update the firewall to reflect the change.

Enter the following:

sudo nano -w /etc/ssh/sshd_config

Change the following lines:

Port 22
PermitRootLogin yes

Set these lines to the values below and save (control-x). 1010 is just something I chose at random for this blog post, replace it with whatever port number you want.

Port 1010
PermitRootLogin no

Its time to restart SSH:

sudo /etc/init.d/ssh restart

Now you can disconnect and reconnect to the port you set earlier. In my example it was 1010.

ssh -p 1010 user@IP

Install and configure Firewall - ufw

Now its time to install a Firewall. UFW - Uncomplicated Firewall is a basic firewall that works very well and easy to configure. UFW manual pages or the Ubuntu UFW community documentation.

Install UFW and enable, enter:

sudo apt-get install ufw

Check the status of the firewall.

sudo ufw status verbose

Allow SSH and Http services. Remember to use the port you chose earlier for your SSH.

sudo ufw allow 1010
sudo ufw allow 80

Now enable the firewall:

sudo ufw enable

Secure shared memory - fstab

Note: This might break on linode, but works fine for me on Digital Ocean. Thanks to UrbainGrandier for this information.

/dev/shm can be used in an attack against a running service, such as httpd. Modify /etc/fstab to make it more secure.

Enter the following:

sudo nano -w /etc/fstab

Add the following line to the bottom and save (control-x). You will need to reboot for this setting to take effect:

tmpfs     /dev/shm     tmpfs     defaults,noexec,nosuid     0     0

Protect su by limiting access only to admin group

To limit the use of su by admin users only we need to create an admin group, then add users and limit the use of su to the admin group.

Enter these commands to create an admin group, add your user to it and lock down /bin/su/:

sudo groupadd admin
sudo usermod -a -G admin <YOUR ADMIN USERNAME>
sudo dpkg-statoverride --update --add root admin 4750 /bin/su

Harden network with sysctl settings

The /etc/sysctl.conf file contains settings related to your network configuration. These edits prevent some very simple attacks with very little work. Instead of explaing edit by edit what to do I am just posting my actual sysctl.conf file:

# /etc/sysctl.conf - Configuration file for setting system variables
# See /etc/sysctl.d/ for additional system variables
# See sysctl.conf (5) for information.

# Uncomment the next two lines to enable Spoof protection (reverse-path filter)
# Turn on Source Address Verification in all interfaces to
# prevent some spoofing attacks

# Uncomment the next line to enable TCP/IP SYN cookies
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5

# Do not accept ICMP redirects (prevent MITM attacks)
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0 
net.ipv6.conf.default.accept_redirects = 0

# Do not send ICMP redirects (we are not a router)
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Do not accept IP source route packets (we are not a router)
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Log Martian Packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Ignore Directed pings
net.ipv4.icmp_echo_ignore_all = 1

Scan logs and ban suspicious hosts - Fail2ban.

Fail2ban scans log files and bans IPs that look malicious. Too many password failures, seeking for exploits, etc.

Install Fail2ban:

sudo apt-get install fail2ban

After the install open up the /etc/fail2ban/jail.conf file for editing:

sudo nano -w /etc/fail2ban/jail.conf

Find this line: (for me its line 93)

# Jails

Delete everything beneath that line and replace it with the content below and save (control-x):


enabled  = true
port     = 888
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 6


enabled  = true
port     = 8888
filter   = sshd-ddos
logpath  = /var/log/auth.log
maxretry = 10


enabled  = true
filter   = recidive
logpath  = /var/log/fail2ban.log
action   = iptables-allports[name=recidive]
       sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log]
bantime  = 604800  ; 1 week
findtime = 86400   ; 1 day
maxretry = 5

Now you need to restart Fail2Ban:

sudo /etc/init.d/fail2ban restart

Installing Node.js

This is actually pretty simple. Run these commands:

sudo apt-get install python-software-properties 
sudo apt-add-repository ppa:chris-lea/node.js 
sudo apt-get update 
sudo apt-get install nodejs

You should now have node.js installed and ready to go.

Installing MySQL

This is another simple one. Run this command:

sudo apt-get install mysql-client mysql-server

While its running it will ask you to set a root password. Make sure to remember this.

Now lets add a couple ghost databases and a user. Enter this command to get into mysql's command line interface: (it will ask for the root password you set earlier)

mysql -uroot -p

You should see output like this:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1269
Server version: 5.5.32-0ubuntu0.13.04.1 (Ubuntu)

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


At the "mysql>" prompt you need to enter these commands one at a time: (replace YOUR_PASSWORD with a password you will remember)

create database ghostdev;
create database ghost;
create user 'ghost'@'localhost' identified by 'YOUR_PASSWORD';
grant all privileges on ghost.* to 'ghost'@'localhost';
grant all privileges on ghostdev.* to 'ghost'@'localhost';
flush privileges;

You now have mysql setup with a production and development database.

Installing Nginx

Installing Nginx is also simple, the configuration gets a bit tricky though. Install it with this command:

sudo apt-get install nginx

Now we need to create the cache directory:

sudo mkdir /var/cache/nginx
sudo chown www-data:www-data /var/cache/nginx

Next up we create the directory for ghost:

sudo mkdir /var/www
sudo chown www-data:www-data /var/www

Its time to put in the new nginx.conf file, open it up for editing:

sudo nano -w /etc/nginx/nginx.conf

Delete everything and replace it with the text below. This is a modified version of the config file from here

user www-data;
worker_processes 4;
pid /run/;

events {
    worker_connections 768;
    # multi_accept on;

http {

    proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m;
    proxy_temp_path /var/tmp;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    gzip on;
    gzip_comp_level 6;
    gzip_vary on;
    gzip_min_length  1000;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    gzip_buffers 16 8k;

    upstream ghost_upstream {
      keepalive 64;

    server {
    listen 80;

    server_name YOUR_DOMAIN www.YOUR_DOMAIN;

    if ($host = 'YOUR_DOMAIN' ) {
            rewrite  ^/(.*)$  http://www.YOUR_DOMAIN/$1  permanent;

#        location ~ ^/(ghost/signup/) {
#                rewrite ^/(.*)$ http://YOUR_DOMAIN/ permanent;
#        }

    location ~ ^/(img/|css/|lib/|vendor/|fonts/|robots.txt|humans.txt) {
      root /var/www/core/client/assets;
      access_log off;
      expires max;

    location ~ ^/(shared/|built/) {
      root /var/www/core;
      access_log off;
      expires max;

    location ~ ^/(favicon.ico) {
      root /var/www/core/shared;
      access_log off;
      expires max;

    location ~ ^/(content/images/) {
      root /var/www;
      access_log off;
      expires max;

    location / {
      proxy_redirect off;
      proxy_set_header   X-Real-IP            $remote_addr;
      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
      proxy_set_header   X-Forwarded-Proto $scheme;
      proxy_set_header   Host                   $http_host;
      proxy_set_header   X-NginX-Proxy    true;
      proxy_set_header   Connection "";
      proxy_http_version 1.1;
      proxy_cache one;
      proxy_cache_key ghost$request_uri$scheme;
      proxy_pass         http://ghost_upstream;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

Now you need to edit the contents and replace everywhere it says YOUR_DOMAIN with your actual domain like

This configuration enforces the www at the front of your domain. This is the code:

    if ($host = 'YOUR_DOMAIN' ) {
            rewrite  ^/(.*)$  http://www.YOUR_DOMAIN/$1  permanent;

If you dont want it forcing users to www.YOUR_DOMAIN then you need to remove that code.

When your done editing it save the file (control-x.)

The next thing this code can do is block people from getting to your signup page. I'll explain that later, right now the code for that is commented out with #s.

You can now restart nginx:

sudo /etc/init.d/nginx restart

Installing and Configuring Ghost

Based on the instructions here.

Download the zip file from your account. Get it unzipped and uploaded. Make sure you put the files into /var/www.

Now go into /var/www and run these commands:

sudo npm install --production
sudo npm install mysql
sudo npm install forever -g

We will be using Forever to keep Ghost alive in the background. This will monitor Ghost and restart it if it ever stops or crashes. To start you need to create a file in /var/www/. This file will be named

sudo nano -w /var/www/

Paste in the script below:


if [ $(ps aux | grep node | grep -v grep | wc -l | tr -s "\n") -eq 0 ]
    export PATH=/usr/local/bin:$PATH
    export NODE_ENV=production
    NODE_ENV=production forever start --sourceDir /var/www index.js >> /var/log/nodelog.txt 2>&1

This script and its crontab are modified versions of information found here

Now enter this command:

sudo chmod +x /var/www/

Next up we want to fix all the permissions:

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

Now we need to add a line to your crontab:

sudo crontab -e

If this is the first time you've used crontab -e then it will ask you which editor to use. I prefer nano for simple edits, but you can choose anything. Place this line at the end of the file and save:

@reboot /var/www/

The Ghost config.js file is up next. Open it up to edit:

sudo nano -w /var/www/config.js

Edit the lines with "url:" right under development and production declarations replacing the default URL with yours:

development: {
    // The url to use when providing links to the site, E.g. in RSS and email.
    url: 'http:// YOUR_DOMAIN',


production: {
    url: 'http:// YOUR_DOMAIN',

Next thing to edit is the database config. Find the "database:" block for development and replace the entire block with this code, changing the password to whatever you set it as:

    database: {
            client: 'mysql',
            connection: {
                    host: 'localhost',
                    user: 'ghost',
                    password: 'YOUR_PASSWORD',
                    database: 'ghostdev',
                    charset: 'utf8'

Do the same for the production "database:" block, with this code:

    database: {
            client: 'mysql',
            connection: {
                    host: 'localhost',
                    user: 'ghost',
                    password: 'YOUR_PASSWORD',
                    database: 'ghost',
                    charset: 'utf8'

The only difference is the database name being changed from ghostdev to ghost.

You are now done. Ghost should be ready to go. Get into the /var/www directory and enter this command:

sudo ./

That should start Ghost, nginx was already running, as was mysql. Visit http://YOUR_DOMAIN and you should see the default page.

Everything else you need to know should be found on the Ghost Usage Document

It will explain intial setup of a user and changing the default settings.