> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Hostinger KVM 4 to KVM 2: A VPS Money-Saving Guide and Ghost CMS Migration Walkthrough
- URL: https://taiwanding.com/en/hostinger-kvm-vps-ghost-cms-migration-guide/
- Published: 2026-03-01T11:36:31.000Z
- Updated: 2026-07-15T02:50:26.000Z
- Author: Kevin Chen
- Tags: #en, #en-tool

## 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:

```bash
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:

```bash
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:

```bash
apt update && apt upgrade -y
reboot

```

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

```bash
adduser youruser
usermod -aG sudo youruser

```

Remember to add an SSH key for the new user:

```bash
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):

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

```

Install MySQL and Nginx:

```bash
apt install -y mysql-server nginx

```

Create the database for Ghost:

```bash
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:

```bash
npm install -g ghost-cli

```

### Step 4: Install Ghost

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

```bash
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):

```bash
# 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:

```bash
# 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:

```json
{
  "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:

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

```

Restart Ghost:

```bash
sudo systemctl restart ghost_taiwanding-com

```

### Step 7: Set up SSL

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

```bash
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:

```bash
cd /var/www/ghost
ghost setup ssl --sslemail your@email.com

```

### 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**:

```bash
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:

```bash
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:

```bash
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:

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

```

### Fail2ban

Guard against brute-force attacks:

```bash
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`:

```bash
# === 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!*