Ghost is a free and open source blogging platform written in Javascript, which saw its first release in 2013. It supports writing posts both using a WYSIWYG (What You See Is What You Get) editor, and the Markdown language. Unlike WordPress, it is focused on simplicity and on being purely a blogging platform, therefore it includes SEO and and social sharing features out of the box. Ghost offers a ready-to-go hosting service, Ghost(Pro), but can be easily self-hosted.
In this tutorial we see how to install and setup the Ghost blogging platform on Ubuntu, both in development and production mode.
In this tutorial you will learn:
- How to quickly install Ghost in development mode manually or using Docker
- How to setup Ghost in production
- How to setup UFW to allow traffic through ports 80 and 443

Category | Requirements, Conventions or Software Version Used |
---|---|
System | Distribution independent |
Software | NodeJs 14.x or 16.x, npm, Docker (if using the Ghost docker image) |
Other | Administrative privileges |
Conventions | # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command $ – requires given linux-commands to be executed as a regular non-privileged user |
Introduction
Ghost can run in development or production mode. Development mode is what we want to use when we need to quickly create a project or, for example, develop a theme. In this mode sqlite is used as database, and the installation of a dedicated web server is not needed. In production mode, instead, a proper stack must be installed: the officially supported one includes MySQL 8 as a database and the NGINX web server.
One requirement common to both installation modes is Node, since Ghost is written in Javascript and runs on the server-side Node.js engine. Supported Node versions are 14.x and 16.x. Chances are that on the platform we are running those versions are not available, therefore we must obtain them from third-party software sources: in this case we will use NodeSource.
Installing the latest Node.js LTS version
The latest Node.js version is 16.x; let’s see how we can install it on Ubuntu from the NodeSource repositories. The first thing we need to do is to download the NodeSource repository signing key, de-armor it, and copy it into the /usr/share/keyrings
directory:
$ curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/nodesource.gpg > /dev/null
As a next step we add the NodeSource repositories to our software sources. We create the /etc/apt/sources.list.d/nodesource.list
file, and write the following content in it:
deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x jammy main deb-src [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource/com/node_16.x jammy main
Next, we synchronize the repositories and finally install the nodejs
package. The node package manager (npm) will be installed as a dependency:
$ sudo apt-get update && sudo apt-get install nodejs
Installing Ghost in development mode
Let’s see how to install Ghost in development mode. The first thing we want to do is to install ghost-cli
, an utility which is designed to help manage our Ghost installation.
Installing ghost-cli and Ghost
To obtain the latest version of ghost-cli, we must use npm
. Often, the suggested way to install a package globally with the node package manager is to run the “npm install” command prefixed with sudo. This, however, is not a good idea, since arbitrary code can be executed as part of a package installation, and we don’t want that code to run with administrative privileges.
An alternative is to run npm with the
--prefix
option. This option takes a path as argument, and is used to specify where packages should be installed. In this case is enough to install the utility just for our user, therefore we can install it in our HOME; in the ~/.npm/
directory, for example:
$ npm --prefix=~/.npm install ghost-cli@latest -g
The command above installs the “ghost” binary as ~/.npm/bin/ghost
. To avoid having to invoke the utility by its full path, we can add the ~/.npm/bin
directory to our PATH. Once installed, we can use the ghost utility to deploy Ghost. First we create the directory that will host our project, than we install Ghost inside of it:
$ mkdir ghost-project && ~/.npm/bin/ghost install local --dir ghost-project
Ghost will be installed in local/development mode. Once the installation is complete, we should visualize a message similar to the following:
Ghost was installed successfully! To complete setup of your publication, visit: http://localhost:2368/ghost/
Visiting the page, we are be prompted to provide some basic project information:

If for some reason we want to uninstall Ghost, we can use the “uninstall” command of the ghost utility:
$ ~/.npm/bin/ghost uninstall --dir ghost-project
To start and stop the Ghost instance, instead, we can use the “start” and “stop” commands, respectively.
Installing Ghost locally using Docker
An alternative, very convenient option we can use to quickly start working on a Ghost project is by using the Docker image provided by the ghost community. In this section of the tutorial I assume you are already familiar with Docker. To build the Ghost docker image and launch a container based on it, we can use the following command:
$ sudo docker run -d --name ghostproject -e NODE_ENV=development -p 2368:2368 ghost
By running the command above, the “ghost” image will be downloaded (if it doesn’t exist on our machine), than a container based on it will be launched. Since we used the
-d
option the container will start in detached mode (it will run in the background, not blocking the terminal). With the --name
option, we specified the container name (“ghostproject” in this case – a random one would be generated otherwise): we can use it as a quick and human-friendly reference. With the -e
option, instead, we defined the value of the NODE_ENV
environment variable: it is used inside the container to establish the environment mode. Finally, we used the -p
option to map port 2368
on the host system to the same container port; this way, we will be able to access the “dockerized” Ghost instance by navigating to: http://localhost:2368/ghost
.
Deploying Ghost in production
Until now we saw how to quickly install Ghost in development mode. When we need to deploy Ghost in production, we must setup MySQL8 as a database and use NGINX as a web server (this is the recommended setup). The official documentation suggests to perform the installation on one of the following Ubuntu versions:
- 16.04
- 18.04
- 20.04
- 22.04
For the sake of this article we will use the latest Ubuntu LTS version (22.04), since in this version MySQL8 is available in the default repositories.
Installing MySQL and NGINX
Installing MySQL8 and NGINX on Ubuntu 22.04 is a very easy operation. All we have to do is to run the following command:
$ sudo apt install mysql-server nginx
As part of the installation both the mysql and nginx services will be started and enabled at boot.
Creating a database for Ghost
We now need to create a database for our project. The first thing we need to do, is to login into MySQL shell:
$ sudo mysql -u root
From the MySQL shell that will be opened, we can issue the following queries to create a database for our project (“ghostproject”) and a database user (“ghostuser”) with all the privileges on it:
mysql> CREATE DATABASE ghostproject; mysql> CREATE USER 'ghostuser'@'localhost' IDENTIFIED BY 'password'; mysql> GRANT ALL PRIVILEGES ON ghostproject.* TO 'ghostuser'@'localhost'; mysql> FLUSH PRIVILEGES; mysql> quit;
Setting up the project directory
The next step consists in the creation of the project directory. Since we are deploying ghost in production, we will create it in the /var/www
directory:
$ sudo mkdir /var/www/ghost-project
Next, we want to give the ownership of this directory to a standard user (“egdoc”, in this case). This user must be someone other than “ghost”, since the “ghost” user and group will be created as part of the installation by
ghost-cli
:
$ sudo chown egdoc:egdoc /var/www/ghost-project
At this point the directory for our project is ready, but we didn’t install the ghost-cli utility, yet. As suggested in the official documentation itself, the easiest way to perform the installation is to invoke the following command:
$ sudo npm install ghost-cli@latest -g
The command above would install ghost-cli in the /usr/local/bin
directory which is already in our user PATH. Running npm with administrative privileges, as we already said, is a bad habit, therefore we can use --prefix
to specify an installation directory. This directory must be accessible to the ghost user, since it is the user the automatically created systemd service will run as. For the sake of this example we will create a directory inside /opt
, and call it “ghost”:
$ sudo mkdir /opt/ghost && sudo chown egdoc:egdoc /opt/ghost $ npm --prefix=/opt/ghost install ghost-cli@latest -g
Once the utility is installed, we can use it to setup Ghost:
$ /opt/ghost/bin/ghost install --dir /var/www/ghost-project
As part of the installation process some checks will be performed, than the needed dependencies will be downloaded and installed. Finally, we will be prompted to enter some information. Among the other things we will be asked to enter the URL of our blog (here I just used “ghostproject.lan”), the MySQL server hostname and the database name, username and password:
? Enter your blog URL: http://ghostproject.lan ? Enter your MySQL hostname: localhost ? Enter your MySQL username: ghost ? Enter your MySQL password: [hidden] ? Enter your Ghost database name: ghostproject
The script will than asks us also our “sudo” password in order to perform certain operations which require administrative privileges:
- Create the “ghost” system user and group (user will be created with the –system option, so its UID will be < 1000)
- Recursively assign the ownership of the “content” project subdirectory (
/var/www/ghost-project/content
in this case) to the ghost user and ghost group - Asks us if we want it to automatically create an NGINX VirtualHost for the project
- Asks us if we want it to automatically setup SSL using Let’s encrypt
- Asks us if it should create a systemd service unit for the project
At the end of the process, we will be asked if we want to start Ghost: this could take a while, since the necessary tables will be created in the database. Once ready, the blog will be accessible at the specified URL. Here is the NGINX VirtualHost file created for this project:
server { listen 80; listen [::]:80; server_name ghostproject.lan; root /var/www/ghost-project/system/nginx-root; # Used for acme.sh SSL verification (https://acme.sh) location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:2368; } location ~ /.well-known { allow all; } client_max_body_size 50m; }
The one below, instead, is the generated systemd service needed to automatically start (and re-start) the ghost project (/lib/systemd/system/ghost_ghostproject-lan.service):
[Unit] Description=Ghost systemd service for blog: ghostproject-lan Documentation=https://ghost.org/docs/ [Service] Type=simple WorkingDirectory=/var/www/ghost-project User=998 Environment="NODE_ENV=production" ExecStart=/usr/bin/node /opt/npm/bin/ghost run Restart=always [Install] WantedBy=multi-user.target
Setting up the firewall
If we have a firewall running on our machine (we really should have!) we need to authorize traffic through the needed ports, which in this case are port 80/tcp
and 443/tcp
if we setup SSL. In the example below, I assume UFW (Uncomplicated Firewall) to be in use, since it is the default in Ubuntu. The command we need to run is:
$ sudo ufw allow "Nginx Full"
Conclusions
Ghost is a free and open source blogging platform built with Javascript. In this tutorial we learned how to quickly install a local instance of Ghost in our system for test and development purposes and how to deploy a Ghost blog in production. If you want to know more about the platform, you can simply visit the project official website.