What the server asks of the machine
The SS14 docs set no minimum specification. Their infrastructure reference does list the hardware behind the official Wizard's Den servers. A Ryzen 5 5600X (6 cores) with 16 GB of RAM and a 256 GB SSD carries two game servers, and elsewhere a virtual guest with 6 dedicated CPUs and 16 GB carries two more. The busiest of those boxes peaks at about 5 TB of traffic a month.
| What | Default | Set by |
|---|---|---|
| Game netcode | UDP 1212 | net.port |
| Status API (launcher, hub) | TCP 1212 | status.bind |
| Watchdog API | 127.0.0.1:5000 here | Urls, BaseUrl in appsettings.yml |
| Database | SQLite preferences.db | [database] engine |
net.bindto defaults to 0.0.0.0,:: (CVars.cs), so the netcode answers on IPv6 as well once the machine has an address; IPv6 is not in default builds and is added at checkout. The soft cap defaults to 30 players (game.soft_max_players, CCVars.Game.cs) and the tick rate to the guide's 30 (net.tickrate, CVars.cs), which config.toml leaves alone.
Picking the machine
Every processor on our game server line-up is rated 2,000 or more on PassMark's single-thread test, a bar that page takes from the Satisfactory docs, not SS14's. The Xeon E3-1585L v5 entry build (4 cores, 32 GB DDR4, NVMe) is sold in all five cities; an E-2286M with 8 cores in Bucharest, or an EPYC 72F3 in New York or Bucharest, leaves room for more instances. We have not benchmarked SS14 on them.
- Take an x86 line, because the commands build for
linux-x64, so the ARM lines, the X-Gene cartridge among them, are outside them. - Pick the city by ping; the data centers page lists a test IP per city.
- Ready units rent by the hour with a one-hour minimum, enough for a playtest weekend; a permanent station runs monthly.
- Ready servers go live about 30 minutes after the order is approved; a build to your configuration takes 4 to 24 hours.
- The 1 Gbps port on these builds is unmetered.
Block 1, build SS14.Watchdog
Install Ubuntu 24.04 from the Linux images and log in as a sudo user. Microsoft's Ubuntu page shows .NET 10 in the built-in 24.04 feed and says the SDK includes the runtimes, so dotnet-sdk-10.0 covers the watchdog's two prerequisites. The block adds ufw, a no-login ss14 account, the watchdog published into /home/ss14/watchdog, the unit, and a helper, /usr/local/sbin/ss14, for ready, update, restart, revert and versions. It then enables ufw with IPv6 rules, OpenSSH and 1212 UDP and TCP. The OpenSSH profile is port 22 only, so if sshd listens elsewhere, run sudo ufw allow PORT/tcp before block 1.
if sudo apt-get update && sudo apt-get install -y dotnet-sdk-10.0 git sqlite3 curl ufw; then
getent passwd ss14 >/dev/null || sudo useradd --system --create-home --shell /usr/sbin/nologin ss14
if sudo test -e /home/ss14/SS14.Watchdog || sudo test -e /home/ss14/watchdog; then echo "STOP - built before"
else
sudo -u ss14 -H bash -c 'cd ~ && git clone --recursive https://github.com/space-wizards/SS14.Watchdog && dotnet publish SS14.Watchdog/SS14.Watchdog -c Release -r linux-x64 --no-self-contained -o /home/ss14/watchdog' &&
sudo tee /etc/systemd/system/ss14-watchdog.service >/dev/null <<'EOF' &&
[Unit]
Wants=network-online.target
After=network-online.target
[Service]
User=ss14
WorkingDirectory=/home/ss14/watchdog
ExecStart=/home/ss14/watchdog/SS14.Watchdog
Restart=on-failure
OOMPolicy=continue
[Install]
WantedBy=multi-user.target
EOF
sudo tee /usr/local/sbin/ss14 >/dev/null <<'EOF' &&
c=$*; if [ "$c" = ready ]; then for i in $(seq 60); do curl -fso /dev/null 127.0.0.1:1212/info && echo READY && exit; sleep 5; done; echo STOP; exit 1; fi
[ "$c" = versions ] && m=GET || m=POST
curl -fsS -X $m --netrc-file /root/ss14-api.netrc "127.0.0.1:5000/instances/station/$c"; echo
EOF
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw && sudo ufw allow OpenSSH && sudo ufw --force enable && sudo ufw allow 1212/udp && sudo ufw allow 1212/tcp &&
sudo systemctl daemon-reload && echo "BUILD OK" || echo "STOP - see above"
fi
else echo "STOP - apt did not finish"; fi
Our departures from the docs are these. The publish names the project and writes to /home/ss14/watchdog with -o rather than copying the output. The unit is named ss14-watchdog, where the docs use SS14.Watchdog, so use our name with systemctl. It runs as User=ss14 instead of root, waits for network-online.target, installs under multi-user.target, has no Description, and drops DOTNET_CLI_HOME=/tmp, kept by the docs for Git updates. If the block stops after the clone on a fresh machine, run sudo rm -rf /home/ss14/SS14.Watchdog /home/ss14/watchdog and paste it again.
Block 2, instance and first start
Only if block 1 printed BUILD OK, set NAME (the launcher name) and ADMIN (your SS14 account) and run block 2; it refuses while either reads CHANGE_ME. It writes a random API token to /root/ss14-api.netrc at mode 600, keeping it off command lines that ps or shell history would show; writes config.toml with the name, the lobby, auth.mode = 1 (accounts required) and console.login_host_user; then sets appsettings.yml to mode 600 and appends the station instance with Manifest updates and BaseUrl and Urls on 127.0.0.1:5000, so the API never binds a public address.
W=/home/ss14/watchdog; NAME="CHANGE_ME"; ADMIN="CHANGE_ME"
if sudo test -x $W/SS14.Watchdog && [ "$NAME" != CHANGE_ME ] && [ "$ADMIN" != CHANGE_ME ] && ! sudo grep -q '^Servers:' $W/appsettings.yml && T=$(openssl rand -hex 24) && [ -n "$T" ]; then
sudo install -m 600 /dev/null /root/ss14-api.netrc && echo "machine 127.0.0.1 login station password $T" | sudo tee /root/ss14-api.netrc >/dev/null &&
sudo -u ss14 mkdir -p $W/instances/station && printf '[game]
hostname = "%s"
lobbyenabled = true
[auth]
mode = 1
[console]
login_host_user = "%s"
' "$NAME" "$ADMIN" | sudo -u ss14 tee $W/instances/station/config.toml >/dev/null &&
sudo chmod 600 $W/appsettings.yml && sudo tee -a $W/appsettings.yml >/dev/null <<EOF &&
BaseUrl: "http://127.0.0.1:5000/"
Urls: "http://127.0.0.1:5000"
Servers:
Instances:
station:
Name: "Station"
ApiToken: "$T"
ApiPort: 1212
UpdateType: "Manifest"
Updates:
ManifestUrl: "https://wizards.cdn.spacestation14.com/fork/wizards/manifest"
TimeoutSeconds: 60
EOF
sudo systemctl enable --now ss14-watchdog && echo "CONFIG OK" || echo "STOP - see the first error above"
else echo "STOP - no build, a CHANGE_ME left, or already configured"; fi
The first start downloads the server build before Robust.Server runs (ServerInstance.Actor.cs). sudo bash /usr/local/sbin/ss14 ready polls http://127.0.0.1:1212/info for up to five minutes and prints READY or STOP.
Block 2 refuses a re-run once appsettings.yml has the Servers section. If it stopped after writing it, start the unit with sudo systemctl start ss14-watchdog and read journalctl -u ss14-watchdog. Per the docs, a game server restarting every 30 seconds means it cannot reach the watchdog's BaseUrl.
Host rights, hub listing, database
- Host. A localhost connection gets host automatically, which does not help on a remote box;
console.login_host_usergrants host to that account when it joins. Withauth.mode = 1every client authenticates, so in our reading no one else can claim that name. The docs warn that +HOST hands over the server and the computer, so keep it and grant admins narrower flags with the in-gamepermissionscommand. - Hub. Listing is off by default, and turning it on binds you to the hub rules. Put
[hub],advertise = trueandtagsfrom the standard set in the instance'sconfig.tomland apply it withsudo bash /usr/local/sbin/ss14 restart, which is immediate and drops players (ServerInstance.Actor.cs). "Unable to contact status address" in the log means TCP 1212 is unreachable from outside. - Tuning. The hosting guide's tweaks are YAML entries under
EnvironmentVariablesin the station's entry ofappsettings.yml(edit it with sudo), with quoted string values, settingDOTNET_TieredPGOto "1",DOTNET_TC_QuickJitForLoopsto "1",DOTNET_ReadyToRunto "0", andROBUST_NUMERICS_AVXto "true", which can hurt on some processors. They apply aftersudo systemctl restart ss14-watchdog, which drops players, so restart on an empty server. - Database. SQLite is the default and, per the docs, sufficient for small servers. PostgreSQL mainly matters if several game servers share a database or the server grows large; MySQL and MariaDB are unsupported. Migrations run automatically at startup.
Updates and rollback
The watchdog checks for a new build when you call update or when the watchdog service restarts; a crash restart of the game does not update it. Back up first and send sudo bash /usr/local/sbin/ss14 update. If the manifest has something newer, ServerUpdateManager.cs restarts the game at round end or once the last player leaves. The watchdog fetches the build as it starts the server again, so the station is offline until that download and start finish; sudo bash /usr/local/sbin/ss14 ready confirms it is back.
To roll back, call sudo bash /usr/local/sbin/ss14 revert (InstanceController.cs, added September 2026, Manifest instances only); it returns to the previous build at the next quiet restart. 'revert?immediate=true' switches now and 'revert?version=NAME' picks a build listed by versions, each given to the helper as one quoted argument. If the newer build migrated the database, restore the backup too. To update the watchdog program itself, back up, wait for an empty server, then run `if sudo test ! -e /home/ss14/watchdog.new && sudo -u ss14 -H bash -c 'cd ~/SS14.Watchdog && git pull --recurse-submodules && dotnet publish SS14.Watchdog -c Release -r linux-x64 --no-self-contained -o ~/watchdog.new && rm ~/watchdog.new/appsettings.yml'; then sudo systemctl stop ss14-watchdog && sudo -u ss14 cp -r /home/ss14/watchdog.new/. /home/ss14/watchdog/ && sudo systemctl start ss14-watchdog && sudo bash /usr/local/sbin/ss14 ready || echo "STOP - may be stopped, check systemctl status ss14-watchdog"; else echo "STOP - not built, old watchdog untouched"; fi. It builds while the old watchdog runs, deletes the build's default appsettings.yml, and only then stops the service (a running binary cannot be overwritten), copies the build over as ss14, keeping instances and your settings, and starts it. "not built" means nothing changed; "may be stopped" means start the service and, if the copy broke it, restore the backup. The stop takes the game down too unless the docs' PersistServers and KillMode=process are set. Remove the leftover afterwards (sudo rm -rf /home/ss14/watchdog.new`), or the next run refuses.
Backups and restore
Everything lives in /home/ss14/watchdog. The token is in the watchdog's appsettings.yml, the settings in the instance's config.toml, player data in the instance's data/preferences.db. Block 3 writes a backup script that copies the live database with sqlite3 .backup, runs integrity_check on the copy, archives the folder with that copy and without the live bin/logs (accepting tar's exit code 1 for a file changing mid-read), confirms the copy is inside, and only then prunes archives older than 14 days. It also writes a restore script and a 04:17 cron entry that overwrites backup.log each night.
sudo install -d -m 700 /var/backups/ss14 && sudo tee /usr/local/sbin/ss14-backup.sh >/dev/null <<'EOF' &&
set -o pipefail; umask 077; H=/home/ss14; B=/var/backups/ss14; S=$(date +%F-%H%M%S); P=prefs-$S.db
sqlite3 $H/watchdog/instances/station/data/preferences.db ".backup '$B/$P'" &&
[ "$(sqlite3 $B/$P 'PRAGMA integrity_check;')" = ok ] &&
{ tar -czf $B/ss14-$S.tar.gz --warning=no-file-changed --exclude='*/data/preferences.db*' --exclude='*/bin/logs' -C $H watchdog -C $B $P; [ $? -le 1 ]; } &&
tar -tzf $B/ss14-$S.tar.gz | grep -cx $P >/dev/null && rm -f $B/$P &&
find $B -name 'ss14-*.tar.gz' -mtime +14 -delete && echo "backup OK $S" || { echo "backup FAILED, see above"; exit 1; }
EOF
sudo tee /usr/local/sbin/ss14-restore.sh >/dev/null <<'EOF' &&
A=$*; H=/home/ss14; T=$(mktemp -d -p $H) || exit 1
[ ! -e $H/watchdog.previous ] && tar -tzf "$A" | grep -q '^watchdog/appsettings.yml$' && tar -xzf "$A" -C $T && ls $T/prefs-*.db >/dev/null &&
echo "ARCHIVE OK" && systemctl stop ss14-watchdog && mv $H/watchdog $H/watchdog.previous &&
mv $T/watchdog $H/ && mv $T/prefs-*.db $H/watchdog/instances/station/data/preferences.db && chown -R ss14: $H/watchdog &&
systemctl start ss14-watchdog && bash /usr/local/sbin/ss14 ready || echo "STOP - roll back if ARCHIVE OK showed"
rm -rf $T
EOF
echo '17 4 * * * root umask 077; /bin/bash /usr/local/sbin/ss14-backup.sh > /var/backups/ss14/backup.log 2>&1' | sudo tee /etc/cron.d/ss14-backup >/dev/null && echo "SCRIPTS OK"
- Only if block 3 printed SCRIPTS OK, test it with
sudo bash /usr/local/sbin/ss14-backup.sh, which must end with backup OK. Copy archives off the machine yourself. - Restore with
sudo bash /usr/local/sbin/ss14-restore.sh /var/backups/ss14/ss14-STAMP.tar.gz. It refuses ifwatchdog.previousexists, extracts and checks the archive before stopping anything (ARCHIVE OK), moves the live folder towatchdog.previous, puts the archive in place with the database copy aspreferences.db(no stale-walor-shmcomes along), fixes ownership, starts and waits for READY. - If ARCHIVE OK showed and READY did not, or the data is wrong, this rolls back. `sudo bash -c 'H=/home/ss14; [ -e $H/watchdog.previous ] && [ ! -e $H/watchdog.failed ] && systemctl stop ss14-watchdog && { [ ! -e $H/watchdog ] || mv $H/watchdog $H/watchdog.failed; } && mv $H/watchdog.previous $H/watchdog && systemctl start ss14-watchdog && echo "ROLLED BACK" || echo "STOP - no watchdog.previous, or watchdog.failed exists"'`
- When the restore is good, delete
watchdog.previous(and anywatchdog.failed), or the next run refuses.
On a new machine, run block 1 (it brings the same ufw rules), then block 3, then the restore. After READY, enable the unit and rebuild the mode-600 token file from the restored appsettings.yml with `sudo systemctl enable ss14-watchdog && sudo install -m 600 /dev/null /root/ss14-api.netrc && sudo sed -n 's/^ *ApiToken. "\(.*\)"$/machine 127.0.0.1 login station password \1/p' /home/ss14/watchdog/appsettings.yml | sudo tee /root/ss14-api.netrc >/dev/null && sudo grep -q password /root/ss14-api.netrc && echo "NETRC OK"`.
Licence and limits
Our general reading, not legal advice. The game's content code and SS14.Watchdog are MIT, so copies passed on keep the copyright notice and licence text, and no source is owed. Most assets are CC-BY-SA 3.0 (attribution, and the same licence on adapted assets you distribute); the README says some are non-commercial, such as CC-BY-NC-SA 3.0, and must go for commercial use. The guide allows changes to a packaged build only through its config file, so custom content means a fork and a build of your own.
Questions
Which ports does an SS14 server need open?
UDP 1212 for the game and TCP 1212 for the status API the launcher and hub query. Keep the watchdog API on 127.0.0.1.
Is Robust.Server on its own enough?
For a test, yes. The hosting guide notes a bare server neither restarts after a crash nor updates; the watchdog adds both.
Can one machine host two stations?
Yes. Add a second instance with its own ApiPort, set the same number as net.port in its config.toml (the docs say the watchdog does not sync them), and open that port for UDP and TCP.