Installing Ghost and Nginx on Ubuntu Server 16.04 LTS

In this post, I'll cover how to deploy and configure a self-hosted Ghost blogging instance from scratch on an instance of Ubuntu Server. These are the same steps I recently followed to configure the blog you are currently reading. If you are at all familiar with Linux/Unix, this is a fairly painless (and fun) process that only takes a few hours to complete. If you wish to skip the introduction and get started, skip ahead to the Installation Steps section of this post.

For around $19/mo you can pay Ghost.org to host and maintain a Ghost blogging platform instance for you. If you aren't a tech-savvy user or are unwilling to commit to maintaining a web server, this might be a good option for you. However, if you are more technical, and are willing to do some routine server maintenance, you can host a Ghost blogging instance for far less than $19/mo. I'm hosting my blog for $5/mo on a VPS, far less than it would cost me to host my blog on Ghost.org.

What is Ghost?

I provided some reasons behind my choice of Ghost as a blogging platform in my previous blog post. Ghost is a free and open source blogging platform written completely in server-side JavaScript, which runs within a Node instance. The direction for this blogging platform is managed by the non-profit Ghost foundation. Ghost's source code is available on GitHub https://github.com/TryGhost/Ghost and distributed under a MIT license. The Ghost Foundation encourages its community of users to contribute to the project. Ghost's code base is clean, well-organized, and intuitive. If you wish to get more involved with the future direction of Ghost, the Ghost Foundation conducts weekly public development meetings and publishes meeting dates on their development site. Ghost's UI and code base are clean and efficient, making it a compelling choice as a blogging platform.

What is Nginx?

Nginx is a free and open source HTTP server and reverse proxy, which similar to Node.js, follows an event-driven (asynchronous) architecture. Nginx is the recommended way to proxy requests for Ghost. Nginx can essentially forward connections from port 80 or 443 to a Node instance running on a different port. You could run Ghost on Node without Nginx, but there are several advantages to using Nginx as a proxy.

  • Security
    • Recall that in Unix/Linux, the first 1024 ports on a server are restricted to processes running under root (admin) user accounts. Web traffic is commonly handled on ports 80 and 443. By using Nginx to proxy requests to a higher port number, Node can run on a higher port under a user account that does not have administrative privileges. If there happened to be a security vulnerability in Node or Ghost, proxying limits the damage that can be done.
    • Nginx is generally considered more efficient at establishing TLS/SSL connection handshakes than Node
    • Nginx serves one role and does it well, is widely deployed and tested, and quickly patched for security vulnerabilities
    • Nginx provides an easy means of blacklisting specific geographical regions or IP addresses from establishing connections
  • Performance
    • Nginx provides extensive request caching options
    • While Node is excellent at handling dynamic web content, Nginx is more efficient at serving static files than Node.
    • Nginx has support for the HTTP/2 protocol, which can greatly reduce page load times. I was able to configure HTTP/2 support on my sever with minimal work.
    • Nginx has robust load-balancing and compression options
  • Control
    • Nginx provides the ability to control or modify outgoing headers
    • Nginx has extensive throttling and connection limiting features
Why Ubuntu Server

There is no compelling reason for my choice of using Ubuntu Server Linux distribution to run Nginx and Ghost. Ubuntu Server is the Linux distribution I am personally most comfortable with, which is the primary reason for its selection. Additionally, Ubuntu Server offers generous long term support and a large, collaborative community of users who are always there to provide a helping hand.

If you would like to eventually support the HTTP/2 protocol on your server, I'd encourage you to use Ubuntu Server LTS 16.04 or higher. Earlier versions of Ubuntu packaged versions of OpenSSL and Nginx using an older version of OpenSSL. OpenSSL version 1.0.2 or higher is required in order to support Application-Layer Protocol Negotiation Extension ALPN(ALPN). The Google Chrome development team decided that they will only support the use of the ALPN TLS protocol extension for negotiating HTTP/2 sessions starting in May 2016 and stop supporting NPN. If your HTTP/2 server does not accept this extension, Chrome will downgrade connections to HTTP/1.1, losing the page load speed advantages of HTTP/2.

Installation Steps

  1. Choose a VPS provider
  2. Create an Ubuntu Server image
  3. Configure a firewall
  4. Configure a swap file
  5. Install package and OS updates
  6. Install Node.js and NPM
  7. Install Ghost
  8. Install and configure Nginx to proxy connections
  9. Create standard user account to run Ghost
  10. Run Ghost as a service

Step 1: Choose a VPS provider

If you decide to self-host Ghost, you will first need to find a virtual private server (VPS) or container hosting provider. Ghost runs on Node.js, which requires a process to be running continually (WordPress and Drupal do not necessarily have this restriction). Because of this requirement, very few managed hosting providers support Node.js. If you need to run Node.js service instance, a VPS is likely your best hosting option to get started with. Some popular VPS providers include Amazon Web Services, Digital Ocean, Heroku, Linode, Microsoft Azure, and Joyent. I chose Digital Ocean as my VPS host because it offers a straight-forward pricing model, reasonable prices, and and has outstanding reputation in development circles. I am currently paying $5/mo for a VPS server with 512MB memory, 1 CPU core, and a 20 GB SSD. If I ever need to upgrade to a more expensive hosting plan, I always can at a later time. So far, my experience with Digital Ocean has been terrific, and I feel comfortable recommending Digital Ocean to others looking to start a blog.

Several hosting providers, including Digital Ocean, offer server images with Ghost pre-configured. This allows you to quickly get Ghost up and running by spinning up a VM instance. Going this route can save you considerable time. However, since I am personally responsible for maintaining my VPS, I wanted to know exactly how my server was configured so that I could keep it updated, patched for security, and running efficiently. For this reason, I decided to set up and configure Ghost from scratch on an Ubuntu Server instance. I imagine this is the route most individuals interested in self-hosting Ghost would take.

Step 2: Create an Ubuntu Server image

Using your cloud provider of choice, create and power on an Ubuntu Server machine image. Using Digital Ocean, I created an Ubuntu x64 16.04 LTS droplet using the smallest available $5/mo size with 512MB RAM, 1 CPU, 20 GB SSD Disk, and 1000 GB Transfer. For a simple Ghost blog, these specifications are sufficient. Most VPS providers make it easy to increase the capacity of your server on demand if you ever require additional capacity in the future.

Your VPS provider will typically have instructions on how to establish a SSH session with your server. If you are using Digital Ocean, they have instructions on how to do this with any of their supported Linux distributions available on their "How To Connect To Your Droplet with SSH" guide. For added security, your VPS provider will likely suggest creating a public/private key pairing in order to establish SSH sessions. Once you are able to start a SSH session, the real fun begins.

Step 3: Configure a firewall

As soon as you boot up your server, it is exposed to the Internet. Most likely, your server's default image configuration does not have a firewall enabled. A firewall is your first line of protection against malicious agents on the Internet. I'd recommend configuring a firewall as soon as possible to keep your server secure.

There are hundreds of different firewall solutions you could use on your server. Some more popular options include iptables, nftables, or even pfSense. If you want a simple, straightforward firewall solution, I'd suggest starting with Uncomplicated Firewall, or UFW. UFW is a front-end to iptables and nftables, abstracting some of the lower-level details of firewall configuration.

Install the UFW package

UFW should come pre-installed on Ubuntu Server, but if it isn't, you can install the package using apt-get in an SSH shell as follows:

sudo apt-get install ufw  
Check UFW Status

You can check the status of UFW by running the following command in a SSH shell:

sudo ufw status  

If UFW is not currently configured or running, you should see a message indicating that UFW is inactive:

Status: inactive  

If UFW is inactive, you can activate it by running the following commands:

sudo ufw disable  
sudo ufw enable  

After disabling and enabling UFW, it should now be in an active status.

Setup default rule: block ALL incoming connections

Before adding custom exceptions, it's best to set a general default rule to deny all incoming connections and to allow outgoing connections:

sudo ufw default deny incoming  
sudo ufw default allow outgoing  

As you become more comfortable with UFW, you may wish to start also denying all outbound connections that are not explicitly listed. This is more secure, but inconvenient when you are first trying to get your Ghost blog up and running.

Setup exceptions to allow specific connections

Since we want to continue having the ability to SSH into our server once we enable UFW, we need to add an exception to allow SSH connections. Assuming, you are running SSH on the standard port 22, you can accomplish this with the following command:

sudo ufw allow ssh  

Additionally, you'll want to accept incoming connections on the port you plan to run your web server on. Assuming you plan to run your server on the standard port 80, you can add an exception as follows:

sudo ufw allow http  

If you plan on supporting SSL/TLS on your server, you'll also want to add an exception for TCP traffic on port 443 using the following command:

sudo ufw allow https  

After you configured any desired rules and exceptions, you can make them take effect by restarting UFW:

sudo ufw disable  
sudo ufw enable  
sudo ufw status verbose  

If UFW is working properly, you should see a status message that looks like this:

sudo ufw status verbose  
Status: active  
Logging: on (low)  
Default: deny (incoming), allow (outgoing), disabled (routed)  
New profiles: skip

To                         Action      From  
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere  
80/tcp                     ALLOW IN    Anywhere  
443/tcp                    ALLOW IN    Anywhere  
22/tcp (v6)                ALLOW IN    Anywhere (v6)  
80/tcp (v6)                ALLOW IN    Anywhere (v6)  
443/tcp (v6)               ALLOW IN    Anywhere (v6)  

Note that (v6) would be visible on a machine configured to be IPv6 ready.

Step 4: Configure a swap file

This step may not be necessary, but it was necessary for my VPS instance. Digital Ocean, my VPS provider, configures their server images, or "droplets", to not have a swap file enabled. Their reason for this is to reduce wear and tear on their SSD drives. While I generally agree with this approach and recommendation, I found that my Ubuntu Server droplet with 512MB RAM was unable to check for and install package updates without a swap file.

You can check if your server has a swap file enabled by running the command:

sudo swapon -s  

If you only get back the header message, this indicates that your server does not have a swap file enabled:

Filename                Type        Size    Used    Priority  
Check available disk space

Before attempting to create a swap file, you should check the available disk space by running the following command:

 df -h

On this particular server, we see that this server has several gigabytes of free space. We can spare to allocate some of this space to the swap file:

Filesystem      Size  Used Avail Use% Mounted on  
udev            230M  4.0K  230M   1% /dev  
tmpfs            49M  376K   49M   1% /run  
/dev/vda1        20G  1.3G   18G   7% /
none            4.0K     0  4.0K   0% /sys/fs/cgroup  
none            5.0M     0  5.0M   0% /run/lock  
none            245M     0  245M   0% /run/shm  
none            100M     0  100M   0% /run/user  

There are varying opinions on the optimal swap file size. For starters, you can set the swap file size to match the size of available memory on your server. You can always increase or decrease the size of the swap at a later time.

We can use fallocate to create a swap file. I found that I needed a 1G swap file in order to complete the NPM installation of Ghost. You can create a swap file as follows:

 sudo fallocate -l 1G /swapfile

You can verify that your swap file was created correctly by running the following command:

ls -lh /swapfile  

As expected, a properly sized swap file was created:

ls -lh /swapfile  
-rw-r--r-- 1 root root 512M Jul  11 16:04 /swapfile
Enable the swap file

We don't want any ordinary user account to have permission to read the swap file contents. Sensitive information, including private keys and passwords, could be present in the unencrypted swap file. We can ensure that permissions are setup correctly by running chmod 600 on the swapfile. Running this command ensures that only the root account has read/write access to the swap file:

sudo chmod 600 /swapfile  

We can verify the permissions as follows:

ls -lh /swapfile  

And see:

-rw------- 1 root root 1.0G /swapfile

You can instruct Ubuntu Server to configure the swap space with the following command:

sudo mkswap /swapfile  
Setting up swapspace version 1, size = 1.0G  
no label, UUID=bb58c487-1d34-4303-beb1-23484a956bdb  

You can then enable the swap file by entering:

sudo swapon /swapfile  

Lastly, you can verify the swap file is in use by entering the following command:

 sudo swapon -s
Filename                                Type            Size    Used    Priority  
/swapfile                               file            1048572 24720   -1
Making the swap file permanent

Even though we have just configured the server to use a swap file, it won't continue to use the swap file following a reboot unless we change the fstab configuration file by adding an entry that instructs the server to use the swap file every time.

Edit the fstab file in a text editor:

sudo nano /etc/fstab  

Add the following entry to the end of the file and save it:

/swapfile   none    swap    sw    0   0
Lower swappiness

The swappiness value specifies how aggressively the operating system should swap out data from RAM to disk. For servers, especially those with SSD drives, this value should be set very low so that the operating system only swaps data from RAM when it is necessary.

The default swappiness value for Ubuntu Server is 60, which you can confirm by executing the following command:

cat /proc/sys/vm/swappiness  

We'd like to set this to a much lower value (5-15) to avoid swapping whenever possible.

You can manually change the swappiness value at run time with the following command. However, it won't persist following reboots:

sudo sysctl vm.swappiness=10  

To permanently specify the swappiness level for your server, open the sysctl.conf configuration file and add a new configuration entry:

sudo nano /etc/sysctl.conf  

Add a permanent swappiness configuration entry at the bottom of this configuration file as follows:

vm.swappiness=10  

Step 5: Install updates

Since your server is publicly exposed on the Internet, it is important to fervently ensure that it always has the latest package updates and security updates. You'll want to verify that your server is up-to-date before you start installing and configuring Ghost. To accomplish this, you'll need to run several package management commands via apt-get. Ubuntu has excellent documentation on apt-get available in its AptGet/Howto Guide if you'd like to read more about the options that are available with the AptGet package manager.

Get available update listing

Use the apt-get update command to update the listing of available updates as follows:

sudo apt-get update  
Upgrade installed packages

The apt-get update command installs all available packages:

sudo apt-get upgrade  

If no new/updated packages are available you'll see the following message:

Reading package lists... Done  
Building dependency tree  
Reading state information... Done  
Calculating upgrade... Done  
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.  
Remove unused packages

You will want to occasionally run the apt-getautoclean command in order to remove any .deb packages that are no longer referenced by or installed on your system from from /var/cache/apt/archives. This command can potentially free up unused space on your server and can be ran as follows:

sudo apt-get autoclean  
Remove packages from package cache

Ubuntu Server caches packages to /var/cache/apt/archives. You can see how much space these cached packages are consuming by running the following command:

du -sh /var/cache/apt/archives  

If you find that the package cache is consuming substantial disk space, you can remove all cached packages by running the following command:

sudo apt-get clean  

To upgrade Ubuntu Server itself, you can use the following command:

do-release-upgrade  

Step 6: Install Node.JS and NPM

Node.JS is the server that will be hosting the instance of our Ghost blog. Ubuntu Server's default repository list has a stable version of Node.JS. This stable version of Node.JS will be ideal for Ghost and can be installed as follows:

sudo apt-get update  
sudo apt-get install nodejs  

You'll also need to install NPM, or the Node Package Manager, which Node uses to manage packages and dependencies as follows:

sudo apt-get install npm  

After installing both Node and NPM, you can confirm the version of Node running on your server by running the following command:

nodejs -v  

Similarly, you can check the version of NPM installed on your server as follows:

npm -v  
Ubuntu Server Node.js interpreter renaming issues

At the time of this writing, in July 2016, Debian/Ubuntu renamed the Node.js interpreter executable name from node to nodejs because of a name conflict with another package. This can cause errors if you try to install Ghost on a Node instance running on Ubuntu Server such as:

sh: 1: node: not found  
npm ERR! weird error 127  
npm WARN This failure might be due to the use of legacy binary "node"  
npm WARN For further explanations, please read  
/usr/share/doc/nodejs/README.Debian

To correct this situation with Ubuntu Server, you should install nodejs-legacy as follows:

sudo apt-get install nodejs-legacy  

For more information about this node naming collision error please read the following Stack Overflow Article

Step 7: Install Ghost

The next step is to install Ghost. Ghost.org suggests installing Ghost to var/www/ghost, which will require us to create a new directory and download the latest version of Ghost from GitHub as follows:

sudo mkdir -p /var/www/  
cd /var/www/  
sudo wget https://ghost.org/zip/ghost-latest.zip  

Once you have downloaded Ghost, unzip ghost-latest.zip and switch to that directory:

sudo unzip -d ghost ghost-latest.zip  
cd ghost/  

Now install Ghost with its production dependencies (this may take a few minutes):

sudo npm install --production  

Ghost should have a configuration file located at /var/www/ghost/config.js, but by default there will be no configuration file at that location. You need to copy the example configuration file Ghost deploys by running the following command:

sudo cp config.example.js config.js  

Open the config file and change the setting "url" to properly reflect your domain name:

sudo nano config.js  

At some point you will also likely want to sign up for a free mailgun account and also specify email settings so that notifications, password resets, and account verification emails can be sent by your Ghost instance.

Once Ghost is installed, you can start Ghost as follows:

sudo npm start --production  

You should see the following message if Ghost was installed successfully:

Ghost is running in production...  
Your blog is now available on http://my-ghost-blog.com  
Ctrl+C to shut down  

By default, Ghost runs on default port 2368. While Ghost is running, you could visit either http://your-ip-address:2368 to view your blog or http://your-ip-address:2368/ghost to create your administrator user. However, since we enabled UFW, this port will not be publicly accessible. In later steps, we will configure Nginx to proxy requests from port 80 to our Node.js instance running with standard user permissions. For security concerns, this is the suggested deployment for Ghost or nearly any other Node application.

Step 8: Install and configure Nginx to proxy connections

By default Ghost runs on port 2368, and if you have a UFW configured correctly you shouldn't be able to access Ghost from the outside world yet. To make this possible we need to configure Nginx to proxy connections from port 80 to port 2368.

Leave Ghost running in a SSH shell at the conclusion of step 7. Now, open a new SSH shell to your server to configure Nginx to proxy connections to your newly created Ghost server.

Install Nginx on Ubuntu by running the following command:

sudo apt-get install nginx  

Next, remove Nginx's default configuration file as we will setup our own by switching to Nginx's configuration directory and deleting the default configuration file:

cd /etc/nginx/  
sudo rm sites-available/default  
sudo rm sites-enabled/default  
Basic Configuration

First, we'll configure Nginx with the most basic configuration file needed with no advanced configuration options such as TLS/SSL or caching.

Create a new configuration file:

cd sites-available  
sudo touch ghost  
sudo nano ghost  

Paste the following simple configuration file, replacing the domain section with your server's IP address or domain name:

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

    server_name chrisebert.net *.chrisebert.net localhost; # Replace with your domain

    root /usr/share/nginx/html;
    index index.html index.htm;

    client_max_body_size 100M;

    location / {
        proxy_pass http://localhost:2368;
        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_buffering off;
    }
}

Note: the example configuration provided above is minimal in nature. Once you get your Ghost server up an running, you can customize this Nginx configuration file to cache resources and to directly serve static content, such as images. While Node is very efficient at serving up dynamic content, Nginx is more efficient at serving static content like image files. You can also eventually enhance your Nginx configuration so that your server will support the HTTP/2 protocol.

It's also worth noting that if your server does not support IPv6, you should remove the following configuration line listen [::]:80 default_server ipv6only=on; as it will cause errors if your server does not have any IPv6 network adapters.

Finally, create a symbolic link from the file in sites-available to sites-enabled:

cd ..  
sudo ln -sf sites-available/ghost sites-enabled/  

You should verify that this symbolic link is working correctly by running the following command:

cd /etc/nginx/sites-enabled/  
ls -la /var/www/ | grep "\->"  

If you see the ghost file listed, you have properly configured the symbolic link.

Restart Nginx. You should see a OK message with no errors

sudo service nginx restart  

At this point, you may want to run CURL to see if you are getting a response back from your Nginx server by running the following command:

curl http://localhost/  

If you get a connection refused error, you likely didn't link your config file from sites available to sites enabled correctly. If you get a different error, you can use the nginx -t command to test the validity of your configuration files. If you get a bad gateway error, its likely that Nginx is running fine, but your Node server is not.

If everything is working correctly you'll now want to visit http://yourserver/admin and create your Ghost administrator account.

Step 9: Create standard user account to run Ghost

Now that you have Ghost configured, it should be working properly. However, there's a security concern that Ghost is currently running with root permissions. We can create a standard user account named ghost to run this site with downgraded permissions. This user account should only have permission to modify files located in var/www/ghost.

Create a new Ghost Linux user account and grant it directory permissions as follows:

sudo adduser --shell /bin/bash --gecos 'Ghost application server' ghost  
sudo groupadd ghost  
sudo chown -R ghost:ghost /var/www/ghost/  

After you have created this user, switch to this user account and verify that this user can start Ghost:

su ghost  
cd /var/www/ghost  
npm start --production  

You should be able to start Ghost just as you did previously, but this time with standard user permissions instead of root, which is far more secure.

Step 10: Start Ghost as a service

Now that you have Ghost up and running. There's one problem left to solve. You currently can run Ghost in a SSH shell or use forever to run Ghost in the background. However, either approach won't start Ghost back up following a system restart. Since we don't want to have to manually start Ghost when our server reboots or the process crashes, we'd like to configure Ghost as a service with systemd system and service manager init system utilized by Ubuntu. Systemd is the new service manager utilized by more recent versions of Ubuntu server.

Temporarily switch back to the root user account:

su root  

Create a file named ghost.service in /etc/systemd/system/:

cd /etc/systemd/system/  
sudo touch ghost.service  

Open ghost.service and paste the following configuration there:

# Place in /etc/systemd/system/ghost.service
[Unit]
Description=Ghost blog chrisebert.net  
After=network.target

[Service]
Type=simple  
PIDFile=/run/ghost-chrisebert.net.pid  
# This is the directory you installed Ghost to
WorkingDirectory=/var/www/ghost/  
User=ghost  
Group=ghost  
ExecStart=/usr/bin/npm start --production  
ExecStop=/usr/bin/npm stop /var/www/ghost/  
StandardOutput=syslog  
StandardError=syslog

[Install]
WantedBy=multi-user.target  

If you have Ghost running in a shell already, close it. Now try to start Ghost as a service by running the following command:

sudo service ghost start  

You can check the status of the service by running the following command:

sudo service ghost status  

If Ghost is running properly as a service, you should see a status message similar to this:

● ghost.service - Ghost blog chrisebert.net
   Loaded: loaded (/etc/systemd/system/ghost.service; disabled; vendor preset: e
   Active: active (running) since XXX 2016-XXXXXX EDT; 3s ago
 Main PID: 8237 (npm)
    Tasks: 19
   Memory: 109.6M
      CPU: 2.950s
   CGroup: /system.slice/ghost.service
           ├─8237 npm
           ├─8248 sh -c node index
           └─8249 node index

Now register the service you created so that it will start whenever you restart your server:

sudo systemctl enable ghost.service  

Now, even if you reboot your server, both Ghost and Nginx should start up automatically for you.

Future Steps

Now that you have Ghost up and running, you can start blogging right away. There are a few tasks you may want to consider now that you have everything up an running:

  • Configure server backups
    • After all this work you've done, you'll want to take a backup or snapshot so you can always revert back to a known good state.
  • Disable root SSH access
    • If you haven't done so already, you'll likely want to disable root SSH access only allow SSH access with a standard user account you have created for yourself
  • Optimize Nginx for caching
    • There are many Nginx optimizations possible for Ghost. Looking at different GitHub Examples is a great starting point
  • Obtain a Let's Encrypt Certificate
    • HTTP/2 requires a TLS / SSL Certificate. Fortunately, its possible to get a free certificate from Let's Encrypt . This is the free service I used to add HTTPS support to my own blog
    • After you have configured your server to support HTTPS, if you have done everything correctly, it should be possible for you to receive an A+ rating from SSL Labs
  • Enable HTTP/2
    • Once you have obtained and configured your certificate, you can enable HTTP/2 support on Nginx. I found the website HTTP/2 Test to be helpful when determining if HTTP/2 was configured correctly on my server
  • Create a free mailgun account
    • You can create a free mailgun account so that your Ghost blog has the ability to send emails to users. Once you have created an account, you can configure Ghost to send emails thru this service.
  • Enable log rolling / health monitoring
    • You'll want to configure some form of log rolling so that your long running server doesn't run out of disk space due to log files. You may also want to consider adding a health monitor service to inform you when your server is in trouble
  • Configure a blog commenting system
    • Configure Facebook, Discourse, or Disqus to support blog commenting

Best of luck with your new blog!