Learn How To Deploy Ghost v0.11 LTS on Debian 8

October 27, 2019

Table of Contents

If you are using a different system, please check our other tutorials.

Ghost is an open source blogging platform that has been gaining popularity among developers and ordinary users since its 2013 release. It puts focus on content and blogging. The most attractive thing about Ghost is its simple, clean, and responsive design. You can write your blog posts from a mobile phone. Content for Ghost is written using the Markdown language. Ghost is a perfect fit for individuals or small groups of writers.

In this guide, we are going to set up and deploy a secure Ghost v0.11.x LTS blog on an Debian 8 VPS using Let’s Encrypt, Certbot, Node.js, NPM, NGINX and MySQL.

Requirements

  • Register (purchase) a domain name.
  • Debian 8 server instance with minimum of 1GB RAM.
  • Sudo user.

Before you begin

  1. Check Debian version:

    lsb_release -ds
    # Debian GNU/Linux 8.9 (jessie)
    
  2. Create a new non-root user account:

    adduser johndoe --gecos "John Doe"
    
  3. Make it superuser by adding it to sudo group:

    usermod -aG sudo johndoe
    
  4. Switch to the new user:

    su - johndoe
    
  5. Update your operating system’s software:

    sudo apt-get update && sudo apt-get upgrade -y
    
  6. Set up the timezone:

    sudo dpkg-reconfigure tzdata
    
  7. Install the required tools:

    sudo apt-get install -y build-essential zip unzip git apt-transport-https
    
  8. Reboot system if required:

    sudo shutdown -r now
    

Install Certbot

NOTE: Before starting this step, ensure that you have set DNS records for your domain.

We are going to use Let’s Encrypt CA and EFF‘s Certbot client to obtain SSL/TLS certificate for our Ghost blog. Don’t forget to replace all instances of blog.domain.tld with your domain name.

  1. Install Certbot (formerly Let’s Encrypt client) certificate management software made with Python:

    sudo -s
    printf "deb http://ftp.debian.org/debian jessie-backports main" >> /etc/apt/sources.list.d/jessie-backports.list
    exit        
    sudo apt-get update
    sudo apt-get install -y certbot -t jessie-backports
    
  2. Check Certbot version:

    certbot --version
    # certbot 0.10.2
    
  3. Obtain RSA certificate by using standalone authentication method (plugin):

    sudo certbot certonly --standalone --domains blog.domain.tld --rsa-key-size 2048 --must-staple --email admin@domain.tld --agree-tos
    # IMPORTANT NOTES:
    #  - Congratulations! Your certificate and chain have been saved at 
    /etc/letsencrypt/live/blog.domain.tld/fullchain.pem. 
    #  Your cert will expire on YYYY-MM-DD. . . .
    #  . . .
    

    After going through previous steps, your certificate and private key will be in the /etc/letsencrypt/live/blog.domain.tld directory.

Install Node.js and NPM

NOTE: Ghost currently supports Node.js versions 4.5+ and 6.9+ only.

Ghost is built on Node.js. We are going to install the recommended version for Ghost which is v6 Boron LTS at the time of this writing.

  1. Download and install Node.js v6 LTS:

    curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
    sudo apt-get install -y nodejs
    
  2. Check Node.js and NPM version:

    node -v && npm -v
    # v6.11.2
    # 3.10.10
    

Install MySQL

By default, Ghost comes configured to use an SQLite database, which requires no configuration.

Alternatively Ghost can also be used with a MySQL database by changing the database configuration. You must create a database and user first, you can then change the existing sqlite3 config.

  1. Download and install MySQL:

    sudo apt-get install -y mysql-server
    

    NOTE: During the installation, you will be prompted for MySQL “root” user password. You should set a secure password for the MySQL “root” user.

  2. Check MySQL version:

    mysql --version
    # mysql  Ver 14.14 Distrib 5.5.57, for debian-linux-gnu (x86_64) using readline 6.3
    
  3. Check if MySQL daemon has started and is running:

    sudo systemctl status mysql.service
    sudo systemctl is-enabled mysql.service
    
  4. Run the mysql_secure_installation script to secure your database a bit:

    sudo mysql_secure_installation
    
  5. Log into MySQL as the root user:

    mysql -u root -p
    # Enter password:
    
  6. Create a new MySQL database and user:

    create database dbname;
    grant all on dbname.* to 'user' identified by 'password';
    
  7. Exit MySQL:

    exit
    

Install NGINX

  1. Download and install the latest mainline version of NGINX from the official NGINX repository:

    wget https://nginx.org/keys/nginx_signing.key
    sudo apt-key add nginx_signing.key
    rm nginx_signing.key
    sudo -s
    printf "deb https://nginx.org/packages/mainline/debian/ `lsb_release -sc` nginx ndeb-src https://nginx.org/packages/mainline/debian/ `lsb_release -sc` nginx n" >> /etc/apt/sources.list.d/nginx_mainline.list
    exit
    sudo apt-get update
    sudo apt-get install -y nginx nginx-module-geoip nginx-module-image-filter nginx-module-njs nginx-module-perl nginx-module-xslt nginx-nr-agent
    
  2. Verify that it is installed by checking the NGINX version:

    sudo nginx -v
    # nginx version: nginx/1.13.4
    
  3. Check status, enable and start NGINX service (daemon):

    sudo systemctl status nginx.service # inactive (dead)
    sudo systemctl enable nginx.service
    sudo systemctl start nginx.service
    
  4. Create /etc/nginx/ssl directory and generate a new Diffie-Hellman (DH) group:

    sudo mkdir -p /etc/nginx/ssl
    sudo openssl dhparam -out /etc/nginx/ssl/dhparams-2048.pem 2048
    
  5. Create log directory for blog.domain.tld virtual host:

    sudo mkdir -p /var/log/nginx/blog.domain.tld
    
  6. Configure NGINX as a HTTP(S) reverse proxy server:

    sudo vim /etc/nginx/conf.d/ghost.conf
    
  7. Paste the following in /etc/nginx/conf.d/ghost.conf:

    # domain: blog.domain.tld
    # public: /var/www/ghost
    upstream ghost_app {
        server 127.0.0.1:2368;
        keepalive 32;
    }
    server {
        listen [::]:80 default_server;
        listen 80 default_server;
        listen [::]:443 ssl http2 default_server;
        listen 443 ssl http2 default_server;
        server_name blog.domain.tld; # Change to your domain/hostname
        root /var/www/ghost; # Change to the path where Ghost is
        error_log /var/log/nginx/blog.domain.tld/error.log;
        access_log /var/log/nginx/blog.domain.tld/access.log;
        client_max_body_size 100M;
        ssl_certificate /etc/letsencrypt/live/blog.domain.tld/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/blog.domain.tld/privkey.pem;
        ssl_dhparam ssl/dhparams-2048.pem;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
        ssl_prefer_server_ciphers on;
        ssl_buffer_size 4K;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50M;
        ssl_session_tickets off;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate /etc/letsencrypt/live/blog.domain.tld/chain.pem;
        resolver 8.8.8.8 8.8.4.4 valid=300s;
        location / {
            proxy_pass http://ghost_app;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_hide_header X-Powered-By;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
    
  8. Save and test NGINX configuration for syntax errors:

    sudo nginx -t
    
  9. Reload NGINX configuration:

    sudo systemctl reload nginx.service
    

Install Ghost

NOTE: If you want to host multiple Ghost blogs on same VPS, each Ghost instance must be running on a separate port.

  1. Create the document root directory:

    sudo mkdir -p /var/www/
    
  2. Create a new ghost user:

    sudo adduser --shell /bin/bash --gecos 'Ghost application' --disabled-password ghost
    
  3. Download Ghost:

    curl -L https://github.com/TryGhost/Ghost/releases/download/0.11.11/Ghost-0.11.11.zip -o ghost.zip
    
  4. Unzip Ghost into the /var/www/ghost directory (recommended install location):

    sudo unzip -uo ghost.zip -d /var/www/ghost
    rm ghost.zip
    
  5. Navigate to the new ghost directory:

    cd /var/www/ghost
    
  6. Change the ownership of the /var/www/ghost directory:

    sudo chown -R ghost:ghost .
    
  7. Switch to new ghost user:

    sudo su - ghost
    
  8. Navigate to document root /var/www/ghost:

    cd /var/www/ghost
    
  9. Install Ghost with production dependencies only. When this completes, Ghost is installed:

    npm install --production
    
  10. Configure Ghost by changing url, mail and database property of production object inside of config.js file:

    cp config.example.js config.js
    vim /var/www/ghost/config.js
    var path = require('path'),
        config;
    config = {
    // ### Production
    // When running Ghost in the wild, use the production environment.
    // Configure your URL and mail settings here
    production: {
        url: 'https://blog.domain.tld',
        mail: {
            transport: 'SMTP',
            options: {
                service: 'Mailgun',
                auth: {
                    user: '',
                    pass: ''
                }
            }
        },
        database: {
            client: 'mysql',
            connection: {
                host: '127.0.0.1',
                user: 'your_database_user',
                password: 'your_database_password',
                database: 'your_database_name',
                charset: 'utf8'
            },
            debug: false
        },
        // . . .
        // . . .
    

    NOTE: You should configure mail settings also. Consult the official Ghost documentation on how to do that.

  11. Start Ghost in production environment:

    npm start --production
    

    Ghost will now be running. Both blog front-end and admin interface are secured with HTTPS and HTTP/2 is working also. You can open your browser and visit the site at https://blog.domain.tld. Don’t forget to replace blog.domain.tld with your domain name.

  12. Shut down the Ghost process by pressing CTRL + C and exit from the ghost user back to the root user:

    exit
    

Running Ghost as a system service

If you close your terminal session with your VPS, your blog will also go down. That’s not good. To avoid this, we are going to use Systemd. It will keep our blog up 24/7.

  1. Create ghost.service Systemd unit file. Run sudo vim /etc/systemd/system/ghost.service and copy/paste the following content:

    [Unit]
    Description=Ghost - the professional publishing platform
    Documentation=https://docs.ghost.org/v0.11.11/docs
    After=network.target
    [Service]
    Type=simple
    # Edit WorkingDirectory, User and Group as needed
    WorkingDirectory=/var/www/ghost
    User=ghost
    Group=ghost
    ExecStart=/usr/bin/npm start --production
    ExecStop=/usr/bin/npm stop --production
    Restart=always
    SyslogIdentifier=Ghost
    [Install]
    WantedBy=multi-user.target
    
  2. Enable and start ghost.service:

    sudo systemctl enable ghost.service && sudo systemctl start ghost.service
    
  3. Check ghost.service status:

    sudo systemctl status ghost.service && sudo systemctl is-enabled ghost.service
    
  4. Navigate to https://blog.domain.tld/ghost/ and create a Ghost admin user. Do this as soon as possible!

Conclusion

That’s it. We now have a fully functional Ghost blog. Your server is delivering content via HTTP/2 when supported by the client. If you want to change the default Ghost theme called Casper to a custom one, you can just download and unzip the theme into the /var/www/ghost/content/themes folder and select it via Ghost admin interface, located at https://blog.domain.tld/ghost.

Need help?

Do you need help setting up this on your own service?
Please contact us and we’ll provide you the best possible quote!