SameOS ~/tools/factorio-linux-headless-server.md

Building and Operating a Factorio Headless Server on Linux

Published and official references checked 2026-08-29 · SameOS operator

Factorio ships an official Linux headless package without client graphics or sound, so a server does not need the full game UI. It does need a save file to start, and public-list authentication plus every mod version must match the clients. This guide begins with a private server, creates a map, registers systemd, and covers the UDP port and staged update path.

Check the Host and Package

The official wiki currently requires glibc 2.31 or newer. Replacing the system glibc on an old CentOS/RHEL 7 host is riskier than moving to a supported distribution or container. Run the service under an unprivileged factorio account rather than root.

Fetch the stable headless Linux package from Factorio and extract it under /opt/factorio. The stable endpoint follows the current release, so record factorio --version before and after any update.

ldd --version | head -1
curl -fL 'https://factorio.com/get-download/stable/headless/linux64' \
  -o /tmp/factorio-headless.tar.xz

sudo useradd --system --create-home --home-dir /opt/factorio \
  --shell /usr/sbin/nologin factorio
sudo install -d -o factorio -g factorio /opt/factorio
sudo tar -xJf /tmp/factorio-headless.tar.xz \
  --strip-components=1 -C /opt/factorio
sudo chown -R factorio:factorio /opt/factorio
sudo -u factorio /opt/factorio/bin/x64/factorio --version

Create a Save and Server Settings

A headless start requires a save ZIP. Copy an existing single-player save into saves or create a new map with --create. Resource and enemy generation options belong in map-gen-settings and map-settings during creation; changing those inputs after the world exists does not regenerate it as if new.

Copy data/server-settings.example.json and review name, description, max players, visibility, game_password, and require_user_verification. A public listing also needs a factorio.com identity and token. That token is stored as plain text, so keep mode 600 and exclude the file from documentation, logs, and Git.

sudo -u factorio mkdir -p /opt/factorio/saves /opt/factorio/mods /opt/factorio/config
sudo -u factorio /opt/factorio/bin/x64/factorio \
  --create /opt/factorio/saves/friends.zip

sudo -u factorio cp \
  /opt/factorio/data/server-settings.example.json \
  /opt/factorio/data/server-settings.json
sudo chmod 600 /opt/factorio/data/server-settings.json

# Validate JSON after editing
python3 -m json.tool /opt/factorio/data/server-settings.json >/dev/null

Load the Latest Save Under systemd

--start-server-load-latest selects the newest save in the data directory. Keep restore-drill and experimental saves elsewhere or the service may load the wrong world. Factorio saves on a clean exit, so send SIGINT and allow enough stop time.

# /etc/systemd/system/factorio.service
[Unit]
Description=Factorio Headless Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=factorio
Group=factorio
WorkingDirectory=/opt/factorio
ExecStart=/opt/factorio/bin/x64/factorio \
  --start-server-load-latest \
  --server-settings /opt/factorio/data/server-settings.json
KillSignal=SIGINT
TimeoutStopSec=120
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

sudo systemctl daemon-reload
sudo systemctl enable --now factorio
sudo journalctl -u factorio -f

UDP 34197 and Connection Diagnosis

Factorio uses UDP 34197 by default; a TCP rule cannot substitute for it. Every client needs exactly the server game version and mod set. An open port does not bypass the mod checksum check performed during joining.

Join over the LAN first, then inspect router forwarding only when external clients fail. A hidden server does not need the public listing and can be joined directly by IP and port.

sudo ss -lunp | grep ':34197'
sudo ufw allow 34197/udp

# Confirm build, port, and loaded save in the log
sudo journalctl -u factorio -n 120 --no-pager | \
  grep -E 'Loading map|Hosting game|version|port'

Mods, Administrators, and Backups

For a modded world, retain the mods directory and mod-list.json alongside the save. Clients need matching names and versions, so a server mod update is not complete until a real client has synchronized and joined.

Administrator names are kept in server-adminlist.json in the data area near factorio-current.log. After a clean stop, archive saves, mods, config, and server-settings.json. That archive contains any listing token, so it must never be placed under a public web root.

sudo systemctl stop factorio
sudo -u factorio tar -C /opt/factorio -czf \
  /opt/factorio/factorio-data-$(date +%F-%H%M).tar.gz \
  saves mods config data/server-settings.json
sudo systemctl start factorio

gzip -t /opt/factorio/factorio-data-YYYY-MM-DD-HHMM.tar.gz

A Safe Update Sequence

Verification scope and official references

Wube’s official Factorio wiki and support FAQ were checked for the headless package, save creation, settings example, glibc requirement, and UDP 34197. No public-list token or production save was used.

Read the SameOS writing, translation, and review policy

Game server restore drill Valheim Linux server Satisfactory Linux server