Installing a self-hosted Ghost instance on Ubuntu

If you're looking to set up your own blog with full control over your data and customization, hosting Ghost on an Ubuntu server is a great option. While an official installation guide exists, it makes certain assumptions about the server setup that weren't right for me.

Below are the steps that I followed to get this blog up and running.

Configure the user

This step is exactly described in the Ghost documentation. As mentioned there, the "ghost" username will cause conflicts so be sure to pick something else.

# Login via SSH
ssh root@your_server_ip

# Create a new user and follow prompts
adduser <user>

# Add user to superuser group to unlock admin privileges
usermod -aG sudo <user>

# Then log in as the new user
su - <user>

# Update your packages before installing dependencies
sudo apt update
sudo apt upgrade

Install dependencies

In theory the Ghost installer is supposed to take care of most of what's to follow. However in my experience, the only way I could get the Ghost installer to function was to install and configure all of the following dependencies manually.

If you get stuck on any of the following steps, DigitalOcean has published excellent guides on installing and configuring MySQL and Nginx. Even if all goes well for you they're definitely worth a read.

MySQL

Start by installing the package

sudo apt install mysql-server

Log in

mysql -u root -p

And create the user and database

CREATE DATABASE ghostblog CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'ghostuser'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON ghostblog.* TO 'ghostuser'@'localhost';
FLUSH PRIVILEGES;

Nginx

Install the package

sudo apt install nginx

If you are using the ufw firewall, allow Nginx for HTTP and HTTPS

sudo ufw allow 'Nginx Full'

Now that it's installed, you'll need to configure Nginx to proxy incoming requests to your Ghost instance.

The basic configuration process is as follows:

  1. Create a configuration file at /etc/nginx/sites-available
  2. Create a symlink to that file in /etc/nginx/sites-enabled.
  3. Verify that the config is valid with nginx -t
  4. Reload Nginx with systemctl reload nginx

Nginx supports a ton of options, but in its most basic form, a configuration file to redirect requests on example.com to port 3000 looks something like this:

server {
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        include proxy_params;
    }
}

In addition to the installation guide referenced above, DigitalOcean has another excellent guide that explains how to configure Nginx for some of the more common reverse proxy setups.

Certbot

Certbot makes it super easy to set up HTTPS. Their official guide will walk you through the installation process better than I can.

Once it's installed, simply run:

sudo certbot --nginx

This command will acquire and install a certificate and apply the necessary changes to your Nginx config that you set up in the last step. Easy!

NodeJS

Unlike the previous dependencies, we'll install NodeJS at the user level. For this we'll use NVM, which makes it super easy to manage NodeJS versions.

The NVM GitHub page contains detailed installation instructions, but it essentially boils down to this:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# Execute this command (printed to the console) to activate nvm:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

With that done, installing NodeJS is a breeze. Ghost requires NodeJS 18.x. You can install the latest minor with:

nvm install 18

(Isn't that great?)

Install Ghost

Start by installing the Ghost CLI package. The official docs say to do this with sudo. I didn't need to:

npm install ghost-cli@latest -g

Now configure the installation directory:

# Create directory: Change `sitename` to whatever you like
sudo mkdir -p /var/www/sitename

# Set directory owner: Replace <user> with the name of your user
sudo chown <user>:<user> /var/www/sitename

# Set the correct permissions
sudo chmod 775 /var/www/sitename

# Then navigate into it
cd /var/www/sitename

Now you're ready to install Ghost!

As mentioned earlier, I had a lot of problems at this step. I ended up getting through it by passing the no-setup-linux-user argument to the installer, which tells it to skip the MySQL, Nginx, and Certbot installation/configuration.

As we configured those earlier, we can run the command without issue

ghost install --no-setup-linux-user

The installer will ask you for several pieces of information, including the public URL of your blog and MySQL connection details. It may also ask you if you want it to configure any of the following:

  • A MySQL user
  • Nginx
  • SSL (Certbot)
  • systemd

Answer "no" to all of these.

With a bit of luck the installer will complete successfully. Ghost is now installed! The final step is to configure systemd to install Ghost as a service.

Configure systemd

You'll need a couple pieces of info to create your service

which node    # Your NodeJS path
which ghost   # Your Ghost path
id -u         # Your user ID

Now you can create your service file. Here I'm doing so with vim, but you can use the editor of your choice

sudo vim /lib/systemd/system/ghost_www-example-com.service

The file should look like the following

[Unit]
Description=Ghost systemd service for blog: www-example-com
Documentation=https://ghost.org/docs/

[Service]
Type=simple
WorkingDirectory=<Your site path, e.g. /var/www/sitename>
User=<Your user ID>
Environment="NODE_ENV=production"
ExecStart=<Your NodeJS path> <Your Ghost path> run
Restart=always

[Install]
WantedBy=multi-user.target

Once you've saved that file you can load and start the service

sudo systemctl daemon-reload

# Your service should appear in this list
systemctl list-units --type=service | grep ghost

# Configure your blog to auto-start on boot
systemctl enable ghost_www-example-com.service

# Start your blog
systemctl start ghost_www-example-com.service

Now you can test your blog locally by running curl on the Ghost port

curl localhost:3000  # Use the port that you configured earlier

If that returns a bunch of HTML then you're all set! Happy blogging!

Levi

Levi

This is my blog. I build stuff and write about it.