Install Ghost on FreeBSD 13

A guide to installing Ghost on FreeBSD 13 from scratch.

Install Ghost on FreeBSD 13
💡
This post is SO OLD
How old is it?
I don't have anything funny to say, actually. The new post will continue to be updated in place after every major/minor release or when Ghost changes node/database dependencies.

My first post for this blog was about setting Ghost up on FreeBSD, which I had just done. I've migrated to a new server running FreeBSD 13 to celebrate the new release, and recent Ghost installer updates changed some things. So we'll do this over.

One thing I want to get out of the way is how much Ghost v4 has changed things. v4 has new monetization features that hobby bloggers might not want to use, whereas v3 is just a friendly blogging app. If you're interested in having email subscribers and paid content, you'll want to use v4. I'm going to describe installing both, but as of the time I'm writing this, my website is still on Ghost v3.


Nothing about what I'm going to show you is officially supported by the project, so don't go crying to them or me if this breaks during an upgrade.

Let's go ahead and install Ghost's dependencies.

# pkg install mysql57-client mysql57-server node14 npm-node14 nginx py37-certbot py37-certbot-nginx

Now let's get the database ready. Enable the server and run the secure installation script. The prompts are self-explanatory.

# service mysql-server enable
# service mysql-server start
# mysql_secure_installation

Prepare a database and user for ghost.

# mysql -u root -p
CREATE USER 'ghost'@'localhost' IDENTIFIED BY 'IsThisEnoughBitsOfEntropy?_^';
CREATE DATABASE yourblog_ext_prod;
GRANT ALL privileges ON `yourblog_ext_prod`.* TO 'ghost'@'localhost';

Install ghost-cli. We'll handle the user account and web directories next.

# npm install ghost-cli -g

Run adduser and follow the prompts to make an unprivileged user named ghost. The official docs say to give it sudoer permissions, but I'm saying not to. From a default install, this just means don't add the user to the group wheel.

# mkdir /usr/local/www/yourblog.ext
# chown ghost:ghost /usr/local/www/yourblog.ext
# chmod 775 /usr/local/www/yourblog.ext
# su - ghost
$ cd /usr/local/www/yourblog.ext

Install either v3 or v4 of ghost.

ghost install v3
OR
ghost install v4

Answer the following questions accordingly:

System checks failed with message: 'Operating system is not Linux'
Some features of Ghost-CLI may not work without additional configuration.
For local installs we recommend using `ghost install local` instead.
? Continue anyway? Yes
? Enter your blog URL: https://yourblog.ext/
? Enter your MySQL hostname: localhost
? Enter your MySQL username: ghost
? Enter your MySQL password: [hidden]
? Enter your Ghost database name: yourblog_ext_prod
? Do you wish to set up Systemd? No
? Do you want to start Ghost? No

Fix permissions on the file the database password is in and start up.

$ chmod 600 /usr/local/www/yourblog.ext/config.production.json
$ ghost start

The rest of the steps require you switch back to the root user.

Let's enable nginx and get our certificate setup.

# service enable nginx
# service start nginx
# certbot run --nginx -d yourblog.ext -d www.yourblog.ext

Edit the http block in /usr/local/etc/nginx/nginx.conf to follow standard practices:

http {
    include       mime.types;
    include       /usr/local/etc/nginx/sites-enabled/*.conf;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout  65;
    gzip  on;
}

Create the sites-enabled and sites-available folders:

# mkdir /usr/local/etc/nginx/sites-available
# mkdir /usr/local/etc/nginx/sites-enabled

Edit /usr/local/etc/nginx/sites-available/yourblog_ext.conf to look like the following:

server {
    listen 80;
    server_name yourblog.ext www.yourblog.ext;
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name yourblog.ext www.yourblog.ext;
    location / {
        proxy_set_header HOST $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass       http://127.0.0.1:2368/;
    }
    location /ghost/ {
        proxy_set_header HOST $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass       http://127.0.0.1:2368/ghost/;
        allow 12.34.56.78; # YOUR IP ADDRESS GOES HERE otherwise your site will get hijacked
        deny  all;
    }
    ssl_certificate     /usr/local/etc/letsencrypt/live/yourblog.ext/fullchain.pem;
    ssl_certificate_key /usr/local/etc/letsencrypt/live/yourblog.ext/privkey.pem;
}

Here we have it redirecting to https because it's 2020. /ghost/ is the admin page, and we want it so that only you have access to it before you launch this. The setup wizard will let you create the admin account on the spot, and robots look for these kind of things. Afterward, you can either remove that, leave it, or add more allow lines to other addresses you'll connect from.

Now make sure that you or I did not make any mistakes here and resolve as needed.

ln -s /usr/local/etc/nginx/sites-available/yourblog_ext.conf /usr/local/etc/nginx/sites-enabled/yourblog_ext.conf
nginx -t
service nginx restart

Login to https://[yourbloghere]/ghost/ and begin setup. You should be ready to go.

Let's throw a service together so this will persist past a reboot. Create /usr/local/etc/rc.d/ghost and modify the following to be relevant to your service:

#!/bin/sh

# PROVIDE: ghost
# REQUIRE: mysql
# KEYWORD: shutdown

. /etc/rc.subr

name="ghost"
rcvar="ghost_enable"
extra_commands="status"

load_rc_config ghost

start_cmd="ghost_start"
stop_cmd="ghost_stop"
restart_cmd="ghost_restart"
status_cmd="ghost_status"

PATH=/bin:/usr/bin:/usr/local/bin:/home/ghost/.bin

ghost_start()
{
    su ghost -c "/usr/local/bin/ghost start -d /usr/local/www/yourblog.ext"
}

ghost_stop()
{
    su ghost -c "/usr/local/bin/ghost stop -d /usr/local/www/yourblog.ext"
}

ghost_restart()
{
    ghost_stop;
    ghost_start;
}

ghost_status()
{
    su ghost -c "/usr/local/bin/ghost status -d /usr/local/www/yourblog.ext"
}

run_rc_command "$1"

Fix the permissions and enable the service. It'll come up on the next reboot.

# chmod 755 /usr/local/etc/rc.d/ghost
# service ghost enable
# service ghost status