Use case · Publishing platform

Self-hosted Ghost on a dedicated Ubuntu server

Self-hosted Ghost runs on Ubuntu 22.04, 24.04 or 26.04 with Node.js 22 LTS, MySQL 8.0 or 8.4, NGINX and systemd, installed by Ghost-CLI from a non-root sudo user on a server with at least 1 GB of memory. Newsletters need a Mailgun account; transactional mail (staff invitations, password resets, member login links) goes through any SMTP service. The one step the official guide leaves open is who reaches /ghost first: whoever does becomes the site owner.

See the machines

The stack Ghost supports

Ghost's docs name one officially recommended production stack and warn that a different setup may run into problems. Use it as the specification for the machine.

ComponentWhat the Ghost docs require
Operating systemUbuntu 22.04, 24.04 or 26.04 LTS
Node.js22 LTS; 20, 21 and 23+ are listed as unsupported
DatabaseMySQL 8.0 or 8.4; MySQL 8 is the only database supported in production
Web serverNGINX, 1.9.5 or newer for SSL
Process managersystemd
MemoryAt least 1 GB
UserA non-root user for running ghost commands; not named ghost
DNSAn A record pointing at the server before install, so SSL can be issued

Ghost listens on 127.0.0.1:2368 by default and NGINX proxies to it, so the application port is never public. Ghost-CLI also creates a low-privilege ghost system user to run the process; your own sudo user only runs the ghost commands. That is why the docs tell you to pick another name for your login.

Sizing the machine

Ghost's stated floor is 1 GB of memory; its Docker guide gives a 2 GB, 1 CPU machine as an example. Above that, the docs give no figure; in our reading it depends on page traffic and on how much of MySQL you want held in memory. Bulk newsletters are delivered by Mailgun, so the mail itself does not leave through your port.

Clustering and sharding are not supported. The docs scale Ghost by putting a CDN and caching layer in front of a single instance, so a bigger box buys headroom, not a second node. That makes a small whole machine a good fit: on our dedicated server range the Moonshot cartridges are described for one service per machine, a small site among them, and the single-socket lines for a web server with its database on the same box, which is exactly Ghost plus MySQL.

The X-Gene ARM cartridge is the wrong line for Ghost: it boots Ubuntu 16.04 and 18.04 only, and Ghost needs 22.04 or later.

Why a whole machine

  • Root, and no other tenant. MySQL, NGINX and Node get every core and all the memory; no neighbour's load shows up in your page times.
  • Unmetered port. Images, video and audio that members load from your publication have no transfer allowance or overage fee behind them.
  • Out-of-band console. Every racked single-, dual- and quad-socket server has HPE iLO, Dell iDRAC or a Supermicro BMC, and among the cartridges, the m510 and m710x have one too. If a ufw rule locks out SSH, you get back in through the console; the other cartridges have power control only.
  • Reinstall from the control panel as often as you need, and Ubuntu up to 26.04 on the widest lines.
  • Delivery. A racked machine is handed over in about 30 minutes once payment is approved; built-to-order lines take 4 to 24 hours. Ready servers lists the machines racked today.

Pick the city nearest your readers; all five locations sit on our own network, AS19624.

Preparing Ubuntu

Log in as root once, create your sudo user, and switch to it for everything after. Turn ufw on with SSH allowed before installing any service. Check that /etc/default/ufw reads IPV6=yes (the Ubuntu default): with it, every rule below applies to IPv6 as well as IPv4, which matters if you added IPv6 addresses at checkout.

adduser yourname            # any name except ghost
usermod -aG sudo yourname
su - yourname

grep IPV6 /etc/default/ufw  # expect IPV6=yes
sudo ufw allow OpenSSH
sudo ufw enable

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install nginx
sudo apt-get install mysql-server

# Ghost cannot use socket auth: give MySQL root a password
sudo mysql
ALTER USER 'root'@'localhost' IDENTIFIED WITH 'caching_sha2_password' BY 'a-long-random-password';
FLUSH PRIVILEGES;
exit

# Node.js 22 from NodeSource, as the Ghost docs install it
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
sudo npm install ghost-cli@latest -g

Installing Ghost without a public /ghost

The Ubuntu guide ends by sending you to /ghost to create the owner account. Between the install starting Ghost and you reaching that page, anyone who loads it first can claim the publication. Our recommendation, not a step in Ghost's guide: close that window by installing with --no-start, then opening HTTPS to your own address only while you create the owner.

sudo ufw allow 'Nginx Full'   # port 80 must answer for Let's Encrypt
sudo mkdir -p /var/www/sitename
sudo chown yourname:yourname /var/www/sitename
sudo chmod 775 /var/www/sitename
cd /var/www/sitename
ghost install --no-start      # say yes to MySQL user, NGINX, SSL, systemd
# now add the SMTP mail section to config.production.json (see Mail below)

# fence: only your own public address reaches 443
sudo ufw delete allow 'Nginx Full'
sudo ufw allow from 203.0.113.10 to any port 443 proto tcp
ghost start
# now open https://example.com/ghost and create the owner

# open the site to everyone
sudo ufw delete allow from 203.0.113.10 to any port 443 proto tcp
sudo ufw allow 'Nginx Full'
  1. Answer the blog URL with the exact https:// address; the docs warn that an IP address causes errors.
  2. Give MySQL root and its password, and let Ghost-CLI create a Ghost-only MySQL user and the database.
  3. Accept NGINX, SSL and systemd. SSL needs an email address for certificate notices.
  4. Before the first ghost start, add the SMTP mail section to config.production.json in the site directory; the file is there once ghost install --no-start finishes.
  5. Replace 203.0.113.10 with your own public IPv4. If your browser reaches the server over IPv6, add the same rule for your IPv6 address, or the page will not load for you.
  6. After the owner exists, run sudo mysql_secure_installation, which the hosting guide strongly recommends.

Mail: two paths, two jobs

Ghost sends two kinds of mail and the docs keep them apart. Transactional mail (staff invitations, password resets, member signups and login links) uses the mail block in config.production.json with the SMTP transport. Bulk newsletters cannot go over plain SMTP: Mailgun is the only supported bulk provider, connected by entering its API keys in Ghost Admin's newsletter settings.

  • Set "transport": "SMTP" with your provider's host, port and credentials, and a real mail.from address; the docs show Mailgun SMTP with "service": "Mailgun", and port 465 with "secure": true for a forced TLS connection. Add it before the first ghost start; after any later edit, run ghost restart.
  • Get transactional mail working first. By default Ghost emails a code when staff sign in from a new device, so a broken mail block can keep you out of your own admin.
  • Mailgun's sandbox domain only delivers to recipients you have authorised, which suits testing and nothing else.
  • Publish SPF, DKIM and DMARC records for every sending domain.
  • Edit the config file rather than passing credentials as command-line flags: arguments show up in ps output and shell history.

Keeping it running

  • Updates. Ghost ships a new version typically every one to two weeks; ghost update applies it and ghost update --rollback steps back.
  • Node upgrades. Install the new Node, then ghost update [version] --force with your current version to rebuild Ghost's binary dependencies. Do not move Node and Ghost in the same step.
  • Backups. ghost backup writes a zip with content as JSON, a member CSV, themes, images and media, and the routes and redirects files. Copy it off the machine; Ghost and its backups are yours to run.
  • Admin domain. The hosting guide suggests a separate admin URL (the admin.url setting) to narrow the attack surface; /ghost/ stays the path.
  • SSH. Keys only, root login off, as the docs advise.

Ghost-CLI or Docker, and the limits

Ghost-CLI is the tested route and uses Ghost's hosted ActivityPub service for the social web features, but it cannot run the new web analytics. The Docker Compose tooling, still labelled a preview, bundles Ghost, Caddy and MySQL and is the route for self-hosting analytics (with a Tinybird account) or ActivityPub in full. The fence above relies on ufw, and ports Docker publishes bypass ufw, so a Docker install needs its own plan for the owner step.

If your team would rather not patch a server every couple of weeks, a managed Ghost host is the better choice, and Ghost's own docs say as much. For a light blog that will never need a whole machine, a Cloud VPS runs the same stack: a virtual machine on a host other customers share, which hands its mail to the SMTP service you use over port 587 or 465.

Questions

Which Node.js version does self-hosted Ghost need?

Node 22 LTS. The Ghost docs list 20, 21 and 23 and above as unsupported, and an unsupported version can stop the install with an "Unsupported version of Node" error. Install Node system-wide from NodeSource, as the Ubuntu guide does.

Can I run Ghost on SQLite or MariaDB in production?

Not as a supported setup. The docs state that MySQL 8 is the only database supported in production, and they recommend migrating from MariaDB to MySQL 8. A SQLite config block exists in the docs, but it is not the production path.

Can my own mail server send the newsletter?

Not for bulk sends. Ghost's documentation states that basic SMTP can't handle bulk newsletter delivery, and Mailgun is the only bulk provider Ghost supports. Your own SMTP service is fine for transactional mail: invitations, password resets and member login links.

What stops someone else claiming the owner account?

Nothing in Ghost: the first person to complete setup at /ghost becomes the owner. Our approach, which Ghost's guide does not include: install with ghost install --no-start, allow HTTPS only from your own address in ufw, start Ghost, create the owner, then open the site. With IPV6=yes in ufw the fence covers IPv6 too.

Can I install Ghost-CLI as root?

Install the npm package with sudo, but run every ghost command as a separate non-root user in the sudo group, as the Ubuntu guide does. Do not call that user ghost: Ghost-CLI creates a system user with that name to run the service.

How much memory does a Ghost server need?

The Ghost docs set the floor at 1 GB, and their Docker guide uses a 2 GB, 1 CPU machine as its example. Past that, the docs set no number, so size for your page traffic and MySQL. Newsletter delivery itself is done by Mailgun, not by your server's mail stack.

A whole machine for your publication

Pick a line, choose Ubuntu 24.04 or 26.04, and follow the steps above from the first SSH login.

See the machines

Related on Server Room

Other use cases