6 min read

Hostinger KVM 4 to KVM 2: A VPS Money-Saving Guide and Ghost CMS Migration Walkthrough

Hostinger KVM 4 to KVM 2: A VPS Money-Saving Guide and Ghost CMS Migration Walkthrough

Introduction

I recently got a renewal notice from Hostinger: KVM 4 was going to cost $371.88 USD per year, which works out to roughly $31 a month. For a box that only runs a single Ghost CMS blog, that price is frankly way too steep, so I decided to take the opportunity to properly survey the VPS market and document the whole moving process along the way.

Why move?

Let's start by looking at how the old box was actually being used:

  • RAM: 16GB provisioned, peaked at around 4GB all year
  • CPU: consistently under 10%
  • Purpose: running nothing but a Ghost CMS blog

In short, a 4 vCPU / 16GB RAM KVM 4 is total overkill for a single blog. Ghost officially recommends a minimum of just 1GB RAM to run, yet here I was using 16GB — that's 12GB of RAM sitting idle and burning money.

VPS price comparison: how much can you save at the same spec?

Since I was moving anyway, I figured I'd do a proper comparison of similar-spec VPS plans on the market. Here's a rundown of specs that are more than enough to run Ghost CMS (2-4 vCPU / 4-16GB RAM):

Provider Plan Spec Monthly Est. annual Notes
Hostinger KVM 4 Renewal 4C/16G/200G ~$31 $371.88 Old plan
Hostinger KVM 2 New account, first purchase 2C/8G/100G $6.99 ~$84 24-month plan
Hetzner CX32 Shared 4C/8G/80G NVMe ~$7.5 ~$90 US data center in Ashburn
Vultr HF High Frequency 2C/4G/128G NVMe $24 $288 Guaranteed Intel 3GHz+
DigitalOcean Premium Intel 2C/4G/80G NVMe $21 $252 Can pin Intel CPU

Final choice: a new Hostinger KVM 2 account

I decided to take advantage of Hostinger's new-purchase promo:

  • KVM 2 / 24-month plan
  • 2 vCPU / 8GB RAM / 100GB NVMe
  • $6.99/month, $167.76 total over two years
  • US data center in Phoenix

$167.76 for two years is more than half off compared to the original KVM 4's $371.88 for a single year.

Money-saving tips

  1. Don't add Daily auto-backup: Hostinger's automatic backup runs $6-12/month, which is $144-288 over two years — almost as expensive as the VPS itself. A cron job you write yourself handles backups for free.
  2. Renewal price goes up: the KVM 2 renewal rate is $14.99/month, so remember to reassess in two years.
  3. Ignore the free domain if you don't need it: the bundled free domain renews at ~$64/year after the first year.

Ghost CMS migration walkthrough

Here are the complete moving steps, from backing up the old box to bringing the new one online.

Environment details

  • Old box: Hostinger KVM 4 / Ubuntu 24.04 with CloudPanel / Phoenix
  • New box: Hostinger KVM 2 / Ubuntu 24.04 / Phoenix
  • Ghost version: v6.19.3
  • Database: MySQL
  • DNS: Cloudflare

Step 1: Back up the old box

SSH into the old box and bundle up Ghost's content folder and config file:

cd /path/to/ghost
sudo tar czf /root/ghost-backup.tar.gz content/ config.production.json

Since the old box was on MySQL, we also need to dump the database:

sudo mysqldump --no-tablespaces -u DB_USER -p'DB_PASSWORD' ghost_db > ~/ghost-db-backup.sql
💡 If your Ghost runs on SQLite, the database file lives inside content/data/ — no separate dump needed.

Step 2: Base setup on the new box

Once the new box is up, update the system first:

apt update && apt upgrade -y
reboot

Create a non-root user (Ghost officially recommends against running as root):

adduser youruser
usermod -aG sudo youruser

Remember to add an SSH key for the new user:

mkdir -p /home/youruser/.ssh
echo "your-public-key" >> /home/youruser/.ssh/authorized_keys
chmod 700 /home/youruser/.ssh
chmod 600 /home/youruser/.ssh/authorized_keys

Step 3: Install Ghost dependencies

Install Node.js v22 (the version Ghost currently requires):

curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs

Install MySQL and Nginx:

apt install -y mysql-server nginx

Create the database for Ghost:

GHOST_DB_PASS=$(openssl rand -base64 24)
echo "Ghost DB Password: $GHOST_DB_PASS"  # write this password down
mysql -u root -e "
CREATE DATABASE ghost_db;
CREATE USER 'ghost_user'@'localhost' IDENTIFIED BY '$GHOST_DB_PASS';
GRANT ALL PRIVILEGES ON ghost_db.* TO 'ghost_user'@'localhost';
FLUSH PRIVILEGES;
"

Install Ghost-CLI:

npm install -g ghost-cli

Step 4: Install Ghost

Switch to the non-root user, create the directory, and install:

sudo mkdir -p /var/www/ghost
sudo chown youruser:youruser /var/www/ghost
cd /var/www/ghost
ghost install \
  --db mysql \
  --dbhost localhost \
  --dbuser ghost_user \
  --dbpass YOUR_DB_PASSWORD \
  --dbname ghost_db \
  --url https://yourdomain.com \
  --process systemd \
  --no-prompt

Step 5: Import the old data

Transfer the backup files to the new box (using your local machine as a relay):

# pull from the old box to local
scp user@OLD_IP:~/ghost-backup.tar.gz ~/ghost-backup.tar.gz
scp user@OLD_IP:~/ghost-db-backup.sql ~/ghost-db-backup.sql

# push from local to the new box
scp ~/ghost-backup.tar.gz user@NEW_IP:/tmp/
scp ~/ghost-db-backup.sql user@NEW_IP:/tmp/

On the new box, import the database and content:

# import the database
sudo mysql -u root ghost_db < /tmp/ghost-db-backup.sql

# restore content
cd /var/www/ghost
sudo tar xzf /tmp/ghost-backup.tar.gz -C /var/www/ghost/
sudo chown -R ghost:ghost /var/www/ghost/content

Step 6: Fix the config file

Extracting the archive overwrites the config with the old one, so we need to adjust it for the new box:

{
  "url": "https://yourdomain.com",
  "server": {
    "port": 2368,
    "host": "127.0.0.1"
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost_user",
      "password": "YOUR_NEW_DB_PASSWORD",
      "database": "ghost_db"
    }
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": ["file", "stdout"]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/www/ghost/content"
  }
}

Remember to fix permissions:

sudo chown ghost:ghost /var/www/ghost/config.production.json
sudo chmod 664 /var/www/ghost/config.production.json

Restart Ghost:

sudo systemctl restart ghost_taiwanding-com

Step 7: Set up SSL

First delete Nginx's default page (otherwise it'll block Ghost):

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -s reload

If you're using Cloudflare, temporarily turn off the proxy (switch the orange cloud to gray) so Let's Encrypt can validate directly:

cd /var/www/ghost
ghost setup ssl --sslemail [email protected]

Step 8: Switch DNS

  1. Go to Cloudflare and edit the A record, pointing the IP to the new box's IP
  2. Turn the orange cloud back on (Proxied)
  3. Set the SSL/TLS mode to Full (Strict)
  4. Confirm the site loads properly in your browser

Step 9: Wrap up

  • Remember to turn off auto-renewal on the old box
  • Once you've confirmed everything is moved over, just let the old box expire on its own

Gotchas I hit

I ran into a few issues during the move — worth documenting:

1. Ghost fails to start — config permission issue

Error message: EACCES: permission denied, open '/var/www/ghost/config.production.json'

Cause: extracting the old backup overwrote the new config.production.json, and the ownership reverted to the old user.

Fix:

sudo chown ghost:ghost /var/www/ghost/config.production.json
sudo chmod 664 /var/www/ghost/config.production.json

2. Cloudflare 522 Error

Cause: the Nginx default page was blocking Ghost's vhost.

Fix: delete the default site:

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -s reload

3. ERR_TOO_MANY_REDIRECTS

Cause: Cloudflare SSL was set to Flexible while Ghost's URL was set to https, causing an infinite redirect loop.

Fix: after installing Let's Encrypt, switch the Cloudflare SSL mode to Full (Strict).

Server hardening

Once the move is done, don't forget the basic security hardening.

SSH hardening

Disable root login and password login, allowing SSH keys only:

sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh
⚠️ Before running this, make absolutely sure your user + SSH key can log in successfully, or you'll lock yourself out.

UFW firewall

Only open the ports you need:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Fail2ban

Guard against brute-force attacks:

sudo apt install -y fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Prettifying the terminal

Now that everything's moved, let's give the terminal a fresh look too. Add this to ~/.bashrc:

# === Hacker Style Prompt ===
GREEN='\[\033[01;32m\]'
CYAN='\[\033[01;36m\]'
RED='\[\033[01;31m\]'
YELLOW='\[\033[01;33m\]'
RESET='\[\033[00m\]'

PS1="${GREEN}┌──[${CYAN}\t${GREEN}]─[${RED}⚡${GREEN}]─[${YELLOW}\u@\h${GREEN}]─[${CYAN}\w${GREEN}]\n${GREEN}└──╼ \$ ${RESET}"

alias ll='ls -lah --color=auto'
alias gs='systemctl status ghost_taiwanding-com'
alias gr='sudo systemctl restart ghost_taiwanding-com'
alias ports='sudo ss -tlnp'

A few handy aliases:

  • gs → check Ghost's status
  • gr → restart Ghost
  • ports → view open ports

Wrap-up

The whole migration took about an hour, and in the end I successfully dropped from KVM 4 ($371.88/year) to a new KVM 2 account ($83.88/year) — saving nearly $290 USD a year.

For a box that only runs Ghost CMS, 2 vCPU / 8GB RAM is more than plenty. Before moving, make sure to check your actual resource usage first — a lot of the time we're paying for resources we never use.

If you're also considering a VPS migration, I hope this article helps, and if you have any questions, feel free to leave a comment!