Minecraft World Backup and Restore Test
Backing up the world and operational configuration, verifying SHA-256, and testing restore away from the production server
A backup is usable for recovery only when its archive passes SHA-256 verification and Minecraft loads the world. Back up the test world, leave the production world where it is, and check whether Minecraft reads the archive from a temporary directory on a separate port.
Prerequisites
The server from part 05 must have reached
Done (...)!, with the test block still in place. Unless a command says otherwise, run it inminecraft-one-rootin Cloud Shell.
Verification scope
The backup and verification scripts were checked locally and in an Ubuntu container for syntax, failure branches, and isolation paths. A real VM, a Cloud Storage upload, and a Minecraft world load inside Google Cloud were not performed. The current state is
local and container verification complete, real Google Cloud apply unverified.
Why the Server Stops During a Backup
Compressing the directory while Minecraft writes world files can mix files from different points in
time into one archive. minecraft-backup.service follows this order.
- Shut down the running
minecraft.servicecleanly. - After the Java process exits, compress the designated worlds and the operational configuration.
- Create a SHA-256 manifest for the archive.
- Upload both files to Cloud Storage.
- Restart the service that was running before, whether or not the upload succeeded.
This configuration stops the server briefly to fix the file point in time. It suits a server with few players and an announced backup window, where no RCON password or online snapshot procedure was added.
Minecraft World Backups and Isolated Restore lays out the reasoning behind file consistency and backup frequency.
Enabling Automatic Backups
Open terraform.tfvars.
Run: in minecraft-one-root
nano terraform.tfvars
Check the following values.
enable_automatic_backup = true
backup_schedule = "*-*-* 04:30:00"
backup_world_directories = [
"world",
]
backup_schedule uses the systemd OnCalendar format, and the VM’s default time zone is UTC. The
example’s 04:30 UTC is 13:30 in Seoul (KST). If you changed level-name in server.properties,
change the world directory name here too. The official vanilla server keeps the Nether and End
dimensions under world. Add other names to the list only when you have moved to a server
implementation such as Paper that creates a top-level directory per dimension.
Run the preflight check again and see whether the plan contains only a metadata change.
Run
terraform fmt
scripts/preflight.sh
terraform plan -out=enable-backup.tfplan
terraform show enable-backup.tfplan
The plan should change the backup settings in the VM metadata. Do not apply it if you see a VM replacement, a disk deletion, or a firewall change.
Cost impact: metadata change on a running VM
terraform apply enable-backup.tfplan
Changing metadata does not rerun the startup script on a running VM. Stop and start the VM once so the timer and the verification commands get installed.
Run: in Cloud Shell
export MINECRAFT_INSTANCE="$(
terraform output -raw minecraft_instance_name
)"
export MINECRAFT_ZONE="$(
terraform output -raw minecraft_zone
)"
gcloud compute instances stop "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}"
gcloud compute instances start "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}"
Wait until the status reads TERMINATED after the stop command and RUNNING after the start
command. Do not repeat a failing start; check whether it is a Spot capacity error.
The Timer and Server Readiness
A systemd timer is a scheduled job that starts the backup service at a set time.
Check
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo systemctl status \
minecraft-backup.timer \
--no-pager"
You should see Active: active (waiting) and the next run time. If the timer is missing, first
check whether the startup script finished with the new metadata.
Also check that the server is ready again before the backup.
Check
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo journalctl \
-b \
-u minecraft \
-n 100 \
--no-pager"
There should be a Done (...)! from the new boot.
The First Manual Backup
Start the backup service once instead of waiting for the scheduled time. Connected players can be disconnected while it runs.
Run
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo systemctl start minecraft-backup.service"
Check
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo journalctl \
-u minecraft-backup.service \
-n 100 \
--no-pager"
A healthy log shows Uploaded gs://.../backups/minecraft-...tar.gz. Even if the upload failed, the
command below must confirm that the Minecraft service came back.
Check
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo systemctl is-active minecraft"
Do not run the archive check if it is not active. Look at the first error in the backup service
journal and at how the restart trap behaved.
Choosing the Archive and Manifest
Check
export BACKUP_BUCKET="$(
terraform output -raw backup_bucket_name
)"
gcloud storage ls "gs://${BACKUP_BUCKET}/backups/"
One backup creates two objects with the same timestamp.
gs://.../backups/minecraft-20260729T043000Z.tar.gz
gs://.../backups/minecraft-20260729T043000Z.tar.gz.sha256
Do not select a backup when either file is missing. Put only the in-bucket path of the archive you want to test into the variable.
The archive’s config/ holds only the files that exist.
server.propertieswhitelist.jsonops.jsonbanned-players.jsonbanned-ips.json
The restore script does not apply this configuration together with the world. The whitelist or operator list from the time of the backup can differ from your current policy.
Run
export BACKUP_OBJECT="backups/minecraft-YYYYMMDDTHHMMSSZ.tar.gz"
Replace the whole YYYY... portion with a timestamp from the actual listing. The listing shows a
full address such as gs://bucket-name/backups/minecraft-20260729T043000Z.tar.gz, but the variable
takes only backups/minecraft-20260729T043000Z.tar.gz, without the gs://bucket-name/ part. The
archive path ends in .tar.gz.
To roll configuration back, first read the difference between the current files and the archive.
Check: prints only the configuration differences
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo minecraft-restore-config \
${BACKUP_OBJECT}"
The output is a per-file diff. Lines starting with - are the current VM configuration, and lines
starting with + are the configuration at backup time. A file with no difference shows only its
name.
Run it with --apply only after reading every difference and deciding to go back to the
configuration from backup time. Run it in the same Cloud Shell as the preview so that the
BACKUP_OBJECT exported above is passed through. An interactive SSH session on the VM is a new
shell without that variable, so the command fails with an empty argument.
Run: replaces the configuration files
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo minecraft-restore-config \
${BACKUP_OBJECT} --apply"
The command stops the server and replaces the files. If the ownership change or the service restart
fails, it puts the previous configuration back. After applying, check the whitelist, the operator
list, and Done (...)! again.
A Load Test That Leaves the Production World Alone
The verification command downloads the selected archive and manifest into a temporary directory on
the VM. It rejects unsafe absolute paths, .., links, and device files, and extracts only the
configured world directories.
It then copies the production server’s JAR and runs a separate Minecraft process on
127.0.0.1:25566. It does not stop the production service or replace the world in
/srv/minecraft.
During verification, the production server (-Xmx4G) and the verification process (-Xmx2G) run
on the same VM. Their maximum heaps total 6 GiB of the default e2-standard-2 VM’s 8 GiB, leaving
limited room for the operating system and other processes. Run verification with no players online.
If a memory error occurs, record the verification as failed and consider the machine type change in
part 08.
Run
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo minecraft-backup-verify \
${BACKUP_OBJECT}"
A normal result is an OK on the SHA-256 and the following line.
Backup backups/minecraft-...tar.gz loaded successfully in an isolated working directory.
The verification process checks the readiness log, exits, and deletes the temporary directory. If
Done (...)! does not appear within 120 seconds, or the JAR exits, it prints the log and fails. Do
not call a failed archive a “recoverable backup.”
This check goes as far as confirming that the world loads in a server. Whether blocks, signs, and chest contents are correct is hard to verify automatically without changing the production world. For a server that matters, connect from a separate test VM or a local Minecraft and check the recorded coordinates as well.
Restore the Production World Only When Needed
The command below actually replaces the running world. Use it only when a server failure calls for a restore and the same archive has passed the isolated test.
Conditional run: this replaces the production world
gcloud compute ssh "${MINECRAFT_INSTANCE}" \
--zone="${MINECRAFT_ZONE}" \
--command="sudo minecraft-restore \
${BACKUP_OBJECT}"
The restore command rechecks the hash and the archive paths, extracts everything into a staging
directory, and then stops the Minecraft service. It moves the existing world into a temporary
rollback directory and puts the restored world in place. If the move, the ownership change, the
service start, or the is-active check fails, it clears the new world away, puts the existing world
back, and starts the service again. On success it cleans up the temporary rollback directory. It
does not replace configuration files.
Once a new Done (...)! appears in the service log, connect with Java Edition and look for the test
block you placed in part 05. The
human-verified restore test ends only when the coordinates match too.
Backup Retention Period
The default backup bucket marks live objects for deletion after 7 days. With a Cloud Storage soft delete policy in place, deleted objects can carry their own storage cost and retention time.
Check
gcloud storage buckets describe \
"gs://${BACKUP_BUCKET}" \
--format="yaml(lifecycle_config,soft_delete_policy)"
Set backup_live_days from how far back you need to recover and what storage costs. The Terraform
state bucket and the world backup bucket serve different purposes, so do not copy the same lifecycle
between them.
Stop Conditions
| Symptom | Where to check |
|---|---|
| No timer | enable_automatic_backup, whether the startup script finished |
403 on upload | The VM service account’s objectCreator on the bucket |
403 on download | The VM service account’s objectViewer on the bucket |
| Hash mismatch | The timestamp pairing of archive and manifest |
| Missing world directory | backup_world_directories and level-name |
| Verification JAR exits early | The Minecraft log printed by the verification command |
| Server stays down after a backup | The backup service journal and the restart trap |
Completion Check
- A
.sha256object with the same name as the archive exists. minecraft-backup-verifypassed both the hash and the isolated load.- The production
minecraft.servicestayedactivethroughout. - If you enabled automatic backups, the timer is
active (waiting).
To move an existing world, follow Moving an Existing Minecraft World to Google Cloud. To keep using the test world, use Minecraft Server Power and Version Changes for power and JAR changes.
References
Comments
No comments yet. Be the first to leave one.
Pending review