Dude looks like a Ghost

Ghost is a beautiful blogging platform built upon the exciting Node.js technology. Well, exciting only in the mind of stupid geeks that never escaped the cellar their parents locked them in. But cellar dwelling geeks are not today's topic. Technology blogs chasing advertisement clicking nuts even described Ghost as beautiful long before John O’Nolan - author of Ghost - wrote a single line of code.

Excuse me thus for being a bit sceptical, so please do stay away from technology blogs because one day, the parents of the authors will be sober up and find themselves in dire need of having a bottle of wine. That’s when they go down into the cellar and discover that their geek son hacked into their internet and pretends to understand what makes the world go round. This will get pretty ugly real fast.

A few breakfasts ago, Ghost was released to some idiots that supported the effort on Kickstarter because they just broke up with their girlfriend and suddenly had some money left at the end of the day. By chance there was none of those nice emails from any gentleman from Nigeria to give their bank data so at the end they gave it to another random stranger on the internet. The result is the same: the money is gone.

One of my next ramblings will expand on how much fun (or not) I have blogging with Ghost but by now the most burning issue on your mind must be how to get rid of your girlfriend and have some money left at the end of the day.

I hear your muffled moaning behind the cellar door! But since you are locked into the cellar you really only have an imaginary girlfriend and still have no money. Typically geeks having the wrong priorities. Sorry, I haven’t had my Orval yet today, so I am not in the mood to help some sore losers that have no money to pay for my beer.

Installing Ghost the secure way

You really want to protect your cat pictures and want to keep nasty people out of your nice and shiny new server. Ghost is still in beta and is build upon alpha software, so there will be bugs and security issues. How to keep this manageable until it is a mature platform?

I do not own a nice server since I have my priorities right so my money goes to beers. That’s why I decided to rent a 5 USD/month Linux server at DigitalOcean. They have some very nice hardware hosted in cellars that feels like home to the geeks locked in there. These are the smart ones that escaped the cellar of their parents.

John O’Nolan promised that the first beta version of Ghost would have a proper installation procedure but as usual this was wishful thinking or maybe I have to look up the meaning of the word “proper” again.

So next is my installation procedure. Boring, but It Just Works (™). Most of the installation procedures on the web end up as a procedure that is almost, but not quite, entirely unlike a proper installation procedure (thanks, Douglas Adams!).

Yes, I made some shortcuts, but you are probably too young to know the full story now. Please wait for a next post where I will delve deeper and tell you everything about chroots and the dangers that lies within.

Before you start another Mountain Dew

The following procedure will teach you on how to install Ghost, protected by Nginx (as a proxy) and ModSecurity. Your password and other secrets will be safe because we will use a SSL certificate.

You should have already registered a domain name for your blog. I am a cheap bastard and used one.com. My website will be hosted there, but my blog will run on a Digital Ocean droplet.

First of all, running a server (droplet) at DigitalOcean is not for dweebs that think that one should not login as “root”. Droplets are for real men that hate “best practices” and acknowledge that one more beer will always make things right. Or on the contrary, things might not be all right, but a few more beers will no doubt make you accept the universe again.

For those refusing to work as “root”, just put “sudo” before every command. Note: I used Ubuntu 12.04 LTS as operating system.

Modify the root password

You will sign on to your droplet as root using the SSH protocol and a password. Make sure to modify the password the first time you sign-on to your droplet, since the original password was sent to you by email.

Use a very long and complex password since the entire internet and the NSA will be there to brute-force your choice. A long password is not the name of your imaginary girlfriend, but something like “Dude,thisisreallypainful!”.



After that you are asked to type in the new root password twice. Finally, your root user has a new password.

A better idea might be certificates but the problem is that I know you will be too lazy to protect the private key on your laptop with a strong password. Unfortunately, bad guys know how to get text files out of your laptop.

Do not forget the basics

Before doing anything else make it a habit to update the software and operating system of your droplet:

We need some more software:

Configure a non-standard port for SSH

Tons of script kiddies will try to use the standard port (22) of the SSH protocol to brute-force your password. Keep them out by modifying the standard port (here in the example port 6543):

YOUR.DOMAIN.NAME.cer The already installed fail2ban software will protect against brute-forcing, no additional configuration is necessary.

Protect your droplet with a firewall

This is done by modifying the iptables configuration. First we will make certain that all local traffic is accepted.
YOUR.DOMAIN.NAME Then allow all existing connections:
blog.igbuend.com We will allow only connections to SSH (6543 or whatever you used in the earlier steps) and to the web server (80, 443):
Node.js Make those rules persistent using the package iptables-persistent.
.tar.gz After this, reboot the server and grab a cold beer:


Next, you will have to sign-in again using SSH, but don’t forget you have to use the newly configured port (6543 in our example)!

Request SSL certificates

Enabling SSL in the web-server protects the data travelling between browser and server (such as your password) against prying eyes.

It is possible to create your own certificates (self-signed) or buy them from a certificate authority (CA). Self-signed certificates will result in a security warning when users visit your site. However, SSL certificates from a CA cost real money, money that is better spent buying beers.

Luckily the nice guys from CACERT will create your SSL certificates for free. Unfortunately some browsers still display a security warning, but some accept the certificate. There is absolutely no reason to only consider paid SSL certificates “secure”. Anyway, if you want to get rid of the pop-up, direct your users to install the root certificate of CACERT.

You will need to create a private key, and generate a certificate signing request (CSR).
When ordering a certificate, you will need to copy the content of the .csr file to a form at CACERT. In return they will give you a certificate. Copy this to a file, e.g. YOUR.DOMAIN.NAME.cer.

As always, modify the YOUR.DOMAIN.NAME to the real name of your blog (e.g. blog.igbuend.com).

Install Ghost

First we need to install Node.js. Download the latest .tar.gz archive (in this case 0.10.21) from Nodejs.org.
blog.igbuend.com Go for for a few more beers and:
/var/www/blog.igbuend.com directory Create a user to run Ghost and use a strong password (modify the YOUR.DOMAIN.NAME to your real domain name, e.g. blog.igbuend.com):
production Get the ghost source (at this moment only available to Kickstarter backers) and unzip the archive. Transfer your local file to the server in the /var/www/blog.igbuend.com directory.
Now unzip the ghost files:
YOUR.DOMAIN.NAME Make sure that production is the default and that Ghost uses your real domain name (again, modify YOUR.DOMAIN.NAME to your real domain):
CTRL-O We need to make certain that Ghost will start automatically after a reboot. Luckily I was able to steal someones script for this. Open the file in the editor:
Copy the following content (do not forget to change the name):
YOUR.DOMAIN.NAME Save the script by CTRL-O and leave the nano editor with CTRL-X. Make the script executable with:

    @reboot /var/www/YOUR.DOMAIN.NAME/starter.sh

We use crontab to start this script after a reboot:

Paste the following line as last line (again, change YOUR.DOMAIN.NAME to your real domain):

    @reboot /var/www/YOUR.DOMAIN.NAME/starter.sh

Save the script. Exit the shell and you are root again. Create the logfile:

    touch /var/log/nodelog.txt
    chown ghost.ghost /var/log/nodelog.txt

Install Nginx and ModSecurity

We will instal Nginx as front-end to Ghost and run it with special filtering software (ModSecurity). The goal is to protect our blog against all kinds of hacking activity. Unfortunately, this means compiling Nginx and ModSecurity. Repeat this step every time Nginx needs to be updated because of security issues!

First download and compile the latest ModSecurity (here version 2.7.5):

    wget https://www.modsecurity.org/tarball/2.7.5/modsecurity-apache_2.7.5.tar.gz
    tar xzvf modsecurity-apache_2.7.5.tar.gz
    cd modsecurity-apache_2.7.5
    ./configure --enable-standalone-module
    make install

Next we create a user for nginx:

    adduser --system --no-create-home --disabled-login --disabled-password --group nginx

Download and compile the latest version of Nginx (here version 1.5.6).:

    wget http://nginx.org/download/nginx-1.5.6.tar.gz
    tar zxvf nginx-1.5.6.tar.gz
    cd nginx-1.5.6
    ./configure --add-module=../modsecurity-apache_2.7.5/nginx/modsecurity --with-http_ssl_module --prefix=/opt/nginx --user=nginx --group=nginx  --without-http_scgi_module --without-http_uwsgi_module --without-http_fastcgi_module --without-http_autoindex_module --with-http_spdy_module
    make install

Create the following file (see references for where I stole this one):

    nano /etc/init/nginx.conf

Copy the following content to make Nginx autorun:

    # nginx
    description "nginx http daemon"
    author "Philipp Klose <me@'thisdomain'.de>"
    start on (filesystem and net-device-up IFACE=lo)
    stop on runlevel [!2345]
    env DAEMON=/opt/nginx/sbin/nginx
    env PID=/opt/nginx/logs/nginx.pid
    expect fork
    respawn limit 10 5
    #oom never
    pre-start script
    $DAEMON -t
    if [ $? -ne 0 ]
      then exit $?
    end script
    exec $DAEMON

Copy the ModSecurity configuration file to the Nginx directory:

    cp ~/modsecurity-apache_2.7.5/modsecurity.conf-recommended /opt/nginx/conf/modsecurity.conf

We need some more rules for ModSecurity:

    cd /opt/nginx/conf/modsecurity.conf
    wget https://github.com/SpiderLabs/owasp-modsecurity-crs/tarball/master
    tar zxvf master --wildcards --no-anchored ‘*.conf’ --strip-components 2

We need to relax some ModSecurity rules to make it work:

    sed -i '/981172/s/^/# /' modsecurity_crs_41_sql_injection_attacks.conf
    sed -i '/981245/s/^/# /' modsecurity_crs_41_sql_injection_attacks.conf 
    sed -i '/981243/s/^/# /' modsecurity_crs_41_sql_injection_attacks.conf

Configure ModSecurity to filter Cross-Site-Scripting (XSS) and SQL Injection (SQLi) attacks:

    sed -i 's/DetectionOnly/On' modsecurity.conf
    sed -i 's/13107200/100000000' modsecurity.conf

Open your editor to make some more modifications:

    nano modsecurity.conf

Add the following 3 lines to the end of the file:

    SecDefaultAction "log,deny,phase:1"
    Include "modsecurity_crs_41_sql_injection_attacks.conf"
    Include "modsecurity_crs_41_xss_attacks.conf"

Save the file.

The only thing left is modifying the Nginx configuration file. Note that I had to disable ModSecurity for file-upload (I am investigating why):

    nano nginx.conf

Modify the content of the file to read (the number of worker_processes can be put to the number of CPUs):

    worker_processes  1;
    events {
        worker_connections  1024;
    http {
        server_names_hash_bucket_size 64;
        types_hash_max_size 2048;
        server_tokens off;
        include mime.types;
        default_type application/octet-stream;
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-Content-Type-Options nosniff;
        sendfile on;
        keepalive_timeout  10;
        gzip on;
        gzip_comp_level 6;
        gzip_min_length 150;
        gzip_proxied any;
        gzip_types text/plain text/xml text/css application/json application/javascript;
        gzip_vary on;
        proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=1000M inactive=60m;
        proxy_temp_path /var/tmp;
        server {
            listen 80;
            listen 443 default ssl;
            server_name YOUR.DOMAIN.NAME;
            ssl_certificate /opt/nginx/conf/YOUR.DOMAIN.NAME.cer;
            ssl_certificate_key /opt/nginx/conf/YOUR.DOMAIN.NAME.key;
            ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
            ssl_ciphers RC4:HIGH:!MEDIUM:!aNULL:!MD5:!DH:!EDH;
            ssl_prefer_server_ciphers on;
            ssl_session_cache shared:SSL:10m;
            ssl_session_timeout 10m;
            if ($scheme = http) {
                return 301 https://$server_name$request_uri;
        location / {
            ModSecurityEnabled on;
            ModSecurityConfig modsecurity.conf;
            proxy_redirect off;
            proxy_read_timeout 180s;
            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 $host;
            proxy_cache one;
            proxy_cache_key "$scheme$host$request_uri";
            proxy_hide_header X-Powered-By;
               location /ghost/upload {
            ModSecurityEnabled off;
            ModSecurityConfig modsecurity.conf;
            proxy_redirect off;
            proxy_read_timeout 180s;
            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 $host;
            proxy_cache one;
            proxy_cache_key "$scheme$host$request_uri";
            proxy_hide_header X-Powered-By;

        #error_page  404 /404.html;
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root html;
        location ~* \.(db|hbs|conf)$ {
             deny all;

Save the file. Do not forget to modify YOUR.DOMAIN.NAME to your real domain name.

Now move the certificate and key to the Nginx configuration directory:

    mv YOUR.DOMAIN.NAME.cer /opt/nginx/conf/
    mv YOUR.DOMAIN.NAME.key /opt/nginx/conf/

Check the validity of the nginx.conf file and fix errors where necessary:

    /opt/nginx/sbin/nginx -t

The output should look like:

    nginx: the configuration file /opt/nginx/conf/nginx.conf syntax is ok
    nginx: configuration file /opt/nginx/conf/nginx.conf test is successful

Next steps

There is nothing left to but reboot:


I will work on improvements:

  • review file system permissions
  • chrooted installation
  • finetune modsecurity.conf
  • finetune (better caching) nginx.conf
  • switch to PM2 instead of using Forever

My first cat picture

Unfortunately my cat escaped when I was having some beers, so here is a picture of me drinking my first beer of the day.

Herman having a beer

I hope you enjoyed this blog, I certainly did. Looking forward to your first cat picture.


Why do you insult geeks?

Stupid geeks! I never intended to insult all geeks, I only stated that stupid geeks are the ones that never escaped the cellar where their parents locked them in.

Smart geeks are the ones that did manage to escape after marauding their parents wine cellar.

Why don’t locked up geeks drink the wine?

Because they only like Mountain Dew. See above.

What do you really think of Ghost?

Did you even read my blog? I told you that I will tell you all there is to know in due course in one of my next posts. At this moment I believe that Ghost will be the greatest platform for people without writing skills but having nice pictures of cats.


01 October 2013
New version Node.js, Ghost, SPDI support NGINX

26 September 2013
first version


Some links I found useful while drinking lots of beers:

Herman Stevens

Just some guy on the internet. Loves technology, diving, travelling, photography and Belgian Trappist beers.