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:
- Create a configuration file at
/etc/nginx/sites-available
- Create a symlink to that file in
/etc/nginx/sites-enabled
. - Verify that the config is valid with
nginx -t
- 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!
Comments ()