Ghost is a publishing platform for websites, newsletters, memberships, subscriptions, and social-web publishing. For a conventional production VPS deployment, Ghost's current official guide supports Ubuntu 24.04 with Nginx, Node.js 22 LTS, MySQL 8.0 or 8.4, systemd, and Ghost-CLI.
Raff Technologies is the VM platform used by the original tutorial. The saved tested environment remains Ubuntu 24.04 LTS on Raff 2 vCPU / 2 GB RAM Linux VM; Ghost 6 stack requirements, Node.js 22, MySQL 8, Ghost-CLI, Nginx, and backup guidance reviewed in July 2026. This revision re-verifies the workflow against current Ghost documentation on September 5, 2026. Ghost 6.61.0 is the latest upstream release observed at verification time; the commands below intentionally install the current supported Ghost 6 release through Ghost-CLI rather than claiming a fresh 6.61.0 machine retest.
One important Ghost 6 deployment boundary has changed since older tutorials were written. Ghost-CLI remains an officially documented production install path for Ghost 6.x, but CLI installs do not self-host Ghost's native web analytics service. Social Web support uses Ghost's hosted ActivityPub service in this model. Ghost is moving toward an official Docker Compose deployment for the additional Ghost 6 services, and the Ghost platform team has announced that Ghost-CLI will be deprecated with Ghost 7. If you specifically need fully self-hosted analytics or ActivityPub, review the Docker Compose path instead of assuming this classic Nginx/MySQL/systemd layout includes those services.
Prerequisites:
- An Ubuntu 24.04 server with at least 1 GB RAM; 2 GB or more gives more operational headroom
- A registered domain with DNS pointed to the server
- SSH access and a non-root sudo user that is not named
ghost - Public TCP 80 and 443 available for Nginx and Let's Encrypt
- A protected place for database, mail, and recovery credentials
Step 1 — Verify Ubuntu, DNS, resources, and existing listeners
Confirm the OS and architecture:
cat /etc/os-release uname -m
Check memory and free storage:
free -h df -h /
Check the publication hostname:
dig +short A blog.example.com dig +short AAAA blog.example.com
Publish an AAAA record only when IPv6 actually reaches this VM and is protected consistently.
Inspect current listeners:
sudo ss -tulpn
Ghost's official Ubuntu install expects a registered domain. Do not plan to use a raw IP address as the final Ghost URL; the Ghost-CLI install flow can error when an IP address is used as the site URL.
Verify: Ubuntu should report 24.04, the server should meet the memory/storage floor, DNS should resolve to this server, and ports 80/443 should not be unexpectedly occupied by another production proxy.
Step 2 — Create or verify a non-root sudo administrator
Ghost-CLI must not be run as root. Also do not name the Linux administrator ghost, because that conflicts with Ghost-CLI's service-management model.
If you are starting as root, create an administrative account such as ghost-mgr:
adduser ghost-mgr usermod -aG sudo ghost-mgr su - ghost-mgr
Verify sudo:
sudo whoami
Expected output:
root
Open a second SSH session with this account before firewall changes:
ssh ghost-mgr@your_server_ip
For broader host hardening, use Harden Ubuntu 24.04.
Verify: The non-root user should be able to log in independently, run sudo, and keep a second recovery-capable SSH session open.
Step 3 — Update Ubuntu and prepare the firewall without risking SSH lockout
Update package metadata and review upgrades:
sudo apt update apt list --upgradable 2>/dev/null
Apply updates according to your maintenance policy:
sudo apt upgrade
Install the base packages used by the stack:
sudo apt install -y ca-certificates curl gnupg
If UFW is already active, confirm your actual SSH port is allowed, then allow web traffic:
sudo ufw allow 'Nginx Full' sudo ufw status numbered
If UFW is currently inactive, do not blindly enable it over a remote session. Use the lockout-safe process in Set Up UFW Firewall on Ubuntu 24.04.
Check whether Ubuntu requests a reboot:
if [ -f /var/run/reboot-required ]; then cat /var/run/reboot-required else echo "No reboot required" fi
Verify: Package operations should complete without repository errors, SSH should remain reachable from the second session, and public TCP 80/443 should be permitted when the firewall is active.
Step 4 — Install Nginx and MySQL 8
Install the officially recommended web/database stack from Ubuntu's repositories:
sudo apt install -y nginx mysql-server
Enable and start both services:
sudo systemctl enable --now nginx mysql
Check them:
systemctl is-active nginx mysql nginx -v mysql --version
Current Ghost production documentation supports MySQL 8.0 or 8.4. Do not substitute MariaDB or SQLite in this documented production Ghost-CLI layout simply because the application may run in other unsupported configurations.
Do not expose MySQL port 3306 publicly on this single-server setup.
Verify: Nginx and MySQL should both be active, MySQL should be an 8.x release supported by current Ghost documentation, and port 3306 should not be intentionally opened to the internet.
Step 5 — Configure MySQL root authentication for Ghost-CLI setup
Ubuntu commonly installs the local MySQL root account with socket authentication. Ghost's official production setup needs a MySQL username/password during installation so Ghost-CLI can create the publication database and a restricted Ghost database user.
Open MySQL locally:
sudo mysql
Current Ghost documentation uses caching_sha2_password for this step:
ALTER USER 'root'@'localhost' IDENTIFIED WITH 'caching_sha2_password' BY 'replace_with_a_long_unique_password'; FLUSH PRIVILEGES; EXIT;
Store the new password in a password manager, then test it:
mysql -u root -p
Exit after authentication succeeds:
EXIT;
Older Ghost tutorials often used mysql_native_password. Do not copy that older authentication method into a new Ubuntu 24.04/Ghost 6 deployment when the current Ghost guide explicitly documents caching_sha2_password.
During ghost install, let Ghost-CLI create a dedicated restricted database user. The running publication should not operate as MySQL root.
Verify: mysql -u root -p should authenticate with the new credential, and you should have the credential stored outside the terminal/session before continuing.
Step 6 — Install the required Node.js 22 LTS runtime system-wide
Ghost's current compatibility documentation requires Node.js 22 LTS. Node 20 is unsupported for current Ghost 6, and Node 23+ is also unsupported.
Install Node.js through the system-wide NodeSource path documented by Ghost:
sudo apt-get update sudo apt-get 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 NODE_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 sudo apt-get update sudo apt-get install nodejs -y
Verify:
node --version npm --version
For this Ghost-CLI production pattern, follow Ghost's documented system-wide Node installation instead of relying on a user-specific nvm runtime that may disappear from systemd's environment.
Verify: node --version should report a supported Node 22.x release and should be available to the non-root Ghost administrator without sourcing an interactive version-manager shell.
Step 7 — Install the current Ghost-CLI and verify its environment checks
Install Ghost-CLI globally:
sudo npm install ghost-cli@latest -g
Verify the CLI:
ghost --version ghost help
Run its diagnostic checks where useful:
ghost doctor
Ghost-CLI remains the official tool used by Ghost's classic Ubuntu production installation for Ghost 6.x. However, Ghost's platform team announced in August 2026 that Ghost 6.x will be the last major line usable with Ghost-CLI; Ghost 7 is planned to move the supported deployment model to the official Docker image/Compose environment.
That future deprecation does not make this Ghost 6.x installation invalid today, but it should influence long-term migration planning.
Verify: Ghost-CLI should run as the non-root administrator and should not report an unresolved Node/system compatibility error.
Step 8 — Create the Ghost installation directory with correct ownership
Create a dedicated directory:
sudo mkdir -p /var/www/blog.example.com sudo chown ghost-mgr:ghost-mgr /var/www/blog.example.com sudo chmod 775 /var/www/blog.example.com cd /var/www/blog.example.com
Replace ghost-mgr and blog.example.com with your real account and hostname.
Check ownership:
pwd ls -ld .
Do not pre-create random ghost service users or manually chown internal Ghost paths before Ghost-CLI has performed its own Linux-user/service setup.
Verify: The installation directory should be owned by the non-root administrator and should be the current working directory before you run ghost install.
Step 9 — Install Ghost 6 through Ghost-CLI
Run the production installer from the Ghost directory:
ghost install
At the time this guide was re-verified, upstream's latest release was Ghost 6.61.0. ghost install intentionally follows the current supported release instead of hard-coding that patch version into a long-lived install command.
Use the interactive answers appropriate to your site:
Blog URL: https://blog.example.com MySQL hostname: localhost MySQL username: root MySQL password: the administrative password created earlier Ghost database name: ghost_prod Set up a Ghost MySQL user: Yes Set up NGINX: Yes Set up SSL: Yes Set up systemd: Yes Start Ghost: Yes
Ghost-CLI uses MySQL administrative access during setup, then can create a database account restricted to the Ghost database.
If the setup is interrupted, use:
ghost setup
If installation needs to be removed, prefer:
ghost uninstall
over deleting directories manually, because the CLI can remove related artifacts cleanly.
Verify: The install should complete without stack-check failures and should report a running Ghost instance using Nginx, SSL, systemd, and a restricted Ghost database user.
Step 10 — Verify Ghost, systemd, Nginx, HTTP redirect, and HTTPS
From the installation directory:
cd /var/www/blog.example.com ghost status ghost doctor ghost version
Inspect the Ghost instances known to the CLI:
ghost ls
Check Nginx:
sudo nginx -t systemctl is-active nginx
Inspect Ghost's generated systemd unit(s):
systemctl list-units 'ghost_*' --all
Test the public endpoints:
curl -I http://blog.example.com curl -I https://blog.example.com
HTTP should redirect to HTTPS, and the HTTPS endpoint should return a valid Ghost response with a trusted certificate.
Verify: ghost status should show the site running, ghost doctor should pass its critical checks, Nginx syntax should be valid, and the publication should load over HTTPS.
Step 11 — Create the publication owner and understand the Ghost 6 service boundary
Open:
https://blog.example.com/ghost
Create the publication owner account with a unique email address and strong password. Then configure the site title, design, staff, membership, and integrations.
For newsletters, Ghost's native bulk email delivery is designed around Mailgun rather than ordinary SMTP. Treat newsletter provider configuration as a separate production dependency and keep its credentials out of screenshots/repos.
For Ghost 6 specifically, understand what this Ghost-CLI deployment does and does not self-host:
- The primary Ghost application runs on this VM.
- Nginx, MySQL, Node.js, and systemd run on this VM.
- Social Web features can use Ghost's hosted ActivityPub service.
- Native web analytics is not self-hosted by this Ghost-CLI install.
- Fully self-hosting the newer analytics/ActivityPub service set requires Ghost's Docker Compose deployment, which is still described as a developer-preview path for Ghost 6 and is planned to become the default with Ghost 7.
Verify: The owner should be able to sign in, core publishing should work, and your team should understand whether the classic CLI architecture satisfies the specific Ghost 6 features you need.
Step 12 — Protect Ghost configuration and inspect runtime state without exposing secrets
Ghost's production config lives at:
/var/www/blog.example.com/config.production.json
Inspect metadata without printing its contents:
stat -c '%a %U:%G %n' /var/www/blog.example.com/config.production.json
This file can contain database, mail, storage, and other sensitive configuration. Do not paste it into tickets, public repositories, or chat.
Use Ghost-CLI for routine runtime inspection:
ghost status ghost log ghost doctor
If Ghost fails under systemd and ordinary logs are insufficient, Ghost-CLI documents ghost run as a direct foreground debugging method:
ghost run
Stop that foreground process after diagnosis and return to the managed service.
Verify: config.production.json should not be broadly readable, normal administration should work through Ghost-CLI, and no secret-bearing configuration should have been copied into public logs or documentation.
Step 13 — Create and verify an off-server Ghost backup
From the Ghost installation directory:
cd /var/www/blog.example.com ghost backup
Current Ghost-CLI documentation says this creates a zip backup containing:
- content in JSON format;
- a full members CSV export;
- installed themes, including the active theme;
- images, files, video, and audio media;
routes.yaml;redirects.yamlorredirects.json.
List the generated archive and inspect it without extracting over the live site:
find . -maxdepth 2 -type f -name '*.zip' -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort -r | head unzip -l /path/to/ghost-backup.zip | head -n 40
Move or copy a protected backup off the VM. Do not keep the only recovery copy on the same filesystem as the publication.
Also preserve infrastructure recovery information separately: DNS ownership, mail provider access, SSH/recovery keys, and any secret-bearing server config required to rebuild the exact environment. The Ghost backup is a publication-data backup, not a substitute for every server credential and infrastructure setting.
Raff Data Protection can provide a separate VM-level recovery layer, and Raff Object Storage can serve as an off-server location when your backup transfer tooling supports S3-compatible storage.
Verify: A current Ghost backup archive should exist, its contents should be readable, and at least one protected copy should exist outside the live VM.
Step 14 — Update Ghost and Node.js in the correct order with rollback available
Check available Ghost updates:
cd /var/www/blog.example.com ghost check-update
Create a backup before changing the application:
ghost backup
For normal updates within Ghost 6.x:
ghost update
Ghost-CLI supports rollback when an update goes wrong:
ghost update --rollback
Do not update Node.js and Ghost in one uncontrolled step. Ghost's current Node documentation says to update Node first, then reinstall the current Ghost version's dependencies before attempting a Ghost version upgrade, because Ghost has binary dependencies compiled for the active Node version.
When a supported Node change is required:
ghost version
Update Node through the documented NodeSource repo, then reinstall the same Ghost version to rebuild dependencies:
ghost update YOUR_CURRENT_GHOST_VERSION --force
Only after the current Ghost version is healthy on the new supported Node runtime should you perform the intended Ghost update.
After any update:
ghost doctor ghost status ghost version sudo nginx -t curl -I https://blog.example.com
Verify: The site should remain healthy on a supported Node version, the active Ghost version should be known, HTTPS should work, and a pre-update backup/rollback path should exist.
Step 15 — Run the final security, maintenance, and migration-readiness audit
Check Ghost and the host:
cd /var/www/blog.example.com ghost status ghost doctor ghost ls systemctl --failed df -h / sudo ss -tulpn
Check the public site and Admin:
curl -I https://blog.example.com curl -I https://blog.example.com/ghost/
Confirm:
- Ghost runs as a managed systemd service rather than an ad-hoc terminal process;
- Node.js is still a supported 22.x runtime for the current Ghost 6 line;
- MySQL is 8.0/8.4 and is not publicly exposed;
- Nginx and TLS are healthy;
- the owner/staff list contains only expected users;
config.production.jsonis treated as secret-bearing configuration;- a recent Ghost backup exists off-server;
- mail/provider credentials have owners and rotation paths;
- disk growth from images, media, logs, backups, and MySQL is monitored;
- your team has explicitly chosen classic Ghost-CLI vs the newer Docker Compose architecture based on the Ghost 6 features you need;
- migration planning acknowledges that Ghost-CLI is scheduled for deprecation with Ghost 7.
Do not assume a classic single-instance Ghost-CLI VPS deployment automatically becomes a multi-node/high-availability design. Ghost's documented classic architecture is a single Ghost application with its supporting services; availability and recovery require a separate architecture decision.
Verify: There should be no unsupported runtime, public MySQL listener, broken HTTPS path, missing off-server backup, unexplained failed service, or unplanned Ghost 7 migration dependency.
Troubleshooting
ERROR: Unsupported version of Node or exit status 231
Ghost requires Node.js 22 LTS for the current Ghost 6 production stack. Check:
node --version
If necessary, replace the unsupported runtime with Node 22 through the documented NodeSource repository, then reinstall the current Ghost version's dependencies with ghost update CURRENT_VERSION --force.
Ghost-CLI cannot authenticate to MySQL root
Check whether the root account still uses socket-only auth:
sudo mysql
For the current official Ghost Ubuntu setup, configure a password-backed caching_sha2_password root login for the setup step, test mysql -u root -p, then rerun ghost setup or ghost install as appropriate.
SSL setup fails
Verify DNS and public access:
dig +short A blog.example.com dig +short AAAA blog.example.com sudo ss -lntp | grep -E ':(80|443)\b' || true sudo ufw status numbered
Then run from the Ghost install directory:
ghost setup ssl
Ghost fails after a Node.js upgrade
Ghost documents that Node upgrades require Ghost's binary dependencies to be reinstalled. Get the current Ghost version and force-reinstall that same version:
ghost version ghost update YOUR_CURRENT_GHOST_VERSION --force
A Ghost update fails
Run diagnostics/logs:
ghost doctor ghost log ghost run
If the update changed the installed version and rollback is appropriate:
ghost update --rollback
Native Ghost 6 web analytics is missing on the CLI install
That is an architecture limitation, not necessarily a broken install. Current Ghost documentation says Ghost-CLI deployments do not support the self-hosted web analytics service. Review Ghost's newer Docker Compose deployment if that feature is a requirement.
Conclusion
You now have Ghost CMS running on Ubuntu 24.04 with the current supported classic production stack: Nginx, Node.js 22 LTS, MySQL 8.0/8.4, systemd, Ghost-CLI, HTTPS, application-level backups, and a controlled update/rollback workflow.
The most important 2026 distinction is architectural. Ghost-CLI remains valid for Ghost 6.x, but it is no longer the long-term deployment direction for the project. The newer Ghost 6 services such as fully self-hosted web analytics require the Docker Compose architecture, and the Ghost platform team plans to deprecate Ghost-CLI with Ghost 7. Build new deployments with that migration boundary in mind rather than treating today's CLI stack as permanent.
For adjacent procedures, use Harden Ubuntu 24.04, Set Up UFW Firewall, and Secure Nginx with Let's Encrypt.
Sources
- Ghost — How to Install Ghost on Ubuntu
- Ghost — Supported Node Versions
- Ghost — Ghost CLI
- Ghost — How to Update Ghost
- Ghost 6.0 release
- Ghost releases
- Ghost Platform Team — Deprecating Ghost-CLI
- Guidance and current release re-verified: 2026-09-05