Tutorial

How to Install Ghost on a VPS

By Amith Kumar25 July 202613 min read
Self-hostingGhost
How to Install Ghost on a VPS
Verified 25 Jul 2026 · Ubuntu 24.04 LTS · node:22.23.1, nginx 1.24.0

What you will have

By the end of this tutorial you will install Ghost on a VPS from nothing: a bare Ubuntu 24.04 slice becomes a real publication, run by the Ghost node process, backed by a MySQL 8 database, and served through nginx over real HTTPS at your own domain. Along the way you create an admin account and write and publish a first post from the Ghost editor.

No hosted plan, no per-member fee, no platform between you and your readers. This is for anyone who wants a fast, clean place to write, a blog or a newsletter they own outright, instead of renting space on Substack, Medium, or a WordPress install bent into a blogging tool.

Here is the destination, shown first so you know what you are building toward. The screenshots are not mockups. They come from a genuine install we ran at ghost.boxlab.space while writing this, so the version and the layout match what you will see.

ghost ls
ghost ls on the finished install showing a real Ghost 6.54.0 publication, status running (production), served at https://ghost.boxlab.space on port 2368 and managed by systemd on the lab slice
The published first post on the live Ghost front-end at ghost.boxlab.space over real HTTPS, the Source theme showing the title, byline and body of the post, the destination you are building toward

That one command is how you check on a Ghost install for the rest of its life: the version, whether it is running, the URL, and the process manager keeping it alive. Below it is the first post from that same install, live on the front-end over a real padlock.

What this tutorial covers
  • Putting the stack on a bare Ubuntu 24.04 slice: the Node.js LTS, MySQL 8, nginx, and the ghost CLI.
  • Creating a dedicated directory and a locked-down system user for Ghost to run as.
  • Running ghost install and understanding each prompt it asks you.
  • Creating the admin account and publishing a real first post from the Ghost editor.
  • Hardening the install and keeping it updated with ghost-cli.

Before you begin

Ghost is friendly to install, but it does expect a few things in place first. None of them are heavy.

What you need first
  • A slice running Ubuntu 24.04 LTS that you reach over SSH as a normal user with sudo. Ghost itself is light, but MySQL likes a little room, so a slice with 1 to 2 GB of RAM is a comfortable starting point.
  • A domain or subdomain you control, with access to its DNS. A dedicated subdomain such as ghost.example.com is the usual choice for a publication.
  • The public IPv4 address of your slice, which one DNS record will point at.
  • A non-root user for the install. This one matters: the ghost CLI refuses to run as root, so do not try to install as the root account.

Every command below runs as that ordinary sudo user. Where a step needs root it uses sudo, so you never log in as root directly, which is exactly the habit ghost-cli is enforcing.

Install the stack from a bare box

Ghost is a Node.js application that stores its content in MySQL and sits behind nginx. So before Ghost itself, four pieces go on the box: the Node.js runtime, the MySQL 8 database, the nginx web server, and the ghost command line tool that installs and manages everything. Start with Node, and use the version Ghost supports rather than whatever ships by default.

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --version && npm --version
Installing the Ghost-supported Node.js LTS from the NodeSource repository on a bare Ubuntu 24.04 slice, then reading the versions back: Node v22.23.1 and npm 10.9.8, because Ghost 6 no longer supports the Node 20 line

Read the versions back so you know what you are on. Ghost 6 supports the current Node LTS lines only, so the 20.x series will be refused at install time. On our box these came back as Node v22.23.1 and npm 10.9.8.

Checkpoint node --version should print a v22 or v24 release. If it prints v20 or older, the NodeSource step did not take; re-run it and re-open your shell before moving on, because ghost install checks this first and stops if it is wrong.

Next the database and the web server, then the ghost CLI itself. MySQL 8 is the database Ghost is built and tested against, so use it rather than MariaDB here. Install both, run the bundled MySQL hardening script, and read the versions back.

sudo apt install -y mysql-server nginx
sudo mysql_secure_installation
mysql --version && nginx -v
sudo npm install ghost-cli@latest -g && ghost version
Installing the rest of the stack: MySQL 8.4.10 and nginx 1.24.0 from apt, securing MySQL, then installing the ghost command line tool globally with npm, ghost-cli version 1.30.0

mysql_secure_installation sets a root password and removes the anonymous account and test database in one pass; answer yes to each prompt. On our box MySQL reported 8.4.10 and nginx 1.24.0, and ghost-cli installed at version 1.30.0. That ghost command is the tool that does the heavy lifting from here: install, configure, update, restart, and read logs.

Ghost wants its own home and its own user. Make a directory for it and hand that directory to your normal sudo user, because ghost-cli installs as you and then creates a separate, unprivileged ghost account to actually run the process.

sudo mkdir -p /var/www/ghost
sudo chown $USER:$USER /var/www/ghost && sudo chmod 775 /var/www/ghost
cd /var/www/ghost
Creating the /var/www/ghost directory and handing it to your normal sudo user, because ghost-cli refuses to run as root and will create a locked-down ghost system user to run the process
Checkpoint You should now be sitting in an empty /var/www/ghost owned by your user, with Node, MySQL and nginx installed and ghost version answering. That is the whole stack, ready for Ghost to be dropped into it.

Install Ghost on a VPS with ghost-cli

Before you run it, point your domain at the slice. In your DNS, add an A record for the hostname you want, for example ghost, aimed at the slice's public IPv4 address, and give it a few minutes to take effect. This matters because ghost-cli asks Let's Encrypt for a certificate during the install, and that check only passes once the domain actually resolves to your box.

With DNS in place, run the installer from inside the empty directory. It is interactive: it downloads Ghost, asks a short series of questions, and wires up nginx, HTTPS and a system service for you.

ghost install
Running ghost install and answering each prompt: the blog URL, the MySQL host, user, password and database name, then yes to the nginx, SSL and systemd setup, ending with Ghost was installed successfully
The Ghost admin posts list at ghost.boxlab.space showing the published first post and the default Coming soon post, the dashboard you manage the publication from once ghost-cli finishes the install

Walk each prompt with intent, because the answers become your configuration. Enter your blog URL as the full https:// address, for example https://ghost.example.com, so Ghost writes secure links from the start. For the database, give the MySQL host (127.0.0.1), the root user and the password you set a moment ago, and a database name such as ghost_prod.

Then say yes when it offers to set up a MySQL user for Ghost: it creates a dedicated account scoped to that one database, not root, which is the least-privilege habit you want. Say yes to nginx, yes to SSL and give an email so Ghost can request a free Let's Encrypt certificate, and yes to systemd so the service survives reboots.

When it finishes you will see Ghost was installed successfully!. In those few seconds ghost-cli wrote an nginx server block, obtained and installed a real certificate, created the ghost system user, registered a systemd unit, and started the site. Confirm the whole path answers over HTTPS.

curl --http2 -sI https://your-domain/
Verifying the live site: curl over HTTP/2 returns 200 from the Ghost front page through nginx, and openssl confirms the served certificate is a real Let's Encrypt certificate, so the padlock is genuine

A 200 over HTTP/2 with server: nginx is your live site, and the certificate behind it is issued by a public authority, so the padlock is the real thing rather than a self-signed placeholder.

Checkpoint ghost ls should list one instance as running (production) at your URL. If the site does not answer, check ghost log for the reason; a wrong MySQL password or a DNS record that has not pointed at the slice yet are the two usual causes.

First run: your admin, the editor, your first post

Open your domain with /ghost on the end, for example https://ghost.example.com/ghost. On a fresh install Ghost shows its setup screen and asks you to create the first account, the owner, who administers everything. Give it your name, your email, and a strong password, and a title for the publication. That account is the one you will sign in with from now on, so pick a real password and store it safely.

Once the owner exists, you land in the Ghost admin. You can confirm from the shell that the publication is set up and reporting its version.

curl -s https://your-domain/ghost/api/admin/site/ | jq '.site.version'
Confirming the publication is live after the setup wizard: the admin site endpoint reports Ghost version 6.54, and the published first post returns HTTP 200 on the front-end over the real padlock
The same first post open in the real Ghost admin editor at ghost.boxlab.space, showing the title, the body content and the Update and Unpublish controls, the clean writing surface that is the point of Ghost

Now the part Ghost is known for: the editor. Click New post, give it a title, and start writing. The editor is deliberately quiet, with formatting appearing as you need it and cards for images, bookmarks, embeds and code. When you are ready, hit Publish, choose to publish now, and the post goes live. Open your bare domain in a browser and there it is on the front-end, running the default Source theme over your own certificate, exactly the post shown at the top of this tutorial.

Two more things are worth doing before you write much more. Set your publication title, description and icon under Settings, so the site and its social cards read as yours. And pick a theme: Ghost ships with Source, the clean default in these screenshots, and you can swap it under Settings for another official theme or one of your own, without touching the posts you have already written.

Do not skip this The owner account can change everything about the publication, so its password is the key to the whole site. Use a long, unique one, and once you have configured email you can add staff users with narrower roles instead of sharing the owner login.

Secure and harden your Ghost install

A ghost-cli install is sensibly set up out of the box, but a few checks confirm the locks are on rather than assumed. The most important property is that Ghost is not exposed directly to the internet at all: the node process listens only on loopback, and nginx is the single public front door that terminates TLS.

sudo ss -tlnp | grep -E ':2368|:443'
Hardening checks that pass: ss shows Ghost bound to 127.0.0.1:2368 and nginx to 127.0.0.1:443 (loopback only), ufw allows just 22, 80 and 443, and the response carries the X-Frame-Options, nosniff and HSTS security headers

Ghost should show on 127.0.0.1:2368 and nginx on 443, never 0.0.0.0:2368. Pair that with a firewall that permits only what the site needs, and the security headers nginx is sending on every response.

The firewall should allow SSH, HTTP and HTTPS and nothing else; on Ubuntu that is ufw allow for 22, 80 and 443, then ufw enable. HTTP stays open only so the certificate can renew and so plain requests get redirected up to HTTPS.

Beyond that, three habits keep the install tidy: leave MySQL bound to 127.0.0.1 so the database is never reachable from outside, let the ghost system user own only its own content directory rather than running as root, and keep the admin at /ghost behind that strong owner password. Ghost applies its own security headers through nginx, and you can confirm them with a quick request.

curl -sI https://your-domain/ | grep -iE 'x-frame|content-type-opt|strict-transport'

Seeing X-Frame-Options, X-Content-Type-Options: nosniff and a Strict-Transport-Security line means the browser is being told to refuse framing, to stop guessing content types, and to insist on HTTPS for future visits.

Keep it running and updated

Because ghost-cli manages the whole install, keeping Ghost healthy for the long term is a handful of commands. Updates are the main one: Ghost ships releases often, and an unpatched publication is the one that gets caught. Updating is a single command that fetches the new version, runs any database migrations, and restarts in place.

ghost update
Keeping it running: ghost update checks for migrations and reports all up to date, the systemd unit is enabled so Ghost restarts after a reboot, and ghost log shows the instance answering requests

Run it from the install directory. It reports All up to date! when there is nothing to do, and steps through the migration and restart when there is. The systemd unit ghost-cli registered means the site comes back on its own after a reboot or a crash, and ghost ls confirms it is enabled. When something looks off, ghost log shows the request and error stream, and ghost restart bounces the process cleanly.

One habit finishes the picture: back up before every update. Ghost holds your posts, pages, members and settings in MySQL, and your images and theme in the content directory, so a real backup captures both together. Exporting content, members and the database, storing that copy off the slice, and rehearsing a restore is its own job, and it is the next tutorial in this Ghost cluster.

For now, know that ghost ls, ghost update, ghost log and ghost restart are the four commands you will lean on, and that every one of them is a plain call away because you built the install by hand.

That is a complete, hand-built Ghost publication: the Node LTS, MySQL 8 and nginx on a bare slice, Ghost installed and served over a real certificate, an admin account, a first post live from the editor, the whole thing hardened and set to update itself. You own every layer, so you can fix every layer.

Frequently asked questions

How much RAM does a Ghost VPS need? Ghost itself is light, and the node process on a quiet publication sits in a few hundred megabytes. The part that wants room is MySQL, so a slice with 1 GB of RAM runs a small Ghost site, and 2 GB gives MySQL and nginx comfortable headroom for a busier one. Start on a modest slice, watch memory as traffic grows, and resize up only when the numbers ask for it, which on a VPS is a single command rather than a migration.
Can I use MariaDB instead of MySQL 8 for Ghost? Ghost officially supports MySQL 8, and that is what it is built and tested against, so this tutorial uses it. MariaDB often appears compatible, but Ghost does not test against it and its own tooling checks for MySQL, so you can hit subtle issues on upgrades or migrations that are hard to trace. On a fresh install there is no reason to take that risk: install MySQL 8, point Ghost at it, and you are on the supported path.
Do I have to use ghost-cli, or can I run Ghost another way? You can run Ghost from source or in Docker, and both are valid. The ghost CLI is the officially supported way to install and manage a production instance on a Linux VPS, and it is worth using because it handles the pieces that are easy to get wrong: the nginx config, the Let's Encrypt certificate, the systemd service, and the database user. It also makes updates a one-line, migration-aware step, which is the part you will be glad of a year from now.
Ghost is not sending email. What is happening? Out of the box Ghost uses direct mail, which most slices cannot deliver reliably, so transactional email such as staff invites and member sign-in codes may fail. That is expected on a fresh install and does not affect writing or publishing. The fix is to configure a mail provider over SMTP, which also unlocks newsletters to members. Wiring up email and memberships is a topic of its own and the next step in this Ghost cluster, so leave it until you are ready to send.

Running Ghost on your own slice means the publication answers to you: your posts, your members, your rules on where the data sits, with no per-member fee and nothing between you and your readers. When you are ready to put it live, a ServerCake slice gives you a clean Ubuntu 24.04 box in India, billed in rupees with GST, ready for exactly the steps above.


Run your own publication on a slice

Host Ghost on a slice

Your writing should live on hardware you own, not a platform that takes a cut of every member. Spin up a ServerCake slice, a clean Ubuntu 24.04 box in India billed in rupees with GST, and run Ghost exactly as in this tutorial, from the ghost-cli install to a first post published over real HTTPS.

Get early access

This guide is provided for educational purposes and offered as is, without warranty of any kind. The commands change the configuration of a server you control, and some can lock you out if run out of order. Test on a non-production machine, keep a second session open, and take a snapshot or backup before you begin. You are responsible for changes made to your own systems; ServerCake accepts no liability for any loss, downtime, lockout, or damage arising from following this guide or running the accompanying script.

Spotted something out of date or have a question?

Built by the ServerCake team

Cloud that speaks India.

ServerCake is India's KYC-verified cloud: VMs and managed databases priced in ₹, billed with GST, on India-resident infrastructure. Reserve your spot for early access.

Reserve your spot