Install Ghost on FreeBSD 12
A guide to installing Ghost on FreeBSD 12.1 from scratch.
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 on this blog is kind of meta. My web server runs on FreeBSD, and although my existing website was running WordPress, I really prefer working with Markdown and decided to create my professional website with Ghost.
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.
This is from my original dry run on a test vm, so I'm assuming you have a fresh install of FreeBSD 12.1.
Let's go ahead and install Ghost's dependencies:
# pkg install node12 npm-node12 nginx py37-certbot py37-certbot-nginx
That will provide the basics, but we need a database backend. The SQLite database is really only for testing purposes. Ghost officially supports MySQL >=5.5 & <=5.8. You should probably install one of these from FreeBSD ports. For the sake of this tutorial, we're throwing caution to the wind and using MariaDB 10.4. Why? No better reason than it's what my server was already running.
# pkg install mariadb104-server mariadb104-client
# service mysql-server enable
# service mysql-server start
Now let's prepare a database and user for Ghost.
# mysql
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 for Ghost to run as. 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 yourusername:yourusername /usr/local/www/yourblog.ext
# chmod 775 /usr/local/www/yourblog.ext
# su - yourusername
$ cd /usr/local/www/yourblog.ext
Now we're ready to install Ghost. It's going to complain a lot.
$ ghost install
✔ Checking system Node.js version
✔ Checking current folder permissions
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? (y/N) y
System stack check skipped
ℹ Checking operating system compatibility [skipped]
Local MySQL install not found. You can ignore this if you are using a remote MySQL host.
Alternatively you could:
a) install MySQL locally
b) run `ghost install --db=sqlite3` to use sqlite
c) run `ghost install local` to get a development install using sqlite3.
? Continue anyway? (y/N) y
So we told it yes regarding the OS and MySQL versions. We'll still be able to setup our MariaDB connection. Accept most of the defaults in the next round as we'll tweak these things later.
? 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
Choose not to start Ghost. You can use ghost stop
if you accidentally did. In the prior step I assumed you have the hostname for your site ready, either by a dns entry, or in your local hosts file for testing. We can talk about certificates later.
Fix the permissions of this file that has the database password in it:
chmod 600 /usr/local/www/yourblog.ext/config.production.json
Now edit config.production.json
and change the following line to "local" instead of "systemd". Although it mentioned this in the wizard, actually using "local" during the install would have just given us a simple dev environment. We don't want this thing complaining about a lack of systemd every time we start it, though.
"process": "local",
We can start Ghost now.
$ ghost start
The rest of the steps require you switch back to the root user.
If you already have a certificate for your site, skip this step.
Enable, but stop nginx. We're going to use the standalone mode to fetch the certificate because nginx isn't configured yet.
# service enable nginx
# service stop nginx
Run certbot
, follow the prompts. This will install a certificate for you. The Nginx config examples assume you're using one. If your cert is from elsewhere, just sub in its path.
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/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;
}
location /.well_known/ {
alias /usr/local/www/yourblog.ext/.well_known/;
}
}
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 start
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-server
# 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/yourusername/.bin
ghost_start()
{
su yourusername -c "/usr/local/bin/ghost start -d /usr/local/www/yourblog.ext"
}
ghost_stop()
{
su yourusername -c "/usr/local/bin/ghost stop -d /usr/local/www/yourblog.ext"
}
ghost_restart()
{
ghost_stop;
ghost_start;
}
ghost_status()
{
su yourusername -c "/usr/local/bin/ghost status -d /usr/local/www/yourblog.ext"
}
run_rc_command "$1"
Now try the following:
# chmod 755 /usr/local/etc/rc.d/ghost
# service ghost enable
# service ghost start
You should see details about the ghost process already running from earlier. If not, check the username and paths you used here.