Skip to content
Bytearium

No ads or affiliate links active

Homelab & storage

Minecraft server backups: automate and restore

Minecraft server backup: run save-off, save-all flush and save-on on a live server, keep a copy offsite and rehearse the restore before you need it.

A backup is a promise to your future self, and the future self is rarely in a good mood when it collects. This guide makes the promise testable.

The short answer

A Minecraft server backup is a copy of the server directory, made while the world files are in a consistent state, stored on a second machine, and restored once as a test. On a running Java server the sequence is save-off, save-all flush, copy, save-on [1][2]. Send these commands over RCON from a script. Keep the RCON password out of the script, and make sure save-on runs even when the copy fails. Our assessment: the restore test is the part that turns a file into a backup, so do it before you need it. Everything below applies to your own worlds and servers.

How it works

  1. What to back up. The world lives in the folder named by level-name, which is world by default [3]. The Minecraft Wiki recommends copying the whole server directory, because you also get server.properties, the ban lists and the other configuration [2]. We agree, with two exceptions that can be rebuilt: logs and crash reports. The layout inside the world changed with Minecraft 26.1. Before that, vanilla kept the Overworld in the world folder and the Nether and End in DIM-1 and DIM1, and Paper kept them in separate world_nether and world_the_end folders [18]. Since 26.1, all default dimensions sit under dimensions/minecraft/ in the world folder, and player files moved to a players folder with data, stats and advancements inside [5][6]. Paper's per-world settings follow the same pattern, in world/dimensions/<namespace>/<dimension>/paper-world.yml [8]. Copying the whole directory means you do not have to care which layout you run. Plugins with an external database need their own dump: a copy of the server folder does not contain that database.
  2. Why a plain copy can break. The server writes terrain into region files, and a copy made in the middle of a write can contain half-old, half-new data [18]. The save commands exist for this. save-off stops the server from writing to the level files so you can copy them, queues level changes meanwhile, and is reset when the server restarts. It does not cover player, advancement and statistics files [1]. save-all flush writes all players and chunks to disk immediately and freezes the server for a short time. The plain save-all only marks chunks as needing a save [1]. save-on turns writing back on [1]. All three need permission level 4 and work on a dedicated server [1]. The sources disagree on the order. syntaxmine.com says save-off, then save-all flush [18], nicompter.de runs the flush first [19]. We use save-off first: after it, nothing new reaches the files, so the flush that follows writes the final state. That is our reasoning, not a sourced statement. Because player, advancement and statistics files stay outside save-off [1], a file can still change while you copy it, and that matters for step 6. Run the commands once by hand and watch the console before you trust the script: we found no source that says how soon an RCON reply comes back after save-all flush, so the script's order (flush, then copy) is our assumption.
  3. Online or offline. Stopping the server with /stop saves the world, and then you simply copy the directory [2]. That is the simplest and safest way, and for a server that is allowed to sleep at night it is enough. For a server that runs around the clock, use the online sequence. The flush freezes the server briefly [1], so schedule it for a quiet hour.
  4. RCON without leaking the password. RCON is the remote console. You enable it with enable-rcon=true, rcon.port and rcon.password in server.properties, and the default port is 25575 [3][4]. The Wiki warns that RCON is not encrypted, that the password can be intercepted on untrusted networks, and that you should ideally connect from localhost only [3]. Never forward the port to the internet. broadcast-rcon-to-ops (default true) sends command output to online operators, so every backup would show up in their chat unless you set it to false [3][4]. The command-line client mcrcon takes host, port and password as options, and it also reads MCRCON_HOST, MCRCON_PORT and MCRCON_PASS from the environment. Command-line options override the environment, and commands with spaces need quotes [9]. Use the environment. A password given with -p becomes part of the process's command line, and the Linux documentation says that line is readable by everybody unless the system mounts /proc with hidepid [10]. Put the password into an environment file with mode 600, which systemd reads through EnvironmentFile= [11]. The same value sits in server.properties, so the backup archive contains the RCON password too. Protect the archive like a key: restrictive permissions locally (umask 077 in the script) and an encrypted target offsite.
  5. Make save-on unavoidable. If the script dies between save-off and save-on, the server keeps running without writing the world. The Wiki says the setting resets on restart [1], so a crash does not make it permanent, but a running server could queue changes for hours. Bash can run a command when the shell exits: a trap on EXIT runs the command before the shell terminates, including after an error [13]. A signal that cannot be trapped, such as SIGKILL, still escapes it, and so does a power cut. nicompter.de uses the same idea [19]. Our script also remembers whether it turned saving off, so it does not send a needless save-on.
  6. Archive, check, copy away. tar exits with 0 on success, 2 on a fatal error, and 1 when some files changed while they were archived [14]. Exit code 1 is therefore plausible here, because of the files that save-off does not cover. We treat 1 as a warning that the restore test must confirm, and 2 as a failure. Run tar under nice -n 19 so the backup yields to the game [15]. We skip ionice -c 3 on purpose: the idle class only gets disk time when nobody else asks for it [16], and while saving is off the server queues changes in memory, so a stretched-out backup has a price. Then list the archive with tar -tzf to see that it can be read, write a SHA-256 checksum next to it and copy both off the machine. The syntaxmine.com guide puts it plainly: a copy on the same disk as the world is a second copy of the same risk [18]. rsync pushes over a remote shell to [USER@]HOST:DEST [17].
  7. Schedule it. A systemd timer starts a service of the same name by default. OnCalendar= sets the time, and Persistent=true runs a missed job after the machine was off [12]. For a service that runs a script and exits, Type=oneshot fits [20]. Check once a week that the newest archive is recent, because a backup that fails quietly looks the same as one that works.
  8. Restore in three sizes. A full restore: stop the server, move the broken world folder aside instead of deleting it, because it may hold newer player data, unpack the backup so the folder sits where the old one was, and start. Announce the rollback window honestly [18]. A single player: stop the server, or make sure that player is offline [2], and copy only that player's file from the backup. It is named after the player's UUID: players/data/<uuid>.dat since 26.1, playerdata/<uuid>.dat before [5][6][18]. Look the UUID up in usercache.json [18]. Stats and advancements are separate files next to it [5]. An area: region files are named r.X.Z.mca and hold 32 by 32 chunks, which is 512 by 512 blocks. A chunk or block coordinate maps to its region by dividing by 32 or by 512 and rounding down [7]. Since the entity and point-of-interest data moved out of the region files, a region exists in the region, entities and poi folders of a dimension, and you restore all three for the same X.Z [7]. Everything inside the region rolls back, chests included [18]. In our assessment, a structure that crosses the region border can end up cut off, so look at the border afterwards.
  9. Restore test, as a duty. A restore you have not tried is a hope. Unpack the newest archive into a separate directory, change server-port and turn RCON off in that copy's server.properties (otherwise it tries to take the same ports), start it, join, and check what you would miss: your base, a chest, your inventory, the plugin data. Write down the date and how long it took. A backup from an older Minecraft version restored on a newer server is upgraded by the newer server and cannot go back [18]. Mojang's notes for 26.1 Snapshot 6 say that worlds are upgraded to the new folder layout and can then no longer be loaded in an older version at all [6], so test an older backup on a copy first. This is the one step you must not skip.

A worked example (commands, not a test)

This is how a setup could look. The paths, host and file names are examples, and we did not run this against a real Minecraft server for this article. We only checked the control flow in a sandbox with a stand-in for mcrcon: when we simulated a failure at save-off, at the flush and at tar, save-on was still sent each time. Assume the server lives in /srv/minecraft, runs as the user minecraft, has enable-rcon=true on 127.0.0.1:25575, and mcrcon is installed.

First the environment file with the secrets, readable only by its owner:

sudo touch /etc/mc-backup.env
sudo chmod 600 /etc/mc-backup.env
sudoedit /etc/mc-backup.env

It contains two lines. The first is the value of rcon.password, the second is where the offsite copy goes, in the [USER@]HOST:DEST form:

MCRCON_PASS=the-value-of-rcon.password
OFFSITE=backup@backup-host.example:/srv/mc-backups/

Then the script in /usr/local/sbin/mc-backup.sh, executable, owned by root and not writable for the server user:

#!/usr/bin/env bash
# Needs MCRCON_PASS and OFFSITE from the environment file. Never put the password in here.
set -euo pipefail
umask 077

SERVER_PARENT=/srv
SERVER_NAME=minecraft
DEST=/srv/backups/minecraft
: "${MCRCON_PASS:?MCRCON_PASS is not set}"
: "${OFFSITE:?OFFSITE is not set}"
export MCRCON_HOST=127.0.0.1 MCRCON_PORT=25575 MCRCON_PASS

NAME="mc-$(date +%F_%H-%M).tar.gz"
ARCHIVE="$DEST/$NAME"
saving_off=0

cleanup() {
  if [ "$saving_off" = 1 ]; then
    mcrcon save-on || echo "WARNING: run save-on by hand" >&2
  fi
}
trap cleanup EXIT

mkdir -p "$DEST"
saving_off=1
mcrcon save-off
mcrcon "save-all flush"

status=0
nice -n 19 tar -C "$SERVER_PARENT" -czf "$ARCHIVE" \
  --exclude="$SERVER_NAME/logs" --exclude="$SERVER_NAME/crash-reports" \
  "$SERVER_NAME" || status=$?

mcrcon save-on
saving_off=0

if [ "$status" -gt 1 ]; then
  echo "tar failed with exit code $status" >&2
  exit "$status"
fi
if [ "$status" -eq 1 ]; then
  echo "tar reports changed files; confirm with a restore test" >&2
fi

tar -tzf "$ARCHIVE" > /dev/null
( cd "$DEST" && sha256sum "$NAME" > "$NAME.sha256" )
rsync -a -- "$ARCHIVE" "$ARCHIVE.sha256" "$OFFSITE"

# Example value: keep two weeks locally. Choose it by how long damage could go unnoticed.
find "$DEST" -name 'mc-*.tar.gz*' -mtime +14 -delete

The service and the timer, as /etc/systemd/system/mc-backup.service and /etc/systemd/system/mc-backup.timer:

[Unit]
Description=Minecraft world backup

[Service]
Type=oneshot
User=minecraft
EnvironmentFile=/etc/mc-backup.env
ExecStart=/usr/local/sbin/mc-backup.sh
[Unit]
Description=Nightly Minecraft backup

[Timer]
OnCalendar=*-*-* 04:15:00
Persistent=true

[Install]
WantedBy=timers.target

Enable it, run it once by hand and read the result:

sudo systemctl daemon-reload
sudo systemctl enable --now mc-backup.timer
sudo systemctl start mc-backup.service
journalctl -u mc-backup.service

The restore test, with an example archive name:

mkdir -p /tmp/mc-restore-test
tar -xzf /srv/backups/minecraft/mc-2026-10-25_04-15.tar.gz -C /tmp/mc-restore-test
cd /tmp/mc-restore-test/minecraft
sed -i 's/^server-port=.*/server-port=25566/; s/^enable-rcon=.*/enable-rcon=false/' server.properties

Start the server from that directory with your usual command, for example java -jar server.jar --nogui for the jar you run, and join on port 25566. If your plugins talk to a shared database or an external service, switch that off in the test copy too.

For one area: block (1500, -600) is chunk (93, -38) and therefore region (2, -2), by our calculation with the formula from the Wiki [7]. You would restore r.2.-2.mca from region, entities and poi of the right dimension, with the server stopped.

If you want versions, deduplication and encryption instead of plain archives, replace the tar step with a restic or Borg run. That is covered in the article on homelab backups with restic or Borg, and the offsite side in the article on offsite backups for your homelab. The 3-2-1 rule frames all of it.

Checklist

  • Back up the whole server directory, not only the world folder, and leave out logs and crash reports.
  • Run save-off, then save-all flush, copy, and save-on, or stop the server and copy.
  • Send the commands over RCON bound to localhost, and never open the RCON port to the internet.
  • Store the RCON password in a file with mode 600 and read it through the environment, never in the script or in -p.
  • Add a trap on EXIT so save-on runs after a failed copy, and test it by making the copy fail once.
  • Check the tar exit code: treat 2 as failure and 1 as a warning that needs a restore test.
  • List the archive with tar -tzf, write a checksum and copy both to a second machine.
  • Protect the archive like a key, because it contains server.properties with the RCON password.
  • Unpack the newest archive into a test directory, change server-port, turn RCON off and join.
  • Write down the date, the archive name and how long the restore took, and repeat the test after every big change and regularly.
  • Check weekly that the newest archive is recent and that the offsite copy arrived.

Primary sources

These sources are the basis of this page. That does not mean every claim was checked individually; whether a review record exists is shown above under “Status”. The interpretation and checklist are our own work.

There is no documented review record for this version yet. The text relies on the sources listed.

How this page was made: Bytearium creates these pages with the help of AI and is responsible for publishing them. AI can be wrong, so we show our sources and review status openly.

  1. Minecraft Wiki/save-all, /save-off, /save-on
  2. Minecraft WikiTutorial: Server maintenance
  3. Minecraft Wikiserver.properties
  4. Minecraft WikiRCON
  5. Minecraft WikiJava Edition level format
  6. MojangMinecraft 26.1 Snapshot 6
  7. Minecraft WikiRegion file format
  8. PaperMCConfiguration
  9. Tiiffimcrcon
  10. Linux man-pagesproc(5)
  11. systemd projectsystemd.exec
  12. systemd projectsystemd.timer
  13. Debianbash-builtins(7)
  14. Linux man-pagestar(1)
  15. Linux man-pagesnice(1)
  16. Linux man-pagesionice(1)
  17. Samba teamrsync(1)
  18. syntaxmine.comHow to Back Up and Restore a Minecraft Server
  19. nicompter.deMinecraft-Server auf Linux: Backups & Performance mit Paper
  20. systemd projectsystemd.service

This page has no ads and no affiliate links.