Ghost is an open-source blogging platform that helps you create a professional-looking blog. It was launched in 2013 as an alternative to WordPress. It is written in JavaScript and is powered by the Node.js library.
In this tutorial, we will explore how to install Ghost CMS using Nginx and MySQL on a server powered by Debian 12. We will use the Let’s Encrypt SSL certificate to secure our installation.
Prerequisites
A server running Debian 12 with a minimum of 2GB of RAM.
A non-root user with sudo privileges.
A Fully Qualified Domain Name (FQDN) like
example.com
pointing to your server.Make sure everything is updated.
$ sudo apt update $ sudo apt upgrade
Few packages that your system needs.
$ sudo apt install wget curl nano ufw software-properties-common dirmngr apt-transport-https gnupg2 ca-certificates lsb-release debian-archive-keyring unzip -y
Some of these packages may already be installed on your system.
Step 1 – Configure UFW Firewall
The first step is to configure the firewall. Debian comes with ufw (Uncomplicated Firewall) by default.
Check if the firewall is running.
$ sudo ufw status
You should get the following output.
Status: inactive
Allow SSH port so that the firewall doesn’t break the current connection on enabling it.
$ sudo ufw allow OpenSSH
Allow HTTP and HTTPS ports as well.
$ sudo ufw allow http $ sudo ufw allow https
Enable the Firewall
$ sudo ufw enable Command may disrupt existing ssh connections. Proceed with operation (y|n)? y Firewall is active and enabled on system startup
Check the status of the firewall again.
$ sudo ufw status
You should see a similar output.
Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 80/tcp ALLOW Anywhere 443 ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) 80/tcp (v6) ALLOW Anywhere (v6) 443 (v6) ALLOW Anywhere (v6)
Step 2 – Install Nginx
Debian 12 ships with an older version of Nginx. To install the latest version, you need to download the official Nginx repository.
Import Nginx’s signing key.
$ curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
Add the repository for Nginx’s stable version.
$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] \ http://nginx.org/packages/debian `lsb_release -cs` nginx" \ | sudo tee /etc/apt/sources.list.d/nginx.list
Update the system repositories.
$ sudo apt update
Install Nginx.
$ sudo apt install nginx
Verify the installation. The sudo
is required to run the command on Debian.
$ sudo nginx -v nginx version: nginx/1.24.0
Start the Nginx server.
$ sudo systemctl start nginx
Step 3 – Install Node.js
Ghost Installer needs Nodejs to work. The first step is to import the Nodesource GPG key.
$ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
Next, create the Nodesource repository file. We will install Node 18x which is the current LTS (Long Term Support) version which is what Ghost recommends.
$ NODE_MAJOR=18 $ echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
Update the system repository list.
$ sudo apt update
Install Node.
$ sudo apt install nodejs -y
Confirm Node install.
$ node --version v18.18.2
Step 4 – Install MySQL using Docker
Debian doesn’t ship with MySQL anymore. Instead, it ships with MariaDB. Ghost only supports MySQL. You can tweak Ghost to work with MariaDB but it is not recommended. Since MySQL’s official repositories haven’t been updated for Debian 12 at the time of writing this tutorial, we will install it using Docker.
Import the Docker GPG key.
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
Create a Docker repository file.
$ echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/debian \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repository list.
$ sudo apt update
Install Docker and Docker Compose.
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
By default, Docker requires root privileges. If you want to avoid using sudo
every time you run the docker
command, add your username to the docker
group.
$ sudo usermod -aG docker $(whoami)
You will need to log out of the server and back in as the same user to enable this change or use the following command.
$ su - ${USER}
Confirm that your user is added to the Docker group.
$ groups navjot wheel docker
Now that the Docker is installed, we need to create a Docker compose file for MySQL. Create a directory for MySQL docker.
$ mkdir ~/mysql
Create and open the docker-compose.yml
file for editing.
$ nano docker-compose.yml
Paste the following code in it.
services: database: image: container-registry.oracle.com/mysql/community-server:latest container_name: mysql restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_USER: ghost MYSQL_PASSWORD: ghostpassword MYSQL_DATABASE: ghostdb ports: - "3306:3306" volumes: - ./mysql:/var/lib/mysql
Save the file by pressing Ctrl + X and entering Y when prompted.
Here we have set the root password and the MySQL credentials for the Ghost database. These will be created when the container is run.
Start the MySQL container.
$ docker compose up -d
Check the status of the Docker container.
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ec42fb205f1e container-registry.oracle.com/mysql/community-server:latest "/entrypoint.sh mysq…" 4 seconds ago Up 2 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060-33061/tcp mysql
Ghost can connect to the MySQL container using port 3306 and perform operations on it.
Step 5 – Install Ghost
We can install Ghost using Docker as well which can simplify things but we won’t be doing it here.
The Ghost installation will comprise three components – Ghost-CLI command line tool that installs and manages updates to the Ghost blog and the blog package itself.
Install Ghost-CLI
Run the following command to install the Ghost-CLI tool.
$ sudo npm install ghost-cli@latest -g
Prepare Ghost Directory
Create the Ghost root directory.
$ sudo mkdir -p /var/www/html/ghost
Set the ownership of the directory to the current user.
$ sudo chown $USER:$USER /var/www/html/ghost
Set the correct directory permissions.
$ sudo chmod 755 /var/www/html/ghost
Switch to the Ghost directory.
$ cd /var/www/html/ghost
Install Ghost
Installing Ghost is a single command process.
$ ghost install
During the installation, the CLI tool will ask several questions to configure the blog.
- Blog URL: Enter your complete blog URL along with the https protocol. (
https://example.com
) - MySQL Hostname: Press Enter to use the default value of
localhost
since our Ghost install and MySQL are on the same server. - MySQL Username: Enter
ghost
as your MySQL username. - MySQL Password: Enter your root password created before in the docker file.
- Ghost database name: Enter the name of the database (
ghostdb
) configured in the docker file. - Sudo password: It will ask for your sudo password to perform administrative tasks.
- Set up Nginx? Usually, Ghost-CLI detects your Nginx installation and automatically configures it for your blog. But that works only for Nginx installed using the OS package. Since we installed it using Nginx’s repository, Ghost can’t detect it and will skip it automatically.
- Set up SSL?: Since it skipped over the Nginx configuration, the CLI tool will also skip setting up an SSL as well.
- Set up systemd?: Ghost will ask if you want to set up a system service for Ghost. Press Y to proceed.
- Start Ghost?: Press Y to start your Ghost installation. However, it won’t work because Nginx and SSL aren’t configured yet.
Step 6 – Install SSL
Before we proceed, we need to install the Certbot tool and install an SSL certificate for our domain.
To install Certbot, we will use the Snapd package installer. Snapd always carries the latest stable version of Certbot. Debian doesn’t come with Snapd installed though. Install it first.
$ sudo apt install snapd
Ensure that your version of snapd is up to date.
$ sudo snap install core $ sudo snap refresh core
Install Certbot.
$ sudo snap install --classic certbot
Use the following command to ensure that the Certbot command can be run by creating a symbolic link to the /usr/bin
directory.
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
Verify the installation.
$ certbot --version certbot 2.7.1
Generate an SSL certificate.
$ sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d example.com
The above command will download a certificate to the /etc/letsencrypt/live/example.com
directory on your server.
Generate a Diffie-Hellman group certificate.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Check the Certbot renewal scheduler service.
$ sudo systemctl list-timers
You will find snap.certbot.renew.service
as one of the services scheduled to run.
NEXT LEFT LAST PASSED UNIT ACTIVATES Tue 2023-10-17 00:00:00 UTC 14h left Mon 2023-10-16 00:00:18 UTC 9h ago dpkg-db-backup.timer dpkg-db-backup.service Mon 2023-10-16 19:12:00 UTC 9h left Mon 2023-10-16 07:27:11 UTC 2h 17min ago snap.certbot.renew.timer snap.certbot.renew.service Mon 2023-10-16 20:49:14 UTC 11h left Mon 2023-10-16 07:48:12 UTC 1h 56min ago apt-daily.timer apt-daily.service
Do a dry run of the process to check whether the SSL renewal is working fine.
$ sudo certbot renew --dry-run
If you see no errors, you are all set. Your certificate will renew automatically.
Step 7 – Configure Nginx
Create and open the file /etc/nginx/conf.d/ghost.conf
for editing.
$ sudo nano /etc/nginx/conf.d/ghost.conf
Paste the following code in the ghost.conf
file. Replace all instances of example.com
with your domain.
server { listen 80; listen [::]:80; server_name example.com; location / { return 301 https://$server_name$request_uri; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com; access_log /var/log/nginx/ghost.access.log; error_log /var/log/nginx/ghost.error.log; client_max_body_size 20m; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers off; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; ssl_dhparam /etc/ssl/certs/dhparam.pem; ssl_stapling on; ssl_stapling_verify on; resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] 8.8.8.8 8.8.4.4 [2001:4860:4860::8888] [2001:4860:4860::8844] valid=60s; resolver_timeout 2s; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://localhost:2368; } }
The above configuration will redirect all HTTP requests to HTTPS and will serve as a proxy for Ghost service to serve it via your domain.
Save the file by pressing Ctrl + X and entering Y when prompted.
Open the file /etc/nginx/nginx.conf
for editing.
$ sudo nano /etc/nginx/nginx.conf
Add the following line before the line include /etc/nginx/conf.d/*.conf;
.
server_names_hash_bucket_size 64;
Save the file by pressing Ctrl + X and entering Y when prompted.
Verify your Nginx configuration.
$ sudo nginx -t
If you see no errors, it means you are good to go. Restart the Nginx server to apply the configuration.
$ sudo systemctl restart nginx
Step 9 – Run the Site
Now, you can verify your installation by opening https://example.com
in your web browser. You will get the following page indicating a successful installation.
Step 10 – Complete Setup
To finish setting up your Ghost blog, visit https://example.com/ghost
in your browser. The extra /ghost
at the end of your blog’s domain redirects you to Ghost’s Admin Panel or in this case the setup since you are accessing it for the first time.
Here, you will be required to create your Administrator account and choose a blog title.
Enter your details and click the Create account & start publishing button to proceed.
Next, you will be taken to the following screen where you are given options such as writing your first post, customizing your site, and importing members.
We will choose the Explore Ghost admin to explore and go to the dashboard directly. At the end of the setup, you will be greeted with the Ghost’s Administration panel.
If you want to switch to dark mode, you can do so by clicking on the toggle switch next to the settings gear button at the bottom of the settings page.
You will see a default post. You can unpublish or delete it and start posting.
Step 11 – Configure Mailer
Ghost not only acts as a blogging platform but also as a newsletter manager. For day-to-day operations, you can use any transactional mail service to work with Ghost for sending mail. But if you want to send newsletters via Ghost, the only official bulk mailer supported is Mailgun. You can use a different newsletter service too but for that, you will need to use the Zapier integration feature of Ghost.
Let us first configure an SMTP service for transactional emails. For this open the file /var/www/html/ghost/config.production.json
file for editing.
$ nano /var/www/html/ghost/config.production.json
Find the following lines.
"mail": { "transport": "Direct" },
Replace them with the following code.
"mail": { "from": "'HowtoForge Support' [email protected]", "transport": "SMTP", "options": { "host": "YOUR-SES-SERVER-NAME", "port": 465, "service": "SES", "auth": { "user": "YOUR-SES-SMTP-ACCESS-KEY-ID", "pass": "YOUR-SES-SMTP-SECRET-ACCESS-KEY" } } },
Here we are using the Amazon SES Mail service since it is affordable and doesn’t require monthly fees.
Save the file by pressing Ctrl + X and entering Y when prompted. Once finished, restart the Ghost application for the changes to take effect.
$ ghost restart
To configure the newsletter settings, visit the Settings >> Email newsletter section.
Click on the Mailgun configuration link to expand.
Fill in your Mailgun Region, domain, and API key.
Click the Save button on the top right to save the settings.
To test the newsletter delivery, create a new test post, hit publish, and select the Email only option. If you want to publish the post as well, select the Publish and email option.
Click the Continue, final review button to proceed. The next page will ask again for final confirmation.
Click the Send email, right now button to send the newsletter. You will get the following message once the mail is sent.
Check your email for the post.
Step 12 – Update Ghost
There are two types of Ghost updates – Minor updates and Major updates.
First, take a full backup if you want to run a minor update. It creates a backup of all the posts, members, themes, images, files, and redirect files.
$ cd /var/www/html/ghost $ ghost backup
Run the update command to perform the minor update.
$ ghost update
To perform a major update, you should follow the official detailed update guide over at Ghost. Depending upon which version you are at currently and the major version you want to update to, steps will vary.
Conclusion
This concludes our tutorial on how to set up Ghost CMS on your Debian 12 server using Nginx. If you have any questions or any feedback, share them in the comments below.