Ghost 1.0 on Laravel Forge

Ghost 1.0 on Laravel Forge

I have been running both my daughter's and my Ghost pre-1.0 blogs on Laravel Forge for quite a while, but installing it and getting it to run with the new ghost-cli was a bit of a challenge, so I thought I'd write it up in case anyone else is interested.

First off this tutorial will assume you have a basic understanding of setting up a server on Forge, and a basic understanding of getting around on the command line, otherwise this may not be the right tutorial for you.

1. Let's create a new server

I prefer Linode, not only for their pricing, but for the incremental control you get in their backend panel.

linode-create

Notice that I changed the database name, but you don't have to, and installed MariaDB.

Once the main process has begun, you'll receive a modal notice like so:

server-modal

Make sure you write those down in case something happens with your email sent from Forge that contains these passwords.

Your panel should look similar to the below:

provisioning

Now while your server is provisioning you can take your newly assigned IP address and point your domain to the server. I'll leave that up to you, if you have an option on TTL set it to 300 so it can propagate as quickly as possible.

Once you server is provisioned you should see this:

panel-post-1

Click on the refresh icon to make sure you can connect to your server.

panel-post-2

Once you know you can connect, we can move forward and add your blog domain.

2. Create Website

add-site

Since this is just a temp server and blog site for this tutorial, I set it up as a sub-domain off one of my main domains.

Notice I set the project type to Static HTML and very important, set your Web Directory to /ghost/current, this is where your blog will be served from.

Once your site is created you can go ahead and remove the default site.

Before we go on, let's make sure our domain has propagated, we can do that with the OpenDNS Cache Tool like so:

open-dns

As you can see the domain is pointing to our server, so we're good to go. You want to wait till this is done before you move forward.

Let's continue.

3. Nginx and SSL

Ghost 1.0 is a whole new animal really, you should read up on it to get all the details, but with the ghost-cli you'll have a wonderful new installation and upgrade experience, part of which is the built in Nginx and SSL options during the install.

But, because we want to manage those with Forge, we'll skip them during the ghost-cli install later.

First, let's make sure our site is live, Forge will automatically set up a phpinfo page for us so, click into your site and at the top right you'll see something similar to this:

check-site

The green arrow icon, circled in red here, will take you straight to your site and you should see the default phpinfo page:

phpinfo

Perfect, we have some content so we can install Let's Encrypt.

Let's Encrypt

Click on your SSL tab, then the Let's Encrypt, remove the www version of the domain (since I'm using a sub-domain, you may want to keep it):

certificate

Once your certificate is installed, go ahead and activate it, then check your site again to make sure you're getting the phpinfo page via https.

If you have success, we can move on.

Nginx Config

At the bottom of each page under your site there is an Edit Files button, click that then the Edit Nginx Configuration

The config for this server I'm setting up, looks like so:

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/blog.19peaches.com/before/*;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name blog.19peaches.com;
    root /home/forge/blog.19peaches.com/ghost/current;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/blog.19peaches.com/228707/server.crt;
    ssl_certificate_key /etc/nginx/ssl/blog.19peaches.com/228707/server.key;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    # FORGE CONFIG (DOT NOT REMOVE!)
    include forge-conf/blog.19peaches.com/server/*;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/blog.19peaches.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/blog.19peaches.com/after/*;

We're going to make 4 edits:

  1. index
  2. location/
  3. error_page
  4. location ~ .php$

Scroll down in the file and change the index line to:

index index.js index.html index.htm index.php;

Next remove the location/ block completely, we don't need this at all.

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Next, change the error_page to the following:

error_page 404 /index.js;

Lastly we're going to remove the entire php location block so we can serve all our blog content via Node.

Find this block:

location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

And replace it with the below:

location / {
    proxy_pass http://127.0.0.1:2368;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
}

Save and close the modal. Go out to main dashboard and check the Recent Events log and make sure that Nginx was reloaded, if not use the restart drop down in the server interface to restart Nginx.

recent-event-check

4. Database

You will need a database tool to log into the database, unless you're a complete newbie to Forge, you already have one, so setting that up I'll leave to you as I can't give instructions for every tool out there.

I use Querious exclusively so any screens you find on this blog for database interaction will be examples from Querious.

Log in to the server with your database tool, and check that the ghostserver database created by Forge and make sure that it's using the proper charset and collation. I want the most flexible unicode collation, it's up to you, but I'd prefer using utf8mb4 and utf8mb4_unicode_ci, so I'll just drop this db and make a new one.

collation

You can see now it's set up correctly, as I said this is optional, but I know it works so it's what I'm recommending.

5. Command Line Work

Time to log into the server via ssh and get everything for installed for Ghost. The official Ghost docs is fairly close, but I found some variations that needed to be made in order to get this installed, hence the main reason for this tutorial.

But first things first, let's get logged in, open a terminal window:

$ ssh forge@IP_ADDRESS

Something I like to do, as a little tip, if I know I'm gonna be doing a bit of sudo work, I'll change my forge password to something simple, then change it back when I'm finished, that way I don't have to copy and paste my sudo pass all the time.

$ passwd

Enter the old pass then a new simple one and confirm. Just don't forget to change it back before you log out of the server.

Node JS

Forge installs node via NVM and this conflicts with ghost-cli so first we'll remove the existing Node install, and install from the NodeSource APT repository.

$ sudo rm -rf $NVM_DIR ~/.npm ~/.bower
$ unset NVM_DIR;
$ which node;
$ sudo rm -rf {path_to_node_version}

{path_to_node_version} being the output from which node

Once complete which node should return nothing.

Now let's install Node from NodeSource.

First add the repository.

$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash

This should install Node, but it installs it as /usr/bin/nodejs instead of /usr/bin/node, so to check it, run both:

$ which node
$ which nodejs

If you get nothing for either command, then install it manually like so:

$ sudo apt-get install -y nodejs

I've installed this several times and it always installs to /usr/bin/nodejs so if it does for you, we need to move that to:

$ sudo mv /usr/bin/nodejs /usr/bin/node

Now check the Node version:

$ node -v

As of this writing, NodeSource installs v8.2.1 ARRGGGG! We need 6.5!

So, let's install n so we can select which version we want to use.

$ sudo npm cache clean -f
$ sudo npm install -g n
$ sudo n 6.5

We should be good to go now, install ghost-cli

$ sudo npm i -g ghost-cli

Installing Ghost

Let's navigate to your domain directory, in our case here to:

$ cd blog.19peaches.com

When you list the directory you should see only the existing ghost directory that Forge set up for us.

$ ls -a

Let's remove this entire directory:

$ sudo rm -rf ghost

This will remove the current directory and our phpinfo page as well.

Let's make a new ghost directory and move into it:

$ mkdir ghost && cd ghost

Now we can run the ghost-cli installer:

$ ghost install

If you get a permission error, follow the resolution shown by ghost-cli.

During the installation you will be prompted for the following (I've written the answers as well for this install)

Enter your blog URL: http://blog.19peaches.com
Enter your MySQL hostname: 127.0.0.1
Enter your MySQL username: forge
Enter your MySQL password: [Forge created Database password]
Enter your Ghost database name: ghostserver

# DO NOT SET UP NGINX
# Answering no will skip the SSL set up as well
Do you wish to set up Nginx? n

# DO NOT SET UP GHOST MYSQL USER
Do you wish to set up "ghost" mysql user? n 

# SAY YES TO SYSTEMD
Do you wish to set up Systemd? Y

Do you want to start Ghost? Y

Now go check your site!

ghost

If for some reason you get an error, try restarting ghost and nginx.

$ sudo ghost restart
$ sudo service nginx restart

Don't forget if you changed your password to change it back before you log out.

6. Conclusion

Laravel Forge is an amazing tool and sets up such a great server environment, don't just use it for your Laravel or PHP sites, it's great for everything, even if you have to do some customizing to get there.

There's more configuration you can do of course, and you need to log in and make your account and all that, upload a theme etc.

I love Ghost and I think you'll really love this new version.

See ya out there.

By Vince Kronlein

Web developer in Laravel and VueJS. Also - father, crappy golfer, obsessed fan of @iamjohnoliver, and complete fucking nerd :)

Website