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
- 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.
- Renewal price goes up: the KVM 2 renewal rate is $14.99/month, so remember to reassess in two years.
- 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
- Go to Cloudflare and edit the A record, pointing the IP to the new box's IP
- Turn the orange cloud back on (Proxied)
- Set the SSL/TLS mode to Full (Strict)
- 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 statusgr→ restart Ghostports→ 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!
Member discussion