Introduction
Deploy Ghost CMS on Ubuntu 24.04 with Nginx by installing the official Ghost production stack, configuring MySQL, installing Node.js 22, running Ghost-CLI, enabling HTTPS, and verifying the site through your domain. This tutorial shows you how to run Ghost CMS on a Raff Technologies Linux VM as a production-ready publishing platform.
Ghost CMS is an open-source publishing platform built for blogs, newsletters, memberships, and modern content websites. In production, Ghost runs as a Node.js application behind Nginx, stores content in MySQL, and uses systemd to stay online after reboots. Ghost-CLI handles most of the operational setup, including Nginx, SSL, systemd, database configuration, and service management.
In this tutorial, you will prepare an Ubuntu 24.04 VM, install Nginx, MySQL, Node.js, and Ghost-CLI, deploy Ghost into /var/www/ghost, configure SSL, create your admin account, and verify that Ghost is running cleanly. We tested this workflow on a Raff CPU-Optimized Tier 2 VM because Ghost is light enough for a small server, but production publishing still deserves predictable CPU, NVMe storage, backups, and unmetered bandwidth.
Note
Ghost-CLI should not be run as the root user. Use a non-root sudo user such as raffadmin, deploy, or your own username. Do not use ghost as the Linux username because it can conflict with Ghost-CLI internals.
Step 1 — Point Your Domain to the Raff VM
Ghost’s production installer expects a real domain name. Before installing Ghost, create a DNS A record that points your domain to your Raff VM’s public IPv4 address.
For example:
| Type | Name | Value |
|---|---|---|
| A | blog | <your_raff_vm_ip> |
| A | www.blog | <your_raff_vm_ip> |
If your domain is example.com, you might use:
textblog.example.com
Wait for DNS to propagate, then verify it from your local computer or from the VM:
bashdig +short blog.example.com
Expected output:
text203.0.113.10
Replace 203.0.113.10 with your actual Raff VM public IP.
You can also test with curl:
bashcurl -I http://blog.example.com
At this stage, the request may fail or return a default Nginx page later. What matters now is that the domain resolves to the correct server.
Warning
Do not install Ghost with a raw IP address as the site URL. Use a real domain such as https://blog.example.com so Ghost-CLI can configure Nginx and SSL correctly.
Step 2 — Update Ubuntu and Install Base Packages
Log in to your Raff VM as your non-root sudo user:
bashssh raffadmin@your_server_ip
Update the package index and upgrade installed packages:
bashsudo apt update
sudo apt upgrade -y
Install base tools required for repository setup, package downloads, and troubleshooting:
bashsudo apt install -y ca-certificates curl gnupg lsb-release ufw unzip
Verify the Ubuntu version:
bashlsb_release -a
Expected output:
textDistributor ID: Ubuntu
Description: Ubuntu 24.04 LTS
Release: 24.04
Codename: noble
This tutorial is written for Ubuntu 24.04 LTS. If you are starting from a fresh server, complete basic hardening first using Raff’s Secure Ubuntu 24.04 Server Setup before exposing the site to the public internet.
Step 3 — Configure the Firewall for HTTP and HTTPS
Ghost needs web traffic on ports 80 and 443. If UFW is already enabled, allow OpenSSH first so you do not lock yourself out:
bashsudo ufw allow OpenSSH
Allow Nginx traffic:
bashsudo ufw allow 'Nginx Full'
Enable UFW if it is not already active:
bashsudo ufw enable
Check the firewall status:
bashsudo ufw status
Expected output:
textStatus: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
This opens only SSH, HTTP, and HTTPS. That is the right starting point for a public Ghost site.
Step 4 — Install Nginx
Ghost uses Nginx as the production reverse proxy. Ghost-CLI can create the final Nginx site configuration for Ghost, but Nginx must be installed first.
Install Nginx:
bashsudo apt install -y nginx
Verify the service is running:
bashsudo systemctl status nginx
You should see active (running) in the output.
Check the installed version:
bashnginx -v
Expected output on Ubuntu 24.04 is similar to:
textnginx version: nginx/1.24.0 (Ubuntu)
Open your domain in a browser:
texthttp://blog.example.com
You should see the default Nginx welcome page. If you want a deeper Nginx walkthrough, Raff’s Install Nginx on Ubuntu 24.04 tutorial explains server blocks, service commands, and common file paths.
Step 5 — Install MySQL 8
Ghost uses MySQL as the recommended production database. Install MySQL Server from Ubuntu’s package repository:
bashsudo apt install -y mysql-server
Verify MySQL is running:
bashsudo systemctl status mysql
Expected status:
textactive (running)
Check the MySQL version:
bashmysql --version
Expected output should show MySQL 8.x:
textmysql Ver 8.0.x for Linux on x86_64
Run the MySQL secure installation helper:
bashsudo mysql_secure_installation
Recommended answers:
textVALIDATE PASSWORD COMPONENT: Y
Password strength: 2
Remove anonymous users: Y
Disallow root login remotely: Y
Remove test database: Y
Reload privilege tables: Y
Ghost-CLI can create the Ghost database and a dedicated Ghost database user during installation, but it needs a MySQL account with sufficient permissions. On Ubuntu, MySQL root commonly uses socket authentication. Set a MySQL root password that Ghost-CLI can use during setup:
bashsudo mysql
Inside the MySQL shell, run:
sqlALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'change_this_mysql_root_password';
FLUSH PRIVILEGES;
EXIT;
Replace change_this_mysql_root_password with a long password stored in your password manager.
Test MySQL password login:
bashmysql -u root -p
Enter the password you just set. If the MySQL prompt opens, exit:
sqlEXIT;
Warning
Do not reuse this MySQL root password anywhere else. Ghost-CLI will use it during install, and you should keep it in a password manager.
Step 6 — Install Node.js 22
Ghost requires a supported Node.js version. For current Ghost production installs on Ubuntu 24.04, use Node.js 22 from NodeSource.
Install the NodeSource signing key and repository:
bashsudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
Add the Node.js 22 repository:
bashNODE_MAJOR=22
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \
| sudo tee /etc/apt/sources.list.d/nodesource.list
Install Node.js:
bashsudo apt update
sudo apt install -y nodejs
Verify Node.js and npm:
bashnode -v
npm -v
Expected output:
textv22.x.x
10.x.x
Ghost is strict about supported Node.js versions. Do not install random Node versions through nvm for this production setup; Ghost’s recommended install expects a system-wide Node.js installation.
Step 7 — Install Ghost-CLI
Ghost-CLI is the official command-line tool for installing, configuring, starting, stopping, updating, and maintaining Ghost.
Install Ghost-CLI globally with npm:
bashsudo npm install ghost-cli@latest -g
Verify the installation:
bashghost --version
Expected output:
textGhost-CLI version: 1.x.x
Check available commands:
bashghost help
You should see commands such as install, start, stop, restart, ls, doctor, and update.
Step 8 — Create the Ghost Install Directory
Ghost must be installed in its own directory with the right owner and permissions.
Create the directory:
bashsudo mkdir -p /var/www/ghost
Set ownership to your non-root user. Replace raffadmin with your actual username:
bashsudo chown raffadmin:raffadmin /var/www/ghost
Set directory permissions:
bashsudo chmod 775 /var/www/ghost
Move into the directory:
bashcd /var/www/ghost
Verify ownership:
bashls -ld /var/www/ghost
Expected output:
textdrwxrwxr-x 2 raffadmin raffadmin 4096 May 4 12:00 /var/www/ghost
If the directory is owned by root, Ghost-CLI will fail or create permission problems later.
Step 9 — Install Ghost in Production Mode
Run the Ghost installer from inside /var/www/ghost:
bashghost install
Ghost-CLI will ask several questions. Use the following answers as a guide.
When asked for the blog URL, enter your HTTPS domain:
texthttps://blog.example.com
For MySQL hostname, use:
textlocalhost
For MySQL username, use:
textroot
For MySQL password, enter the MySQL root password you created earlier.
For the Ghost database name, use a clear name:
textghost_prod
When asked whether Ghost-CLI should create a Ghost MySQL user, choose:
textY
When asked whether to set up Nginx, choose:
textY
When asked whether to set up SSL, choose:
textY
When asked for your email address for Let’s Encrypt notifications, enter a real email address.
When asked whether to set up systemd, choose:
textY
When asked whether to start Ghost, choose:
textY
A successful install ends with output similar to:
textGhost was installed successfully!
Your site is now available on https://blog.example.com
Note
If SSL setup fails, confirm that your DNS A record points to this VM and that ports 80 and 443 are open. After fixing DNS or firewall issues, run ghost setup ssl from /var/www/ghost.
Step 10 — Verify Ghost, Nginx, and systemd
Check Ghost status:
bashghost status
Expected output:
text┌─────────────┬────────────────────┬─────────┬──────────────────────┐
│ Name │ Location │ Version │ Status │
├─────────────┼────────────────────┼─────────┼──────────────────────┤
│ ghost │ /var/www/ghost │ 6.x.x │ running │
└─────────────┴────────────────────┴─────────┴──────────────────────┘
List Ghost installations:
bashghost ls
Check the systemd service created by Ghost:
bashsystemctl status ghost_blog-example-com
The exact service name depends on your domain. If you are unsure, list Ghost services:
bashsystemctl list-units 'ghost_*'
Check Nginx configuration:
bashsudo nginx -t
Expected output:
textnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx:
bashsudo systemctl reload nginx
Finally, check the site from the command line:
bashcurl -I https://blog.example.com
Expected output:
textHTTP/2 200
server: nginx
If the site returns 200, Ghost is running behind Nginx with HTTPS.
Step 11 — Create the Ghost Admin Account
Open your browser and go to:
texthttps://blog.example.com/ghost
Ghost will show the setup screen for the site owner account. Create your admin account with:
- Site title
- Owner name
- Email address
- Strong password
After setup, Ghost takes you to the admin dashboard.
From there, you can:
- Write your first post
- Choose or upload a theme
- Configure staff users
- Set publication settings
- Configure newsletter and membership options
- Add custom navigation
For production use, create a strong admin password and enable good account hygiene immediately. A CMS login page is a public target.
Step 12 — Add Basic Maintenance Commands
Ghost-CLI gives you useful operational commands. Run them from the Ghost install directory:
bashcd /var/www/ghost
Check the installation:
bashghost doctor
Restart Ghost:
bashghost restart
Stop Ghost:
bashghost stop
Start Ghost:
bashghost start
View logs:
bashghost log
Update Ghost when you are ready:
bashghost update
Warning
Before major Ghost updates, take a Raff snapshot or backup first. Ghost updates can include database migrations, and database migrations should never be treated casually on a production publishing site.
You can also check Ghost’s runtime configuration:
bashcat /var/www/ghost/config.production.json
Do not paste this file publicly. It can include database credentials and mail configuration.
Step 13 — Verify SSL Renewal
Ghost-CLI uses Let’s Encrypt when it sets up SSL. Let’s Encrypt certificates expire every 90 days, so automatic renewal must work.
Check Certbot timers:
bashsystemctl list-timers | grep certbot
You should see a scheduled Certbot renewal timer.
Test renewal without changing the certificate:
bashsudo certbot renew --dry-run
Expected successful output includes:
textCongratulations, all simulated renewals succeeded
If you want a deeper SSL hardening walkthrough, use Raff’s Secure Nginx with Let’s Encrypt on Ubuntu 24.04 tutorial after Ghost is working.
Step 14 — Back Up Ghost Content and Database
A Ghost site has two important backup targets:
- MySQL database content
- Uploaded images and themes under
/var/www/ghost/content
Start with a manual Ghost backup from the admin dashboard when needed. For server-level backups, you can also dump the database and copy the content directory.
Find the database name:
bashcat /var/www/ghost/config.production.json | grep database -A 12
For a simple database dump, run:
bashmysqldump -u root -p ghost_prod > ~/ghost_prod_$(date +%F).sql
Back up the content directory:
bashtar -czf ~/ghost_content_$(date +%F).tar.gz -C /var/www/ghost content
Verify the files:
bashls -lh ~/ghost_prod_*.sql ~/ghost_content_*.tar.gz
Expected output:
text-rw-r--r-- 1 raffadmin raffadmin 2.1M May 4 12:45 ghost_prod_2026-05-04.sql
-rw-r--r-- 1 raffadmin raffadmin 8.4M May 4 12:45 ghost_content_2026-05-04.tar.gz
For production, do not leave backups only on the VM. Store them off-server using Raff Object Storage, another secure backup target, or Raff’s backup features. For a broader backup strategy, read Cloud Server Backup Strategies: Snapshots, RPO, and Recovery Planning.
Step 15 — Test the Final Ghost Site
Run these final checks before considering the deployment complete.
Check Ghost:
bashcd /var/www/ghost
ghost status
Check Nginx:
bashsudo nginx -t
sudo systemctl status nginx
Check HTTPS:
bashcurl -I https://blog.example.com
Check HTTP-to-HTTPS redirection:
bashcurl -I http://blog.example.com
Expected redirect output:
textHTTP/1.1 301 Moved Permanently
Location: https://blog.example.com/
Check the admin path:
bashcurl -I https://blog.example.com/ghost
Expected output should include a valid HTTP response from Nginx/Ghost.
Finally, open both pages in your browser:
texthttps://blog.example.com
https://blog.example.com/ghost
You should see the public Ghost site and the Ghost admin login/dashboard.
Step 16 — Roll Back or Remove Ghost
If you need to stop Ghost temporarily:
bashcd /var/www/ghost
ghost stop
To start it again:
bashghost start
To restart after configuration changes:
bashghost restart
If you need to remove Ghost completely, first create a backup. Then run from the install directory:
bashcd /var/www/ghost
ghost uninstall
Ghost-CLI will remove the Ghost service and related configuration. After uninstalling, you can remove the directory if you are certain you no longer need the files:
bashsudo rm -rf /var/www/ghost
Warning
Do not remove /var/www/ghost until you have exported the Ghost database and copied the content directory. That directory contains uploaded images, themes, and other publication assets.
Conclusion
You deployed Ghost CMS on Ubuntu 24.04 with Nginx, MySQL, Node.js 22, Ghost-CLI, HTTPS, and systemd on a Raff VM. Your Ghost site now runs as a production service behind Nginx, uses MySQL for content storage, redirects through HTTPS, and can be managed with Ghost-CLI.
This setup is a good fit for founders, developers, technical writers, newsletters, documentation-style blogs, and content teams that want a faster publishing system than a heavy plugin-based CMS. On Raff, Ghost benefits from NVMe storage, unmetered bandwidth, DDoS protection, snapshots, and the ability to add off-server backups as the site grows.
Next, secure the server with Secure Ubuntu 24.04 Server Setup, review Secure Nginx with Let’s Encrypt, and define a real recovery plan using Cloud Server Backup Strategies.
This tutorial was prepared in Serdar’s infrastructure-first style: install the official stack, verify every layer, back up before risky changes, and do not call a publishing site production-ready until DNS, SSL, systemd, Nginx, and restore paths are all understood.
