Minecraft Server Backup Guide: RPO, RTO, Restore Drills, and Safe Rollbacks

Published 9 min read
Minecraft server backupMinecraft VPS hosting backuprestore Minecraft worldRPO and RTO

Reliable Minecraft hosting requires more than occasional snapshots. You need measurable recovery targets, automated backup schedules, and tested restore workflows. Community surveys consistently show that data loss is the number one reason players leave a server permanently. A server that loses 4 hours of player builds due to a failed restore or missing backup loses those players forever. The investment in a proper backup strategy is trivial compared to the community trust it protects.

---

Define Recovery Objectives

Before configuring backups, set explicit targets for how much data you can afford to lose (RPO) and how quickly you must recover (RTO):

RPO (Recovery Point Objective)

The maximum acceptable amount of data loss measured in time. If your RPO is 15 minutes, your backup frequency must be at least every 15 minutes.

RTO (Recovery Time Objective)

The maximum acceptable downtime before the server is back online with recovered data.

Server TypeRPO TargetRTO TargetRationale

Small survival (< 10 players)30-60 minutes1 hourLimited builds, lower risk
Active community (10-30 players)15 minutes30 minutesRegular building activity
Large SMP (30-50+ players)5-15 minutes15 minutesSignificant hourly progress
Event / competition server1-5 minutes5 minutesEvery minute of play has value
Creative / build server30 minutes1 hourSlower pace of changes

Tiered Backup Schedule

Use multiple backup tiers for different recovery scenarios:

Tier 1: Frequent Incremental Backups

#!/bin/bash

<h1>/opt/scripts/backup-incremental.sh</h1>

<h1>Run via cron: */15 * * * * /opt/scripts/backup-incremental.sh</h1>

set -euo pipefail

MC_DIR="/opt/minecraft"

BACKUP_DIR="/backups/minecraft/incremental"

SCREEN_NAME="minecraft"

TIMESTAMP=$(date +%Y%m%d_%H%M)

<h1>Pause world saving</h1>

screen -S "$SCREEN_NAME" -p 0 -X stuff "save-off$(printf '\r')"

screen -S "$SCREEN_NAME" -p 0 -X stuff "save-all flush$(printf '\r')"

sleep 5

<h1>Incremental backup (only changed files since last full)</h1>

rsync -a --delete \

--link-dest="$BACKUP_DIR/latest" \

"$MC_DIR/world/" \

"$MC_DIR/world_nether/" \

"$MC_DIR/world_the_end/" \

"$BACKUP_DIR/$TIMESTAMP/"

<h1>Update latest symlink</h1>

ln -sfn "$BACKUP_DIR/$TIMESTAMP" "$BACKUP_DIR/latest"

<h1>Resume world saving</h1>

screen -S "$SCREEN_NAME" -p 0 -X stuff "save-on$(printf '\r')"

echo "[$(date)] Incremental backup completed: $TIMESTAMP"

Tier 2: Daily Full Backups

#!/bin/bash

<h1>/opt/scripts/backup-full.sh</h1>

<h1>Run via cron: 0 4 * * * /opt/scripts/backup-full.sh</h1>

set -euo pipefail

MC_DIR="/opt/minecraft"

BACKUP_DIR="/backups/minecraft/full"

SCREEN_NAME="minecraft"

TIMESTAMP=$(date +%Y%m%d)

<h1>Pause and save</h1>

screen -S "$SCREEN_NAME" -p 0 -X stuff "save-off$(printf '\r')"

screen -S "$SCREEN_NAME" -p 0 -X stuff "save-all flush$(printf '\r')"

sleep 10

<h1>Full compressed backup of entire server</h1>

tar -czf "$BACKUP_DIR/full_$TIMESTAMP.tar.gz" \

-C "$MC_DIR" \

world/ world_nether/ world_the_end/ \

plugins/ \

server.properties banned-ips.json banned-players.json \

ops.json whitelist.json \

bukkit.yml spigot.yml paper-global.yml paper-world-defaults.yml

<h1>Resume saving</h1>

screen -S "$SCREEN_NAME" -p 0 -X stuff "save-on$(printf '\r')"

<h1>Calculate and log backup size</h1>

SIZE=$(du -sh "$BACKUP_DIR/full_$TIMESTAMP.tar.gz" | cut -f1)

echo "[$(date)] Full backup completed: $SIZE"

Tier 3: Weekly Off-Site Archive

#!/bin/bash

<h1>/opt/scripts/backup-offsite.sh</h1>

<h1>Run via cron: 0 5 * * 0 /opt/scripts/backup-offsite.sh</h1>

BACKUP_DIR="/backups/minecraft/full"

REMOTE="b2:minecraft-backups"

<h1>Upload to Backblaze B2 (or S3, R2, etc.)</h1>

rclone sync "$BACKUP_DIR" "$REMOTE/full/" \

--transfers 4 \

--checkers 8 \

--max-age 8d

<h1>Verify upload</h1>

rclone check "$BACKUP_DIR" "$REMOTE/full/" --max-age 8d

echo "[$(date)] Off-site backup sync completed"

Tier 4: Monthly Archive with Retention

<h1>Retention policy</h1>

<h1>Incremental: keep 7 days</h1>

find /backups/minecraft/incremental/ -maxdepth 1 -mtime +7 -exec rm -rf {} \;

<h1>Full daily: keep 30 days</h1>

find /backups/minecraft/full/ -name "full_*.tar.gz" -mtime +30 -delete

<h1>Off-site: keep 90 days</h1>

rclone delete "$REMOTE/full/" --min-age 90d

<h1>Monthly archive: keep 1 year</h1>

<h1>Copy first-of-month full backup to archive tier</h1>

MONTH_BACKUP=$(ls -1 /backups/minecraft/full/full_$(date +%Y%m)01*.tar.gz 2>/dev/null | head -1)

if [ -n "$MONTH_BACKUP" ]; then

rclone copy "$MONTH_BACKUP" "$REMOTE/archive/"

fi

Backup Scope Checklist

Ensure every backup captures all necessary data:

DataLocationCriticalityNotes

Overworld`world/`CriticalPlayer builds, redstone, entities
Nether`world_nether/`CriticalNether builds and portals
The End`world_the_end/`HighEnd builds, dragon state
Plugins`plugins/`HighConfig files + plugin data
Server config`server.properties`HighCore server settings
Access lists`ops.json`, `whitelist.json`MediumPermissions
Ban lists`banned-*.json`MediumModeration state
Paper/Spigot config`*.yml`MediumPerformance tuning
Server JAR + version`server.jar`LowRe-downloadable

Restore Drills

Backups without tested restores are a false sense of security. Run restore drills monthly.

Monthly Drill Procedure

#!/bin/bash

<h1>restore-drill.sh - Run on test/staging server</h1>

BACKUP_FILE="/backups/minecraft/full/full_$(date +%Y%m%d -d "yesterday").tar.gz"

TEST_DIR="/opt/minecraft-test"

DRILL_LOG="/var/log/restore-drills.log"

echo "=== RESTORE DRILL $(date) ===" >> "$DRILL_LOG"

START=$(date +%s)

<h1>1. Clean test environment</h1>

rm -rf "$TEST_DIR"

mkdir -p "$TEST_DIR"

<h1>2. Extract backup</h1>

tar -xzf "$BACKUP_FILE" -C "$TEST_DIR"

<h1>3. Start test server</h1>

cd "$TEST_DIR"

timeout 120 java -Xms2G -Xmx2G -jar server.jar --nogui &

SERVER_PID=$!

<h1>4. Wait for startup</h1>

for i in $(seq 1 60); do

if grep -q "Done" "$TEST_DIR/logs/latest.log" 2>/dev/null; then

break

fi

sleep 2

done

<h1>5. Validation checks</h1>

CHECKS_PASSED=0

CHECKS_TOTAL=4

<h1>Check: server started</h1>

if grep -q "Done" "$TEST_DIR/logs/latest.log"; then

echo "PASS: Server started successfully" >> "$DRILL_LOG"

((CHECKS_PASSED++))

else

echo "FAIL: Server did not start" >> "$DRILL_LOG"

fi

<h1>Check: world data present</h1>

if [ -d "$TEST_DIR/world/region" ]; then

REGIONS=$(ls "$TEST_DIR/world/region/" | wc -l)

echo "PASS: World data present ($REGIONS regions)" >> "$DRILL_LOG"

((CHECKS_PASSED++))

else

echo "FAIL: World data missing" >> "$DRILL_LOG"

fi

<h1>Check: plugins loaded</h1>

if [ -d "$TEST_DIR/plugins" ]; then

PLUGINS=$(ls "$TEST_DIR/plugins/"*.jar 2>/dev/null | wc -l)

echo "PASS: Plugins present ($PLUGINS jars)" >> "$DRILL_LOG"

((CHECKS_PASSED++))

else

echo "FAIL: Plugins missing" >> "$DRILL_LOG"

fi

<h1>Check: config intact</h1>

if [ -f "$TEST_DIR/server.properties" ]; then

echo "PASS: Config files intact" >> "$DRILL_LOG"

((CHECKS_PASSED++))

else

echo "FAIL: Config files missing" >> "$DRILL_LOG"

fi

<h1>6. Calculate time</h1>

END=$(date +%s)

DURATION=$((END - START))

<h1>7. Cleanup</h1>

kill $SERVER_PID 2>/dev/null

wait $SERVER_PID 2>/dev/null

<h1>8. Report</h1>

echo "Restore time: ${DURATION}s | Checks: $CHECKS_PASSED/$CHECKS_TOTAL" >> "$DRILL_LOG"

echo "=== END DRILL ===" >> "$DRILL_LOG"

Drill Results Template

Drill DateBackup UsedRestore TimeChecks PassedIssues Found

2026-04-01full_20260331.tar.gz47s4/4None
2026-03-01full_20260228.tar.gz52s3/4Plugin config mismatch
2026-02-01full_20260131.tar.gz45s4/4None

Safe Plugin and Mod Rollback

Plugin and mod updates are the most common cause of server issues. Follow this safety process:

Pre-Update Checklist

<h1>1. Snapshot before update</h1>

cp -r plugins/ plugins_backup_$(date +%Y%m%d_%H%M)/

<h1>2. Document current versions</h1>

for jar in plugins/*.jar; do

echo "$jar: $(unzip -p "$jar" plugin.yml 2>/dev/null

grep "version:"
head -1)"

done > plugin_versions_$(date +%Y%m%d).txt

<h1>3. Test on staging first</h1>

cp new-plugin.jar /opt/minecraft-test/plugins/

<h1>Start test server and validate</h1>

<h1>4. Update production with rollback ready</h1>

screen -S minecraft -p 0 -X stuff "stop$(printf '\r')"

sleep 15

cp new-plugin.jar plugins/

<h1>Start server</h1>

Rollback Procedure

<h1>If the update causes issues:</h1>

screen -S minecraft -p 0 -X stuff "stop$(printf '\r')"

sleep 15

<h1>Restore previous plugin version</h1>

cp plugins_backup_YYYYMMDD_HHMM/problem-plugin.jar plugins/

<h1>Restart server</h1>

screen -S minecraft -p 0 -X stuff "./start.sh$(printf '\r')"

<h1>Verify rollback</h1>

tail -f logs/latest.log

grep -i "error\warn\
plugin"

Compare plans on the Pricing page. Review platform options on the Services Overview page. Get migration help via Contact.

Frequently Asked Questions

How much storage do Minecraft backups need?

A typical survival server world with 20 regular players generates 2-10 GB of world data after a few months. Full compressed backups are 30-50% of raw size. Plan for 50-100 GB of backup storage to maintain 30 days of daily backups plus incremental snapshots. Off-site archival storage (Backblaze B2, S3) costs approximately $0.005-0.01/GB/month.

Should I back up the server JAR file?

The server JAR is re-downloadable, so it is low priority for backups. However, including it ensures you can restore to the exact same server version. This matters when plugin compatibility is tied to specific server builds. Include it in full backups but not in incremental ones.

Can I take backups while players are online?

Yes, but use `save-off` before the backup and `save-on` after. This prevents the server from writing to world files during the copy, which could create corrupt chunks. The `save-all flush` command ensures all pending changes are written to disk before the backup begins.

How do I verify a backup is not corrupted?

After creating a compressed backup, verify with `tar -tzf backup.tar.gz > /dev/null` (checks archive integrity). For deeper validation, restore to a test environment and verify the world loads correctly, region files are intact, and plugin data is accessible. Automated restore drills catch corruption that simple checksum verification misses.

What is the best off-site backup provider for game servers?

Backblaze B2 offers the best price-to-reliability ratio at $0.005/GB/month with free egress up to 3x stored data. AWS S3 Glacier is cheaper for archival but has slow retrieval times. Cloudflare R2 has no egress fees, making it cost-effective if you need frequent restores. Use rclone to sync backups to any provider with a single configuration.

Final Takeaway

A Minecraft backup strategy is only as good as its last successful restore drill. Set RPO and RTO targets based on your community size, automate the backup schedule across multiple tiers, sync to off-site storage, and run monthly restore drills. Document plugin versions before every update and keep rollback procedures ready. Your players' trust depends on your ability to recover quickly and completely.