To self-host Ghost on a managed VPS is to publish your articles, gather your members and send your newsletters from a machine of your own, which our teams keep up to date and monitor day and night. Ghost is free software under the MIT license. This guide follows its official documentation: Ghost-CLI or Docker Compose, MySQL, HTTPS, email, backups and updates.
By Simon Vasconcelos Lee
Published on 9 November 202613-minute read
content folder, then update with ghost update or docker compose pull: the project releases updates every week.Ghost is a professional, open source publishing platform built on Node.js. It brings together in one place what an independent publication often assembles from several tools.
According to the documentation, the product has the same features when self-hosted. What surrounds it is yours to arrange: newsletter delivery, web analytics, a CDN and full site backups.
Ghost is released under the MIT license. It allows you to use, copy, modify, merge, publish, distribute, sublicense and sell the software, on one condition: keep the copyright notice and the license text in every copy. You can therefore run Ghost in your company, adapt it and publish for your clients, with no license to buy.
The license covers the code. The name and logo belong to Ghost Foundation and fall under its trademark policy: read it if you are building an offer around the software. Ghost is a registered trademark of Ghost Foundation.
This publication is independent of and not affiliated with, authorized, sponsored, or approved by Ghost Foundation.
The project documents two paths for production. Ghost-CLI is the install it officially recommends, and Docker Compose is a “preview” that it describes as brand new. It also says it is moving toward this Docker tooling instead of updating Ghost-CLI, and it provides an assistant to go from one to the other.
| Item | Ghost-CLI | Docker Compose (preview) |
|---|---|---|
| System | Ubuntu 22.04, 24.04 or 26.04 | A Linux server with Docker 20.10.13 or higher |
| Memory | At least 1 GB | The project cites a server with 2 GB and 1 CPU as an example |
| Web server and HTTPS | Nginx and a Let’s Encrypt certificate, set up by Ghost-CLI | Caddy, in a container, which handles TLS |
| Database | MySQL 8.0 or 8.4 installed on the server | MySQL in a container |
| Web analytics | Not compatible | Available, with a Tinybird account |
| Social web (ActivityPub) | The service hosted by the project, free within its usage limits | The same service by default, or your own, self-hosted |
Point the domain at the server before installing, because the certificate is issued during setup. On a Simafri VPS, visitors arrive on ports 80 and 443, under the server’s domain name, with A and AAAA records. The site address is written in full, with its protocol, such as https://example.com: an IP address causes errors.
Beyond the memory shown above, the project gives no figure for disk or load: the right size depends on your traffic, your images and your media. On a managed VPS, describe your publication to us and we will recommend the size that fits.
Ghost-CLI runs under a user other than root, whose name is not ghost, since that name conflicts with the tool. Create it from the root account, replacing user with the name you choose:
adduser <user>
usermod -aG sudo <user>
su - <user>
Update the system, then install Nginx (1.9.5 at minimum for SSL) and MySQL. The ufw line opens HTTP and HTTPS if that firewall is active:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install nginx
sudo ufw allow 'Nginx Full'
sudo apt-get install mysql-server
On newer versions of Ubuntu, the MySQL root account authenticates through a socket, which Ghost does not support. Give it a password:
sudo mysql
ALTER USER 'root'@'localhost' IDENTIFIED WITH 'caching_sha2_password' BY '<your-new-root-password>';
FLUSH PRIVILEGES;
exit
Ghost requires Node.js 22: versions 20, 21, 23 and above are not supported. The project asks you to install it system-wide, this way:
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
sudo npm install ghost-cli@latest -g
Ghost installs in its own directory, with a specific owner and permissions. Replace sitename with the name of your choice:
sudo mkdir -p /var/www/sitename
sudo chown <user>:<user> /var/www/sitename
sudo chmod 775 /var/www/sitename
cd /var/www/sitename
ghost install
The command then asks its questions, and the project recommends answering yes to each step it offers:
https://.localhost for the host, root and the password you set above, then the database name, which Ghost-CLI creates.The site is now live, and https://example.com/ghost invites you to create the owner account. If the install is interrupted, ghost setup restarts the configuration; if it fails, ghost uninstall cleans up better than deleting the folder. ghost doctor checks the system at any time.
The project presents this method as a “preview”: to its eyes, it is brand new. It is the method that lets you self-host web analytics and ActivityPub.
On a server with Docker installed, fetch the official tooling into /opt/ghost and copy its two example files:
git clone https://github.com/TryGhost/ghost-docker.git /opt/ghost && cd /opt/ghost
cp .env.example .env
cp caddy/Caddyfile.example caddy/Caddyfile
In the .env file, fill in these values:
DOMAIN: the site’s domain, for example example.com.ADMIN_DOMAIN: a separate domain for the admin area, if you want one.DATABASE_ROOT_PASSWORD and DATABASE_PASSWORD: two passwords generated with openssl rand -hex 32. They are set once and for all: once the database is initialized, changing them breaks the connection.In caddy/Caddyfile, uncomment the block for your case: redirecting www to the root domain, or the reverse, or a separate admin domain. Then start Ghost and finish the setup at /ghost:
docker compose pull
docker compose up -d
HTTP_PORT and HTTPS_PORT if Ghost sits behind another reverse proxy. MySQL stays on the containers’ internal network.data/ghost and the database in data/mysql, two locations you can set in .env.-f compose.yml -f compose.ipv6.yml to your docker compose up commands.Analytics requires a Tinybird account, an external service. From /opt/ghost, log in, deploy its configuration and get the tokens to paste into .env, in the “Tinybird configuration” section:
docker compose run --rm tinybird-login
docker compose run --rm tinybird-sync
docker compose run --rm tinybird-deploy
docker compose run --rm tinybird-login get-tokens
ActivityPub works right away with the service hosted by the project, free within usage limits published in its documentation. To self-host your own, uncomment ACTIVITYPUB_TARGET=activitypub:8080. Then enable the profile or profiles you want in .env, and restart the containers:
COMPOSE_PROFILES=analytics,activitypub
docker compose pull
docker compose up -d
For ActivityPub, the project also asks you to recreate Ghost and Caddy, with docker compose up -d --force-recreate ghost caddy.
After the clone and the configuration, bash scripts/migrate.sh copies the content folder, imports the database into the MySQL container and starts Caddy in place of Nginx. It offers to stop Nginx, which takes the site offline: answer no if other sites run on the server. It deletes no data. bash recovery_instructions.sh restores the original site if you interrupt the migration.
The Ghost admin must run over HTTPS, and the project recommends HTTPS for the whole site: without it, usernames and passwords travel in plain text. With Docker Compose, Caddy handles it. With Ghost-CLI, the install sets up the certificate, and one command does it later:
ghost setup ssl
To change the address of a site installed with Ghost-CLI, ghost config url https://example.com updates the configuration, and ghost restart applies it.
The project also advises a separate admin domain, which reduces the attack surface. The path remains /ghost/. With Ghost-CLI, you declare it in config.production.json:
"admin": {
"url": "https://admin.example.com"
}
With Docker Compose, fill in ADMIN_DOMAIN, uncomment its block in the Caddyfile and run docker compose up -d --force-recreate ghost caddy. This domain also needs its own A and AAAA records.
MySQL 8 is the only database the project supports in production. Ghost-CLI installs with MySQL 8.0 or 8.4, and the Compose file provides a MySQL 8.0 image.
ghost config database.client, in the site folder, shows the database in use.Ghost distinguishes two kinds of sending, which do not take the same path. This is the most important thing to set up after the install.
| Sending | Examples | What carries it |
|---|---|---|
| Transactional | Staff invitations, password resets, member signups and login links, login code from a new device | The Ghost mail configuration, over SMTP |
| Newsletters | Articles sent to your members | The Mailgun API, whose keys you enter in the “Email newsletter” settings |
The documentation lists this configuration as required in production. Ghost also emails a code when it detects a login from a new device: set up this relay before inviting your team.
On a Simafri VPS, sending goes through an authenticated SMTP relay, on port 587: the address of an email account you already have, or Simafri Suite.
"mail": {
"transport": "SMTP",
"from": "'Acme Support' <support@example.com>",
"options": {
"host": "smtp.example.com",
"port": 587,
"secure": false,
"requireTLS": true,
"auth": {
"user": "support@example.com",
"pass": "1234567890"
}
}
}
mail__transport=SMTP
mail__options__host=smtp.example.com
mail__options__port=587
mail__options__secure=false
mail__options__requireTLS=true
mail__options__auth__user=support@example.com
mail__options__auth__pass=1234567890
mail__from="'Acme Support' <support@example.com>"
docker compose up -d --force-recreate ghost
secure and requireTLS: Ghost sends through Nodemailer, whose documentation asks for secure set to false on port 587, with the connection then upgraded through STARTTLS. requireTLS refuses to send if that upgrade fails. The repository’s example file shows port 465 and secure=true: replace those two lines.from carries a real address that someone reads, which improves deliverability according to the project, and SPF, DKIM and DMARC records on the sending domain complete the setup.The SMTP relay does not cover newsletters. According to the documentation, bulk email is not sent over SMTP, and Mailgun is, at present, the only bulk email service Ghost supports. You create your Mailgun account and enter its API keys in the Ghost settings.
If you do not send your articles by email, you need no Mailgun account at all: you can ignore those settings. If you prefer another newsletter tool, the project suggests syncing your members to it, for example with Zapier or its integration guides.
Ghost offers three levels of copy, and they do not hold the same things. To rebuild a site exactly, the project asks you to back up the database and the content folder directly.
| Method | What it holds | What it is for |
|---|---|---|
ghost backup (Ghost-CLI) | A zip: content as JSON, members as CSV, themes, images, files and media, routes and redirects | A precaution before an update |
| Export from the admin area | A zip: content and settings, members, post analytics, themes, routes and redirects, as you choose. No comments, member activity history or media | Moving content to another site |
Database and content folder | All of the data | Disaster recovery, or an exact copy |
The export is under “Settings”, “Advanced”, “Import/Export”, “Export” tab, “All data” button. For the full copy with Ghost-CLI, the MySQL account and the database name to use are in config.production.json:
mkdir -p ~/ghost-backups
mysqldump -u ghost_user -p ghost_production > ~/ghost-backups/ghost_backup.sql
cd /var/www/sitename
tar czf ~/ghost-backups/ghost_content_backup.tar.gz content/
With Docker Compose, the official tooling keeps content in data/ghost. The dump follows the form given by the MySQL image, and the root password is already in the container. Also keep a copy of .env and the Caddyfile.
cd /opt/ghost
mkdir -p ~/ghost-backups
docker compose exec -T db sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" ghost' > ~/ghost-backups/ghost_backup.sql
tar czf ~/ghost-backups/ghost_content_backup.tar.gz data/ghost
cd /var/www/sitename
ghost stop
mysql -u ghost_user -p ghost_production < ~/ghost-backups/ghost_backup.sql
tar xzf ~/ghost-backups/ghost_content_backup.tar.gz
sudo chown -R ghost:ghost content
ghost restart
cd /opt/ghost
docker compose stop ghost
docker compose exec -T db sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" ghost' < ~/ghost-backups/ghost_backup.sql
tar xzf ~/ghost-backups/ghost_content_backup.tar.gz
docker compose start ghost
Copy these files off the server, with rsync or scp, and run a test restore on a test machine. If you start again from an export with paid members, connect the same Stripe account before importing your members.
The project releases updates every week and points out that a public web server must stay up to date. Read the release notes, then run the command for your method.
ghost check-update
ghost backup
ghost update
ghost update --rollback returns to the previous version if an update goes wrong.content folder stays in place during updates.ghost update [version] --force with your current version, since its binary dependencies are compiled for a specific Node.js version.cd /opt/ghost
git pull
docker compose pull
docker compose up -d
docker image prune
The last line, which is optional, removes old images. Leave GHOST_VERSION commented out in .env: pinning a version stops these update commands from working.
The documentation gives several settings for a server in production:
mysql_secure_installation after a successful Ghost-CLI install.For load, the project places a CDN and a caching layer in front of Ghost. It specifies that clustering and sharding are not supported: a single instance serves the site, and the cache absorbs the traffic.
Self-hosting means running the server as well as the application. On a managed VPS, our teams take charge of the server and proactively monitor your instance; Ghost remains your software, with its settings and its data.
| Topic | Our teams | You |
|---|---|---|
| The server | System and security updates, server interventions included | The time that suits your business |
| Monitoring | Server watched 24/7, with intervention without waiting for your call | Your content, your members and your subscriptions |
| Ghost | Proactive monitoring: errors, slowdowns and disk space spotted, fix recommended | Its install, updates and settings, its SMTP relay and its Mailgun account, which you apply, yourself or with your AI assistant |
| Backups | Every night, encrypted and kept off the machine; restores included | The restore request, when you need one |
| Migration | Your current server migrated, complimentary | The time of the switch |
If you would rather have our teams step into the application itself, this service is billed by the hour or as a fixed fee, with an estimate shared before the work begins.
With a Simafri managed VPS, our teams keep the server behind your Ghost instance up to date, watch it day and night and step in without waiting for your call; they also proactively monitor your instance and recommend useful fixes, which you apply, yourself or with your AI assistant. Backups are encrypted every night and kept off the machine, restores are included, and migration from your current server is complimentary.
Yes. Ghost is under the MIT license: you can use it in your company and adapt it, as long as you keep its license notice. What remains is the services around it: the server, the email relay, Mailgun if you send newsletters, Tinybird if you want its analytics.
The project recommends Ubuntu 22.04, 24.04 or 26.04, Nginx, Node.js 22, MySQL 8.0 or 8.4, systemd, at least 1 GB of memory, a domain and a non-root user. For Docker Compose, it cites a server with 2 GB and 1 CPU as an example.
Ghost-CLI is the install the project recommends for production. Docker Compose is a preview, the only method that allows web analytics and self-hosted ActivityPub, and the project says it is moving toward this tooling. An assistant lets you move from Ghost-CLI to Docker Compose.
In production, MySQL 8 is the only supported database. The project recommends a reinstall on MySQL 8 from SQLite, and a migration to MySQL 8 from MariaDB. Its documentation makes no provision for PostgreSQL.
No. SMTP is for transactional email: invitations, passwords, member login links. Newsletters require the Mailgun API, the only bulk email service supported. If you do not send articles by email, you need no Mailgun account.
For full recovery, back up the MySQL database with mysqldump and the content folder with tar. The ghost backup command produces a zip before an update. The admin export leaves out comments, member activity history and media.
With Ghost-CLI, run ghost backup then ghost update in the site folder, and ghost update --rollback if something goes wrong. With Docker Compose, run git pull, docker compose pull and docker compose up -d in /opt/ghost.
A managed VPS covers the server: system, security, monitoring and backups. Our teams also proactively monitor your instance and recommend useful fixes, which you apply. Work inside the application is billed by the hour or as a fixed fee.
Simafri
Let's talk about your website
Tell us about your project in a few words: we will get back to you quickly.
Thank you! Your request has been sent. We'll get back to you shortly.
Prefer email? Write to us at support@simafri.com.