Rust Field guide

Restore a Rust world from backup without losing your rollback

Restore a Rust world from a complete backup: check the archive, version compatibility, identity and an isolated test instance.

Restore a Rust world from backup without losing your rollback

When Rust opens an empty world after an incident, stop restarting it. Preserve the current files, identify the backup's original identity and build, then rehearse recovery in isolation. A large .sav file is only a candidate: it does not prove the world can load or that its accompanying databases match.

This guide covers an existing Ubuntu 24.04 server with sudo access. Examples use service rust, installation /srv/rust/game and identity friends; replace all three if your installation differs. Reserve a maintenance window and enough free space for the backup, the incident state and a separate rehearsal installation.

Keep the untouched backup and incident-state copy until a player has checked the restored world and a save survives restart.

Choose a coherent recovery point#

Record when the damage occurred and what progress an earlier backup will discard. Ask players for a known landmark, building or inventory state to check afterward. Backup timestamps need a timezone; an archive produced after the incident may faithfully contain the damaged world.

Facepunch's identity inventory distinguishes terrain, world saves, player databases and configuration. Its database notes also mention WAL journals. Prefer a complete identity captured while all writers were stopped, together with the matching launch configuration. Copying selected database files from a running server can produce an inconsistent set.

Keep external plugin configuration/data, custom-map assets and the recorded server branch/build with the backup. For a modded server, plugin data may live outside the identity. An identity-only archive does not automatically include it. Check what your backup actually contains before scheduling replacement.

If the backup predates a forced wipe, assess compatibility first. Renaming an old save's protocol suffix does not convert its contents; Facepunch explicitly advises against this workaround. Preserving an old binary also does not guarantee current public clients can join it.

Freeze production and preserve the incident state#

Disable this instance's automatic restarts, updates and scheduled jobs for the maintenance window. Use its authenticated console to request a save when appropriate, then its normal graceful shutdown. If the current world is valuable evidence, avoid an extra save over the last useful rotating copy. Verify the shutdown completed before copying.

Check the actual unit and remaining processes:

bash
sudo systemctl show rust -p ActiveState -p SubState -p MainPID
pgrep -x RustDedicated
sudo ss -lunp

Other Rust instances may still be running. Match their working directories and identities privately; do not stop unrelated games. An inactive unit does not exclude a manually started second writer. Investigate any writer using this identity before proceeding.

Once stopped, preserve the whole current identity, service/launcher configuration and relevant external data in a separate dated incident backup. Record checksums and confirm it can be read. Keep the original recovery archive untouched; do all selection and experimentation on copies.

Unpack into private staging#

Extract your own trusted backup as an ordinary administrator, never directly over /srv/rust/game. The following example expects a locally available gzip tar archive and Python 3.12 extraction filters, available with Ubuntu 24.04. Replace the archive path. Check the expected checksum from your backup record first:

bash
sha256sum /path/to/rust-backup.tar.gz
python3 -B - <<'PY'
from pathlib import Path, PurePosixPath
import tarfile, tempfile
archive = Path('/path/to/rust-backup.tar.gz')
stage = Path(tempfile.mkdtemp(prefix='rust-restore-'))
with tarfile.open(archive, 'r:gz') as source:
    members = source.getmembers()
    for member in members:
        path = PurePosixPath(member.name)
        if path.is_absolute() or '..' in path.parts:
            raise SystemExit('Unsafe archive path')
        if not (member.isfile() or member.isdir()):
            raise SystemExit('Only regular files and directories accepted')
    source.extractall(stage, members=members, filter='data')
print(stage)
PY

This rejects links, special files and unsafe paths before extraction. It does not establish archive authenticity or sufficient disk space. Keep the printed directory private, inspect its layout and verify that the intended identity is present. If the archive uses another format, use an equivalently restricted extraction workflow rather than changing production paths to fit it.

Rehearse without contacting production services#

Prepare a separate compatible installation with its own writable directories, logs and explicit startup settings. Copy the staged data there. Audit every absolute path, symlink, mount, plugin database connection and scheduled job. Changing the working directory alone cannot prevent a copied launcher from writing into the live identity.

Keep public access blocked and allocate different game, query and administration ports. Prevent plugins from contacting production databases, webhooks or shops. Disable Rust+ in the rehearsal: Facepunch documents the launch form +app.port 1-, and says the same companion.id must not operate on multiple servers. Preserve that file privately for the actual recovery.

Read startup logs for the selected world and identity. Stop if the server generates a replacement map, reports incompatible data or loads another directory. Do not resolve an unexplained load failure by deleting databases.

Before cutover, allow only the rehearsal tester through the isolated access path. Join with a compatible client, inspect the agreed landmark and player state, then save and restart the rehearsal to check persistence. Keep public access blocked.

Cut over and prove persistence#

After a successful rehearsal, stop it cleanly. Keep production stopped, retain the old identity separately, then install the verified recovery set into the intended production location. Restore ownership for the real service user and verify the launcher still selects the expected identity, map and configuration.

Start once with players restricted. Join using a compatible client, check the agreed landmark and player state, then make a controlled change, save, stop and restart. Confirm the change persists and the logs show the intended world. A running process or open port cannot establish this.

If validation fails, stop the instance and restore the preserved incident-state set with its matching configuration. Do not merge two generations of databases. Re-enable jobs and public access only after accepting the restored state; retain the recovery archive and rollback copy under your normal backup policy.