Factorio Field guide

How to select a save on a Factorio headless server

Find Factorio’s data directory, choose an explicit ZIP or latest-save policy and verify the factory loaded after a restart.

How to select a save on a Factorio headless server

Uploading a factory ZIP does not switch the world already running in memory. On the next start, Factorio follows the service's launch arguments: it may load one named file or choose the latest available save. Make that policy explicit before replacing anything.

These examples use the existing Ubuntu installation: account and systemd service factorio, executable /opt/factorio/bin/x64/factorio, and write-data=/srv/factorio. They assume Factorio 2.x, Bash, sudo, Python 3 and a working server. Container and panel-managed installations need changes in their own persistent launch configuration; editing an unrelated host service will not affect them.

First identify the actual startup policy#

The command reference offers two different choices: --start-server FILE and --start-server-load-latest. Use the first to make a deliberate world switch. Use the second only when the instance's save directory contains the one factory you want to resume.

bash
sudo systemctl cat factorio
sudo systemctl show factorio -p User -p WorkingDirectory -p ExecStart
sudo journalctl -u factorio -n 100 --no-pager
sudo cat /srv/factorio/config.ini

Follow the actual --config, --mod-directory and server-settings paths. The application-directory documentation identifies write-data as the runtime location. The service account may use different data from your SSH user's client. A ZIP placed under your home directory is irrelevant if the service never reads it.

An older forum discussion describes the practical symptom: autosaves existed, but a fixed startup filename loaded older progress after an unexpected shutdown. That historical report illustrates why startup policy matters; it is not evidence of a current Factorio defect.

Choose a world by identity and verify it in game. A familiar filename or a recent upload timestamp does not establish which factory it contains.

Inventory without moving the active save#

bash
sudo find /srv/factorio/saves -maxdepth 1 -type f -name '*.zip' \
  -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %f\n' | sort

This lists filesystem dates and sizes, not factory playtime. Uploading, extracting or touching a file can change its date. Do not assume a restored ZIP becomes the right latest-save candidate merely because it arrived most recently. Read the startup journal to identify the path actually loaded.

Before changing worlds, make an external backup. Keep rollback copies outside the active saves directory. With latest-save startup, mixing factories, experiments and restored copies in that directory makes selection harder to reason about.

For a candidate from another computer, retain the source copy and check its game release, expansion content and required mods. An archive integrity check catches transfer damage, but cannot establish that the installed game can load it. Do not solve a missing-mod error by saving over the only original factory without those mods; use the compatibility guide.

Stage the chosen file under a new name#

Arrange a maintenance window, stop normally and inspect the shutdown log. If the current save fails, preserve that state before proceeding.

bash
sudo systemctl stop factorio
sudo systemctl show factorio -p ActiveState -p SubState -p Result
sudo journalctl -u factorio -n 100 --no-pager

Set the source path to a ZIP already uploaded to your SSH account. The example installs it as selected-world.zip; use a different new name if that target already exists. Run the commands one at a time and stop on any failure:

bash
(
set -e
read -r -p "Absolute source save path: " source_save
sudo test -f "$source_save"
sudo python3 - "$source_save" <<'PYZIP'
import sys
import zipfile
with zipfile.ZipFile(sys.argv[1]) as archive:
    if archive.testzip() is not None:
        raise SystemExit('ZIP integrity check failed')
PYZIP
sudo test ! -e /srv/factorio/saves/selected-world.zip
sudo install -o factorio -g factorio -m 600 \
  "$source_save" /srv/factorio/saves/selected-world.zip
sudo -u factorio test -r /srv/factorio/saves/selected-world.zip
)

An existing-target check is intentional: preserving names makes it easier to distinguish the incoming factory from the old one. The source file remains untouched. Leave the save as a ZIP; do not unpack it into another directory with the same world name.

Persist the selection in systemd#

For the manual service from our installation guide, create a dedicated override:

bash
sudo install -d -m 755 /etc/systemd/system/factorio.service.d
sudo nano /etc/systemd/system/factorio.service.d/selected-save.conf

Use the following only if your inspected paths match. Preserve any additional launch options required by your actual server:

ini
[Service]
ExecStart=
ExecStart=/opt/factorio/bin/x64/factorio --config /srv/factorio/config.ini --mod-directory /srv/factorio/mods --start-server /srv/factorio/saves/selected-world.zip --server-settings /srv/factorio/server-settings.json --port 34197

The empty ExecStart= clears the inherited command; the next line replaces it. Review other drop-ins too: a later override can change the effective command. If the inspection showed different arguments or a different service identity, adapt the example rather than replacing a working setup blindly.

bash
sudo systemctl daemon-reload
sudo systemctl show factorio -p ExecStart
sudo systemctl start factorio
sudo journalctl -u factorio -n 100 --no-pager

Check the logged save path, game version and loaded mods. Then connect from a matching client. Confirm recognizable construction and research, make a small identifiable change, and stop/restart normally. Rejoin to check that the change remains.

Account for the next unexpected shutdown#

Explicit-file startup is predictable about the chosen path. It does not automatically select a newer autosave after a crash. The headless documentation describes saving on exit, but a forced termination may prevent that final write. Keep rotating autosaves and select a recovery candidate deliberately when needed.

If you prefer latest-save resumption, first archive unrelated worlds outside saves. Edit this same owned override to replace --start-server /srv/factorio/saves/selected-world.zip with --start-server-load-latest, reload systemd and inspect the effective command before restarting. Do not combine both selectors. Verify which path loads after the change; avoid relying on undocumented timestamp tie-breaking.

ObservationCheck before the next change
Old factory startsEffective ExecStart, actual config and logged loaded path
Permission deniedService user and execute permission on parent directories
Save loads but mods failPreserved mod versions and settings
Client cannot connectLoaded world is a separate question from UDP reachability
New save disappears after rebootPersistent volume or supervisor configuration, not another upload

When organizing several instances in a panel for your own VPS, record each instance's world path and startup policy. Keep changes in the system that actually owns its lifecycle.