WzServer Setup health check → /api/ping

(EXPERIMENTAL DOCUMENTATION - UNTESTED)

Written with AI assistance — the information here may not be correct. As a server admin, you use this guide at your own risk. Full disclaimer shown on first visit.

We are updating our documentation, database and gateway for the next release — 2026-08-26

We apologize for mistakes during this migration. Everything you need to run a server today is under PRODUCTION — that is mod 1.0.16, the version on the Workshop right now, with gateway 0.7.1. The BETA tab documents the next release while it is still being tested. Do not run BETA on a live server.

Mod 1.0.16 · gateway 0.7.1 — the live Workshop release. Use this one. Next release · gateway 0.9.0 — in testing. Not on the Workshop yet.
You are reading the BETA guide. It documents an unreleased mod build and gateway 0.9.0. Running it against mod 1.0.16 — the version the Workshop gives you today — silently stops saving gear. Switch to Production unless you are deliberately testing the next release.
Wasteland-Z · Dedicated Server

Set up your server, step by step.

Everything, in the order you run it — install the Arma Reforger server, make the database, start the gateway, and wire them together with simple start/stop scripts. No prior server experience needed. Pick your operating system below and follow the numbered steps.

The game, the mod, and this kit

Arma Reforger is Bohemia Interactive’s military simulation game (PC and Xbox), built on the Enfusion engine — the platform that succeeds Arma 3. Anyone can host a dedicated server, and mods install themselves: when a player joins a modded server, the game downloads its mods automatically from the built-in Workshop.

Wasteland-Z is a hardcore PVP open-world mod for Arma Reforger, by Heavy Forge, Inc. The world after the Z — the outbreak ended, the cities emptied, and what survived now scavenges, fights, and trades for what’s left. Town-based scarcity loot, missions, gun / general / vehicle stores, a server-authoritative money economy with ATM banking, three-faction PVP, and a cross-server hive: the same money, bank and gear on every server in a cluster. It runs on top of any Game Master scenario, on any map. The gameplay concept honors the A3 Wasteland lineage, rebuilt ground-up for Reforger.

This kit is the server side — for admins who want to host a Wasteland-Z server: the guide below, the gateway program, the database schema, example configs, and start/stop scripts. Just want to play? You need nothing from here — find a Wasteland-Z server in the Reforger server browser and join. The mod installs itself.

What you're building

A working server is three programs talking to each other. You install all three once, then they run on their own.

Game Server
Arma Reforger + the Wasteland-Z mod (from the Workshop). Where people play.
Gateway
The small program in this download. It carries player data between the game and the database.
MySQL
The database. Stores money, gear, progress, and the world — so nothing is lost on restart.

What's in this download

gateway/
The gateway program. Copy this whole folder to your server and run it.
gateway/setup_database.sql
Every database table, defined in one file. You run it once, by hand, when you first set up the database — step 3 below. It only ever adds, so running it again changes nothing.
gateway/config.example.py
The settings template. You copy it to config.py and fill in your password + key.
gateway/start_gateway.bat
Windows one-click start.
configs/
Reference copies of the server settings (loot, vehicles, towns, admins…) — the server writes its own working copies on first start (see below), so these are for reading and optional pre-tuning, not required drop-ins. The README inside lists each file and which ones auto-create; every setting is documented inline.
missions/
Reward templates for the mission system, plus a guide to authoring missions in-game.
server/
Game-server kit — SteamCMD install script, example server1.json, and the start/stop batch files (auto-restart loop included).
The server builds its own config files — you don’t have to

The first time Wasteland-Z starts, it writes a complete, ready-to-run set of config files into your server profile’s hf_wastelandz\configs\ folder, each pre-filled with the current defaults. You can start with no config files at all and edit the generated ones afterwards — that’s the recommended path, because the files the server writes always match the mod version you’re running (a hand-copied kit file can fall behind after an update).

Auto-created on first start: HFWastelandZ_server.conf, HFWastelandZ_secrets.conf, HFWastelandZ_admins.conf, HFWastelandZ_blacklist.conf, HFWastelandZ_command_tiers.conf, HFWastelandZ_loadouts.conf, HFWastelandZ_town_tiers.conf, HFWastelandZ_loot_spawn_filter.conf, HFWastelandZ_item_catalog.conf, HFWastelandZ_vehicle_spawn.conf, HFWastelandZ_server_message.txt, plus the per-map files under configs\map\<MapName>\ (towns, spawn points, loot pools, world loot, and the gun / general / vehicle store catalogs — these regenerate for whatever map you load).

The two you must fill in yourself: HFWastelandZ_secrets.conf is written with a placeholder API key you must replace, and HFWastelandZ_admins.conf is written empty so you must add your Bohemia Identity GUID (not your Steam number) to become an admin — see Step 9d. Everything else runs on defaults until you decide to change it.

Where everything lives on your server

After setup you have three top-level folders (Linux /opt/ shown below; Windows mirrors this under C:\reforger\ plus your gateway folder). The spot that trips people up: HF’s live config files sit under the profile’s extra profile/ subfolder.

/opt/
├── wastelandz/                # THIS KIT (git clone) — reference copies, read-only
│   ├── gateway/                 # gateway program + setup_database.sql + config.example.py
│   ├── configs/                 # reference *.example HF settings (optional pre-tuning)
│   ├── missions/                # mission reward templates + in-game authoring guide
│   └── server/                  # systemd + server1/2.json examples + SteamCMD script
│
├── wastelandz-gateway/        # the RUNNING gateway (copied out of the kit)
│   ├── config.py                # YOUR DB password + gateway key — never share or commit
│   ├── gateway.py               # the Flask service
│   ├── setup_database.sql       # builds the tables (tables only — holds NO credentials)
│   └── venv/                    # isolated Python you create (Ubuntu 24.04 / PEP 668)
│
└── reforger/                  # the game server
    ├── server/                  # ArmaReforgerServer + game files (from SteamCMD)
    ├── workshop/                # -addonDownloadDir: Workshop mods download here, shared by all servers
    │   └── addons/
    │       └── Wasteland-Z_68A616565DECAB19/   # the downloaded mod (data.pak)
    ├── configs/
    │   └── server1.json         # Reforger config: server NAME, admin password, mods, scenario
    └── profiles/
        └── server1/             # -profile dir for this server
            ├── logs/            # console.log lives here
            └── profile/         # $profile: root  <-- note the extra 'profile/' level
                └── hf_wastelandz/
                    ├── configs/  # *** HF configs: server.conf, secrets.conf, admins.conf, catalogs...
                    └── missions/ # mission reward templates go here

Key takeaway: the HF config files are at /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/ — note the profile/ level Enfusion adds. On Windows it’s the same shape under C:\reforger\profiles\server1\profile\hf_wastelandz\configs\.

Before you begin

You need a PC (or rented machine) that stays on, and this download. That’s it — the guide covers installing the game server itself, and the server downloads from Steam anonymously (no Steam account needed). The Wasteland-Z mod downloads automatically from the Reforger Workshop the first time the server starts.

“This download” = the setup kit’s GitHub repo. To grab it: open the repo page → green Code button → Download ZIP → unzip anywhere on your PC. No git and no GitHub account needed. (Linux users can skip the ZIP — the Linux tab’s Step 1 pulls it straight onto the server.)

Choose your system
Part 1

Install the game server

Three steps: get SteamCMD, download the server with it, drop in a config. The server\ folder of this download has everything ready-made.

Planning 2–3 servers?

Build server 1 first. Part 4 adds each extra server: the game installs once; every additional server is a config file with new ports (+ its own map if you want) and its own start script.

1

Make the folders and get SteamCMD

SteamCMD is Valve’s little downloader — it fetches and updates the dedicated server. Make this folder layout (one parent folder, four inside):

C:\reforger\
  steamcmd\    ← SteamCMD lives here
  server\      ← the game server gets installed here
  configs\     ← your server settings (server1.json)
  profiles\    ← each server's save data + Wasteland-Z configs
  workshop\    ← downloaded mods (shared by all your servers)

Get SteamCMD from Valve’s official page: developer.valvesoftware.com/wiki/SteamCMD — under Windows it links the steamcmd.zip download. Unzip it into C:\reforger\steamcmd\. No Steam account is needed — the dedicated server downloads anonymously.

2

Download the server

Copy the server\ folder from this download into C:\reforger\scripts\, then double-click install_or_update_server.bat. It pulls the Arma Reforger dedicated server (Steam app 1874900, a few GB) into C:\reforger\server\.

If you prefer typing it yourself, the script just runs:

C:\reforger\steamcmd\steamcmd.exe +force_install_dir C:\reforger\server ^
  +login anonymous +app_update 1874900 validate +quit
Game updated?

Whenever Reforger gets an official update, stop your servers and run this same script again. It only downloads what changed.

3

Drop in the server config

Copy server\server1.json.example to C:\reforger\configs\server1.json and open it in Notepad. Set two things:

  • name — your server’s name in the browser list.
  • passwordAdmin — a password only you know.

Everything else already works: the Wasteland-Z mod is pre-listed in mods (it auto-downloads on first start), and the map is Everon (21_GM_Eden). For Arland instead, set scenarioId to {2BBBE828037C6F4B}Missions/22_GM_Arland.conf.

Router / firewall

Players connect over UDP ports 2001 (game) and 17777 (server browser). On a home connection, forward both UDP ports to this PC in your router. On a rented server, allow them in the provider’s firewall panel.

Don’t start it yet — first set up the database and gateway below, so the server has something to save into.


Part 2

Database & gateway (one time)

Do these eight steps in order. Copy each command with the button on its right.

1

Install the two programs you need

Install MySQL 8 (the database) and Python 3.12+ (runs the gateway).

  • MySQL: get the MySQL Installer from dev.mysql.com → install MySQL Server. When it asks, set a root password and write it down.
  • Python: get it from python.org. On the first install screen, tick “Add python.exe to PATH” — this matters.

Check both installed — open Command Prompt and run:

python --version
mysql --version
2

Create the database

Open “MySQL 8.0 Command Line Client” from the Start menu (it asks for the root password from Step 1). Paste these four lines — they make an empty database and a login for the gateway:

CREATE DATABASE wastelandz;
CREATE USER 'wastelandz'@'localhost' IDENTIFIED BY 'your_password_here';
GRANT ALL PRIVILEGES ON wastelandz.* TO 'wastelandz'@'localhost';
FLUSH PRIVILEGES;
Pick a password

Replace your_password_here with a password you choose, and write it down — the gateway needs it in Step 5. Because MySQL 8’s password policy is on, it must be 8+ characters with an upper- and lower-case letter, a number, and a symbol (e.g. Wast3land_Z!) — a weaker one fails with ERROR 1819.

3

Check the login works

Prove the database and password you just made actually work, before anything depends on them. Open a normal Command Prompt, go to the folder where you unzipped this download, and run:

mysql -u wastelandz -p wastelandz < gateway\setup_database.sql

It asks for the wastelandz password (from Step 2). No error message means it worked — and it built the tables while it was there.

You are not required to run this

On this release the gateway does not create tables, so this step is required — it is what builds them. Running it twice is harmless: the file only ever adds.

Running it and letting the gateway run it are the same thing — the file is written so that running it twice changes nothing. There is no first-time-only path to get wrong.

4

Put the gateway in place

Copy the whole gateway folder somewhere permanent — for example C:\wastelandz-gateway. Open Command Prompt in that folder and install the parts it needs:

pip install -r requirements.txt
5

Enter your settings

First generate the gateway key — a unique API key (use a different one for every server). Copy the 64-character line it prints:

python -c "import secrets; print(secrets.token_hex(32))"

In the gateway folder, make a copy of config.example.py and name the copy config.py. Open config.py in Notepad and set two things:

  • DB_PASSWORD = the password you made in Step 2.
  • Set api_key — the GATEWAY KEY, shown in the file as CHANGE_ME_UNIQUE_KEY_1 — to the key you just generated. Each server needs its own unique gateway key.

Leave host as 127.0.0.1 and port as 5000.

Keep it private

config.py holds your password and key. Never share it or upload it anywhere public.

6

Start it and check

Double-click start_gateway.bat (or run python gateway.py). A black window opens and stays open — that’s normal, leave it running. You should see Database connection: OK and a Listening… line.

Now open a browser to:

http://127.0.0.1:5000/api/ping

You should see "status":"ok" and "database":"connected". That means the gateway and database are talking.

7

Firewall

If the gateway and the game server are on the same computer (the normal setup) — do nothing. Local connections already work.

Only if a different computer or a Discord bot needs to reach the gateway, open port 5000 to that one address:

netsh advfirewall firewall add rule name="WZ Gateway" dir=in action=allow ^
  protocol=TCP localport=5000 remoteip=THE.OTHER.IP.ADDRESS
Two hard rules

Never open port 5000 to everyone (no wide-open rule). And leave MySQL’s port 3306 closed — the gateway reaches it locally; the internet never should.

8

Connect the game server — then first start

Start the server once (double-click C:\reforger\scripts\start_server1.bat) and let it boot — it downloads the Wasteland-Z mod and creates its profile folder, then Wasteland-Z writes starter config files into C:\reforger\profiles\server1\profile\hf_wastelandz\configs\. Close the server window, then wire it up:

  • HFWastelandZ_server.confGATEWAY_URL already defaults to http://127.0.0.1:5000/ (correct for a local gateway); only change it if your gateway is on another machine/port.
  • In …\hf_wastelandz\configs\HFWastelandZ_secrets.conf, add a line: API_KEY followed by the same key you put in the gateway’s config.py in Step 5. (A ready-made template with key-generation instructions ships in this download: configs\HFWastelandZ_secrets.conf.example.)
  • Want to pre-tune settings? Take the files in this download’s configs\, remove .example from each name, and copy them into that same profile\hf_wastelandz\configs\ folder. configs\README.md lists what each file controls; every setting is documented inside its file.
  • Copy missions\rewards\ into …\profiles\server1\profile\hf_wastelandz\missions\rewards\ — these are the loot crates and vehicles missions hand out. (Missions themselves are built in-game — see missions/README.md.)

Start the server again. Player data now saves to the database. You’re live.


Part 3

Run · monitor · stop

Day-to-day, with zero server knowledge: every piece runs in its own labeled window. If a window is open, that piece is running. Close a window, that piece stops — nothing else is touched. The batch files live in C:\reforger\scripts\.

Start everything

Double-click start_all.bat. It opens the WZ-Gateway window, waits a few seconds, then opens WZ-Server-1 (and 2/3 if you enabled them).

Crash protection

Each server window runs a loop: if the game server crashes or closes, it restarts by itself in 10 seconds. The gateway just keeps running.

Stop ONE game server

Close its WZ-Server-1 window (the X), or double-click stop_server1.bat. The gateway and any other servers stay up.

Stop the gateway only

Close the WZ-Gateway window. Game servers keep running (players keep playing; saves hold in memory and it catches up when you start the gateway again — don’t leave it off long).

Stop everything

Close all the WZ-… windows. Done. (Stop servers before the gateway so last saves land.)

Watch it (monitor)

The windows are the monitors — gateway saves/loads scroll in WZ-Gateway, the game log scrolls in each server window.

Start on boot

Press Win+R, type shell:startup, Enter — then put a shortcut to start_all.bat in the folder that opens. Everything launches when you log in. (Prefer no auto-login? Use Task Scheduler → At startup instead.)

Game updated?

Stop the server windows, run install_or_update_server.bat, start again. The Wasteland-Z mod updates itself on server start.

Is it alive?

Browse http://127.0.0.1:5000/api/ping anytime. status: ok = healthy.

Restart MySQL

Win+R → type services.msc → find MySQL80 → right-click → Restart.

Back up your data (do this regularly)

mysqldump -u wastelandz -p wastelandz > wastelandz_backup.sql

Keep a copy of that file somewhere safe. It is your whole server’s save.

Troubleshooting

  • “Database connection failed” — the password in config.py doesn’t match Step 2, or MySQL isn’t running.
  • Port 5000 already in use — change port in config.py (and update GATEWAY_URL to match).
  • /api/ping won’t load — the gateway window isn’t running. Start it.
  • Money/gear not savingGATEWAY_URL or api_key don’t match between the game server and config.py.
  • Last location & kept gear don’t restore on relog (but money & inventory DO) — your game server’s SERVER_ID (in HFWastelandZ_server.conf) doesn’t match the server_id the gateway uses for it. Money/inventory are hive-wide so they still work; only per-server data (your position, kept-on-relog gear) needs the two ids to match. Set SERVER_ID to the gateway’s id and restart. The game log prints SERVER_ID MISMATCH with both values when it detects this — grep for it.

Part 4

Two or three servers on one PC

One database, one gateway, several game servers — that’s a hive. Players keep the same money, bank and gear on every server in the hive; each server keeps its own world (positions, placed objects, map). One gateway serves them all — you do not run a second gateway or database.

The finished two-server setup, and how the pieces talk:

players → UDP 2001/17777 → WZ-Server-1 (Everon)  → http://127.0.0.1:5000 + key #1 ─┐
players → UDP 2002/17778 → WZ-Server-2 (Arland)  → http://127.0.0.1:5001 + key #2 ─┤
                                                                              └→ WZ-Gateway  MySQL
The gateway tells the servers apart by the PORT each one calls (5000 vs 5001)
and proves it with that server's key. Both ports are ONE gateway window,
and everything right of the players is local to the PC — nothing exposed.

Every extra server is the same four things with different numbers. The unique-ports table:

Server 1Server 2Server 3
Game port (UDP, forward it)200120022003
Browser port (UDP, forward it)177771777817779
Gateway port (local only)500050015002
Config fileserver1.jsonserver2.jsonserver3.json
Profile folderprofiles\server1profiles\server2profiles\server3

To add server 2 (server 3 is the same pattern):

  • Config: copy this download’s server\server2.json.example to C:\reforger\configs\server2.json — ports 2002/17778 and the Arland map are pre-set; change name and the admin password. The map is the scenarioId line — Wasteland-Z runs on top of any Game Master scenario:
    Everon (the big island):
    "scenarioId": "{59AD59368755F41A}Missions/21_GM_Eden.conf"
    
    Arland (the smaller southern island):
    "scenarioId": "{2BBBE828037C6F4B}Missions/22_GM_Arland.conf"
    e.g. server 1 on Everon, server 2 on Arland — same hive, so players keep their money, bank and gear when they switch.

    Any other map — including community maps — works the same way. Find its ID on the Workshop website: open the map mod’s page at reforger.armaplatform.com/workshop and click its Scenarios tab — the ScenarioId shown there (it looks like {GUID}Missions/Name.conf) is what goes in scenarioId. Add that map mod to the same server’s mods list too.

  • Batch files: copy start_server1.batstart_server2.bat and stop_server1.batstop_server2.bat. Inside both, change every server1 to server2 and the title WZ-Server-1 to WZ-Server-2. (The install folder and workshop\ mods folder are shared — don’t copy those.)
  • Gateway: in config.py, un-comment the server-2 line in SERVERS and give it its own random api_key (Step 5 command). Restart the gateway window — it now listens on 5000 and 5001.
  • Wire it: in profiles\server2\profile\hf_wastelandz\configs\ (created on server 2’s first start), set GATEWAY_URL http://127.0.0.1:5001/ and put server 2’s key in its HFWastelandZ_secrets.conf.
  • Match the SERVER_ID (critical): in server 2’s HFWastelandZ_server.conf, set SERVER_ID server-2 so it exactly matches that server’s server_id in the gateway’s SERVERS list. If they differ, last-location and kept-gear will silently not restore on that server (money/inventory still work, since those are hive-wide). The game log prints SERVER_ID MISMATCH if it’s wrong.
  • Start it: remove the rem in front of the server-2 lines in start_all.bat. Forward UDP 2002 + 17778 in your router.
One key per server

Each server gets its own gateway port and its own random api_key — that’s how the gateway tells them apart. Never reuse a key across servers.

Will my PC handle it?

Each Reforger server is heavy — roughly a few CPU cores and 4–8 GB RAM per server under load. Two servers want a strong 8-core machine; three want more. The gateway and MySQL are lightweight by comparison.

Big box? Give each server its own cores

RAM needs no dividing — Windows hands it out on demand, and a full 128-player server uses roughly 8–16 GB. (Windows has no simple per-program RAM cap like Linux’s — skip that part.) CPU is the part worth splitting: a big AMD chip (e.g. a 16-core Ryzen 7950X3D) is really two groups of 8 cores, each sharing one pot of very fast memory (cache). Two servers squeezed into one group fight over the pot — give each server its own group.

On Windows the tool is the /affinity flag. In start_server1.bat, change the server line:

rem before:
"%SERVER_EXE%" -config "%CONFIG%" -profile "%PROFILE%" -addonDownloadDir "%ADDONS%" -maxFPS 60

rem after — server 1 pinned to the first 16 processor numbers (group one):
start "" /affinity FFFF /wait "%SERVER_EXE%" -config "%CONFIG%" -profile "%PROFILE%" -addonDownloadDir "%ADDONS%" -maxFPS 60

rem and in start_server2.bat — the next 16 (group two):
start "" /affinity FFFF0000 /wait "%SERVER_EXE%" -config "%CONFIG%" -profile "%PROFILE%" -addonDownloadDir "%ADDONS%" -maxFPS 60

The mask is a code for “which processors”. FFFF = the first 16 processor numbers; Windows usually numbers each core twice back-to-back, so that’s the first 8 cores = the first group (the big-cache one on X3D chips — give it to your busiest server). /wait keeps the auto-restart loop working. Sanity-check anytime: Task Manager → Details → right-click ArmaReforgerServer.exeSet affinity shows exactly which processors each server is allowed on.

Cheat-sheet: 1–4 servers on a 16-core PC (two 8-core groups; Windows numbers each core twice back-to-back):

SetupServer & players/affinity mask
1 server (up to 128)WZ-Server-1 · 128none needed (whole chip; or FFFF for the big-cache group)
2 servers (2×128)WZ-Server-1 · 128FFFF
WZ-Server-2 · 128FFFF0000
3 servers (2×128 + 1×64)WZ-Server-1 · 128FFFF
WZ-Server-2 · 128FFFF0000
WZ-Server-3 · 64none (floats in the gaps)
4 servers (4×64 or 4×32)WZ-Server-1 · 64FF
WZ-Server-2 · 64FF00
WZ-Server-3 · 64FF0000
WZ-Server-4 · 64FF000000

The logic: 1–2 servers → a whole group each (max cache per server). 4 servers → half a group (4 cores) each — plenty at 32–64 players, and no server ever spans both groups. 3 servers is the awkward count: pin the two big ones, let the third float. Player caps live in each serverN.json (maxPlayers); what actually loads a core is AI + player count together, so watch Task Manager on busy nights and adjust aiLimit before touching the masks.

Different CPU? Older Intels (e.g. an i9-9900K, 8 cores) are one group — no favourite half; split down the middle with FF / FF00 and expect lower comfortable player caps. Newer hybrid Intels (12th gen up, e.g. an i7-13700K) mix fast P-cores (numbered first, each twice back-to-back) and small E-cores (after them): pin game servers to P-cores (FF / FF00) and leave the E-cores unpinned — they carry the gateway, MySQL and Windows itself. See your layout anytime: Task Manager → Details → right-click the server → Set affinity lists every processor number.

Part 1

Set it up (one time)

These are Ubuntu commands. Type them into the server’s terminal (connect with SSH). Every command is explained.

Planning 2–3 servers?

Build server 1 first. Part 3 adds each extra server: the game installs once; every additional server is a config file with new ports (+ its own map if you want) and its own service. The kit ships the server-2 files pre-made.

New to Linux? The 60-second picture

Your Windows PC is the remote control; the Linux box is the machine that runs everything. You type commands into it over SSH — open PowerShell on your PC and run ssh youruser@your-server-ip (SSH is built into Windows, nothing to install). Every command below is typed into that window.

Linux has no C:\. Your personal folder is /home/youruser — written ~ for short, so ~/reforger would mean /home/youruser/reforger. Software you install yourself goes in /opt/ by convention — that’s this guide’s equivalent of making C:\reforger\, and everything lives there:

/opt/wastelandz            ← this setup kit, straight from GitHub (Step 1)
/opt/reforger              ← the game server (Step 2)
/opt/wastelandz-gateway    ← the running gateway (Step 5)
0

Accounts — you and your friends

Your rented box hands you the root login — the master account. Setting up from it is fine. First job: make an admin account for yourself and each friend who’ll help run the server — an admin can do everything in this guide (restart servers, edit configs, run game updates) without needing you.

See your admins:

getent group sudo     # the admins — every name after the last ':' is one

Add an admin (yourself, or a friend who’ll help run the server — two commands, that’s it):

sudo adduser dave           # pick any name; it asks you to set a password
sudo usermod -aG sudo dave  # make him an admin

Dave logs in with ssh dave@your-server-ip and the password you set (he can change it with passwd). He must log out and back in once for admin power to kick in — then he can do everything in this guide: restart servers, edit configs, run updates. Only add people you trust with the whole box.

Remove an admin:

sudo deluser dave sudo              # drops admin power, keeps his login
sudo deluser --remove-home dave     # deletes the account completely

Do the steps below from root or from your own admin account — the commands work either way. One thing to remember for later: the service files in Part 2 and Step 9 have a User= line — put your account name there, so the game and gateway run under your account.

1

Get the files onto the server

Everything in this kit is one GitHub repo. Two ways to get it onto the Linux box — pick one:

Option A — git (recommended). One command pulls the whole kit straight onto the server; updating later is one more command. On the server:

sudo apt install -y git
sudo git clone https://github.com/Motavar/HF_WastelandZ_SETUP.git /opt/wastelandz
sudo chown -R $USER /opt/wastelandz

Get updates later with: cd /opt/wastelandz then git pull

Option B — WinSCP (drag & drop). On your PC: open the repo page on GitHub → green Code button → Download ZIP → unzip it. Make the target folder on the server first:

sudo mkdir -p /opt/wastelandz
sudo chown $USER /opt/wastelandz

Then connect WinSCP (a free tool) to the server and drag the unzipped folder’s contents into /opt/wastelandz — it works like copying between two Explorer windows.

2

Install the game server

SteamCMD is Valve’s little downloader — it fetches and updates the dedicated server. On Ubuntu it lives in the “multiverse” package section, so enable that first:

sudo add-apt-repository multiverse
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y steamcmd

A license screen appears during install — accept it. No Steam account is needed; dedicated servers download anonymously.

Not on Ubuntu? Debian, Arch and other distros install SteamCMD differently — see Valve’s official page for yours: developer.valvesoftware.com/wiki/SteamCMD. The rest of this guide works the same once steamcmd is installed.

Make the game server’s folder layout (same idea as C:\reforger\ on Windows), then download the server into it — a few GB:

sudo mkdir -p /opt/reforger/server /opt/reforger/configs /opt/reforger/profiles /opt/reforger/workshop
sudo chown -R $USER /opt/reforger

steamcmd +force_install_dir /opt/reforger/server +login anonymous +app_update 1874900 validate +quit
Game updated?

Updated for 1.8 — 2026-08-17: you no longer have to do this by hand. The service file from Part 2 runs SteamCMD before every start, so the server brings itself up to date each time it restarts. That matters because a server on an older build than its players is unjoinable — clients auto-update through Steam whether you do or not. Keep the command above for the first install, and for repairs (the validate flag re-checks every file — useful when something is corrupt, too slow to run on every restart).

Drop in the ready-made config from the kit (Wasteland-Z is pre-listed in mods — it works as-is; open it in nano later to rename your server — save/exit with Ctrl+O, Enter, Ctrl+X):

cp /opt/wastelandz/server/server1.json.example /opt/reforger/configs/server1.json

Don’t start it yet — it needs the database and gateway first (Steps 3–8). Step 9 does the first start.

3

Install MySQL and Python

Update the package list, then install both:

sudo apt update
sudo apt install -y mysql-server python3 python3-pip

Then lock MySQL down and set the root password:

sudo mysql_secure_installation

It asks a series of questions. Recommended answers:

  • VALIDATE PASSWORD component? — optional. N is fine (you set your own strong passwords).
  • New root password — set a strong one and write it down.
  • Remove anonymous users?Y — closes password-less access.
  • Disallow root login remotely?Y — root only connects locally; the gateway uses its own wastelandz login.
  • Remove test database and access to it?Y — the default test database is open to everyone.
  • Reload privilege tables now?Y — applies it all right away.
Safe to say yes

None of these touch the wastelandz database or login you create in the next step — they only strip out MySQL’s insecure defaults. Answering Y to all four is the right call for a dedicated server.

4

Create the database

Open MySQL as admin:

sudo mysql

At the mysql> prompt, paste these four lines, then type exit:

CREATE DATABASE wastelandz;
CREATE USER 'wastelandz'@'localhost' IDENTIFIED BY 'your_password_here';
GRANT ALL PRIVILEGES ON wastelandz.* TO 'wastelandz'@'localhost';
FLUSH PRIVILEGES;
Pick a password

Replace your_password_here with one you choose and write it down — you need it in Step 6. Because you enabled MySQL’s password policy in Step 3, it must be 8+ characters with an upper- and lower-case letter, a number, and a symbol (e.g. Wast3land_Z!) — a weaker one fails with ERROR 1819.

5

Set up the gateway

Copy the gateway out of the kit into its own folder (the running copy stays separate, so kit updates never touch your live settings), then build the database tables and install its Python parts:

# the running copy gets its own folder
sudo mkdir -p /opt/wastelandz-gateway
sudo chown $USER /opt/wastelandz-gateway
cp -r /opt/wastelandz/gateway/. /opt/wastelandz-gateway/

# build the tables and check the login works in one go
cd /opt/wastelandz-gateway
mysql -u wastelandz -p wastelandz < setup_database.sql

# create an isolated Python environment and install the gateway into it
sudo apt install -y python3-venv
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# give the whole folder (incl. the venv) to the account the service will run as
sudo chown -R youruser:youruser /opt/wastelandz-gateway

The mysql line asks for the wastelandz password you set in Step 4, then builds the tables — no output means it worked. The script itself holds no usernames or passwords; you never edit it.

This mysql line is required — don’t skip it

On this release the gateway does not create tables. If you skip this step there is no database for it to talk to, and it will start and then fail every request.

Running it twice is harmless — the file only ever adds, so a second run changes nothing.

Ownership — replace youruser

Change both youruser parts to your Linux username and group (it’s username:group). This must be the same user you’ll put in the service’s User= line in Part 2.

Not sure what yours are? Run these two:

whoami        # prints your username       (e.g. motavar)
id -gn        # prints your primary group  (e.g. motavar)

On Ubuntu/Debian your primary group is the same as your username, so the two are almost always identical. For a user named motavar the command is:

sudo chown -R motavar:motavar /opt/wastelandz-gateway

Shortcut: you can type $USER:$USER instead and the shell fills in your username automatically — the same $USER the earlier chown steps use.

Why it matters: if you created the venv while in a root shell (e.g. after a sudo -i or a sudo mysql session), the files end up owned by root, and the gateway service — which runs as your user — can’t read or run them. The chown -R above fixes that no matter who created the files, so it’s safe to always run.

Why the virtual environment (venv)?

Modern Ubuntu (24.04 / Python 3.12) blocks system-wide pip install to protect the OS Python — you’d hit error: externally-managed-environment. A venv is an isolated Python just for the gateway: the clean, recommended way. Your prompt shows (venv) once it’s active, and that’s why Step 7 and the service below run venv/bin/python.

No-venv option (older systems)

On systems without that restriction (older Ubuntu/Debian), you can skip the venv and install into system Python — then Step 7 and the service use plain python3 instead of venv/bin/python:

pip3 install -r requirements.txt
# to force it on a PEP-668 system instead of using a venv (not recommended):
pip3 install -r requirements.txt --break-system-packages
6

Enter your settings

First generate the gateway key — a unique API key (use a different one for every server). Copy the 64-character line it prints:

python3 -c "import secrets; print(secrets.token_hex(32))"

Now copy the template to the real settings file and open it in nano:

cp config.example.py config.py
nano config.py

Set DB_PASSWORD to your Step 4 password, and set api_key — the GATEWAY KEY, shown in the file as CHANGE_ME_UNIQUE_KEY_1 — to the key you just generated. Each server needs its own unique gateway key. Leave host as 127.0.0.1 and port as 5000. Save in nano: Ctrl+O, Enter, Ctrl+X.

Keep it private

config.py holds your password and key. Never share it or upload it anywhere public.

7

Test that it runs

venv/bin/python gateway.py

This runs the gateway in the foreground, so it holds this terminal — that’s normal (it’s a server, meant to keep running). Look for Database connection: OK in its output — that line is your success check. Then press Ctrl+C to stop it; Part 2 below runs it properly on its own. (venv/bin/python works whether or not the venv is “activated”; a no-venv install uses plain python3 gateway.py.)

Optional — curl check (needs a second terminal)

Because the gateway holds this terminal, the curl check has to run from a separate SSH session while the gateway is still running. Or just skip it — once it’s a service (Part 2) it runs in the background and curl works from any terminal:

curl http://127.0.0.1:5000/api/ping
8

Firewall

Gateway on the same box as the game server (normal) — nothing to do. Local traffic is already allowed.

Only for a remote server or bot, open port 5000 to that one address:

sudo ufw allow from THE.OTHER.IP.ADDRESS to any port 5000 proto tcp
Two hard rules

Never run ufw allow 5000 on its own — that opens it to the whole internet. And leave MySQL’s port 3306 closed; it only needs to be reached locally.

If you turned ufw on, also open the game’s own ports so players can join: sudo ufw allow 2001/udp and sudo ufw allow 17777/udp. (Ubuntu ships with ufw off — then there’s nothing to do.)

Ports at a glance (per server — all UDP)
  • Open to the internet: 2001 (game — players join) & 17777 (A2S / Steam query — this is the Steam port; it’s what lists your server in the browser). Server 2 = 2002/17778, etc.
  • Optional: 19999 (RCON remote admin) — only if you use it, and restrict it to your admin IP; never open it wide.
  • Keep local, never public: 5000 (gateway — open only to a remote bot’s IP if off-box) & 3306 (MySQL).

No separate “Steam” ports are needed — Reforger isn’t a Source-engine server (no 27015 etc.), and SteamCMD downloads are outbound (nothing to forward).

9

Connect your game server — then first start

Start the game server once and let it boot — it downloads the Wasteland-Z mod from the Workshop and writes its starter config files. This is the command (one long line — same program, its config, its profile folder, the shared mods folder):

cd /opt/reforger/server
./ArmaReforgerServer -config /opt/reforger/configs/server1.json -profile /opt/reforger/profiles/server1 -addonDownloadDir /opt/reforger/workshop -maxFPS 60

When it settles, press Ctrl+C to stop it. Wasteland-Z has now written its starter config files into /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/.

You’re running on defaults

Wasteland-Z ships with working defaults for everything — loot, economy, missions, weapons, vehicles. To go live you only need to set two things: the gateway URL and its key (steps a & b below). Everything else is optional tuning you can do later.

a Gateway URL — already set for a local gateway

The mod defaults GATEWAY_URL to http://127.0.0.1:5000/, which is correct when the gateway is on the same box (the normal setup) — so you can skip this. Only edit it if your gateway runs on a different machine or port:

nano /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_server.conf

Set the GATEWAY_URL line to your gateway’s address (keep the trailing slash), then save: Ctrl+O, Enter, Ctrl+X.

b Give it the gateway key

Open the secrets file:

nano /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_secrets.conf

Add a line — API_KEY then a space then the same gateway key from Step 6 (the one in config.py for this server). Save and exit the same way. A ready-made template ships at /opt/wastelandz/configs/HFWastelandZ_secrets.conf.example.

c Add the mission reward templates
cp -r /opt/wastelandz/missions/rewards /opt/reforger/profiles/server1/profile/hf_wastelandz/missions/

These are the loot crates and vehicles missions hand out. (Missions themselves are built in-game — see missions/README.md.)

Now start it again (same command). Player data saves to the database — you’re live.

+ Two optional asides (not required to go live)
Server name & admin password

Those aren’t HF settings — they live in the Reforger config server1.json (the name and passwordAdmin fields). Change them any time:

nano /opt/reforger/configs/server1.json
Optional — tune loot / economy later

To customize loot/economy/etc., rename the kit’s /opt/wastelandz/configs/*.example files (drop .example) and copy them into the profile’s hf_wastelandz/configs/ folder. configs/README.md documents each one — none of it is needed to go live.

d Make yourself the admin

Do this once it’s running. Wasteland-Z admins are keyed by Bohemia Identity GUID — a lowercase xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx code tied to your Bohemia account. It is NOT your Steam number. There are two ways to find yours:

1) In the game (easiest): launch Arma Reforger, click your profile in the upper-right corner — your Identity GUID is listed there. Copy it.

2) From the server log: join the running server once, then read it out of the log:

grep -rh "Authenticated player" /opt/reforger/profiles/server1/logs/ | tail

Copy the identityId= value on the line with your player name. (After Part 2 makes it a service, you can use sudo journalctl -u wz-server1 | grep "Authenticated player" instead.)

Add yourself as OWNER in the admin file:

nano /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_admins.conf
OWNER a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d   # ← your GUID from the log, not this placeholder
⚠ No trailing spaces or Windows line-endings in this file

Wasteland-Z matches admins by exact ID text. A stray trailing space after the GUID, or Windows line-endings (from editing or pasting on Windows), are invisible in most editors but make the ID silently fail to match — you stay a regular player, with no error shown. After saving, strip any hidden characters and verify:

cd /opt/reforger/profiles/server1/profile/hf_wastelandz/configs
sed -i 's/[[:space:]]*$//' HFWastelandZ_admins.conf
cat -A HFWastelandZ_admins.conf

In the cat -A output your OWNER line must end in just $no ^M and no space before it. Why: RESOLVED July 5, 2026 — the mod’s config parser now strips trailing whitespace and Windows line-endings (^M) at the read boundary, so your ID matches even with hidden characters. This sed line is kept as belt-and-suspenders (and is still needed on older mod builds) — it never hurts to run. Restart the server after editing — then F8 opens the admin menu and /zeus gives you Game Master.

Next: make it run on its own

Right now you’re starting the game server by hand to confirm it works. Part 2 → Step B turns it into a background service (wz-server1) that starts on boot and restarts itself if it crashes — do that once you’ve seen it boot and connect to the gateway.


Part 2

Run · monitor · restart

Run the gateway as a service so it starts on its own and survives reboots. Built-in Linux tools only.

A

Make it a service (once)

Create the service file:

sudo nano /etc/systemd/system/wz-gateway.service

Paste this in (change youruser to your Linux username), then save and exit — in nano: Ctrl+O, Enter, then Ctrl+X:

[Unit]
Description=WastelandZ Gateway
After=network.target mysql.service

[Service]
WorkingDirectory=/opt/wastelandz-gateway
ExecStart=/opt/wastelandz-gateway/venv/bin/python /opt/wastelandz-gateway/gateway.py
Restart=on-failure
User=youruser

[Install]
WantedBy=multi-user.target

That ExecStart path is the venv — pointing systemd straight at venv/bin/python runs the gateway inside the venv automatically; you do not activate it for a service. (A no-venv install uses /usr/bin/python3 instead.) Set User= to your Linux username, and make sure that user owns /opt/wastelandz-gateway (the chown -R from Step 5). Then turn it on — this starts it now and on every reboot:

sudo systemctl daemon-reload
sudo systemctl enable --now wz-gateway

Don’t want to type it? The kit ships this file ready-made: copy /opt/wastelandz/server/wz-gateway.service.example to /etc/systemd/system/wz-gateway.service and just change youruser.

B

Make the game server a service

Same idea as Step A, but for the game server. (You did its one-time first boot back in Part 1, Step 9 — that generated its config files; this step makes it run permanently in the background.) First make sure your user owns what the server writes to (its profiles/ and logs/) — needed if you ran any of the install as root:

sudo chown -R youruser:youruser /opt/reforger

Then drop in the ready-made service file and set your username:

sudo cp /opt/wastelandz/server/wz-server1.service.example /etc/systemd/system/wz-server1.service
sudo nano /etc/systemd/system/wz-server1.service   # change User=youruser to YOUR username (save: Ctrl+O, Enter, Ctrl+X)

Now check it before you start it. These two commands catch the two mistakes that account for nearly every “my service won’t start” report (updated for 1.8 — 2026-08-17):

sudo systemd-analyze verify /etc/systemd/system/wz-server1.service   # must print NOTHING
grep -n "User" /etc/systemd/system/wz-server1.service              # must NOT still say youruser

If verify prints Missing '=', ignoring line a long line got split when you pasted — almost always ExecStart, which must be one unbroken line. Your editor wrapping it on screen is fine; a real newline in the middle is not. This one is nasty because the server still starts — systemd keeps the first half of the line and silently discards the rest, so you lose -maxFPS and -addonDownloadDir without any error.

If it still says youruser the server will fail with status=217/USER. verify does not catch this — the syntax is valid, the user just doesn’t exist.

Both clean? Start it:

sudo systemctl daemon-reload
sudo systemctl enable --now wz-server1
sudo systemctl status wz-server1                   # look for "active (running)"

The game server now starts on boot, updates the game before every start, and restarts itself if it crashes.

Any time you edit a unit file afterwards: run daemon-reload before restarting. Without it systemd re-runs the version it already had cached and your edit appears to have done nothing. And if a service has failed repeatedly, systemd locks it out with start-limit-hit — fix the cause, then clear the lockout with sudo systemctl reset-failed wz-server1 before starting again.

Your three services: wz-server1 (the game server), wz-gateway (the gateway), and mysql (the database). Every command below works on any of them — just swap the name. After a reboot they start on their own; if you ever start them by hand, the order is mysqlwz-gatewaywz-server1.

Is it running?

sudo systemctl status wz-server1

Green active (running) = good. It opens a scrollable view — press q to exit back to the prompt.

Restart / stop / start

sudo systemctl restart wz-gateway
sudo systemctl stop wz-gateway
sudo systemctl start wz-gateway

Watch live logs (monitor)

journalctl -u wz-gateway -f

Ctrl+C stops watching (the service keeps running).

Is it alive?

curl http://127.0.0.1:5000/api/ping

Restart MySQL

sudo systemctl restart mysql

Start on boot (on / off)

sudo systemctl enable wz-server1
sudo systemctl disable wz-server1

Your earlier enable --now already turned both on — they auto-start after a reboot.

Back up your data (do this regularly)

mysqldump -u wastelandz -p wastelandz > wastelandz_backup_$(date +%F).sql

That file is your whole server’s save — keep copies somewhere safe.


Part 3

Two or three servers on one box

One database, one gateway, several game servers — a hive. Players keep the same money, bank and gear on every server; each server keeps its own world (positions, placed objects, map). The Windows tab’s “Part 4” ports table applies here too: game 2001/2002/2003, browser 17777/17778/17779, gateway 5000/5001/5002.

The finished two-server setup, and how the pieces talk:

players → UDP 2001/17777 → wz-server1 (Everon)  → http://127.0.0.1:5000 + key #1 ─┐
players → UDP 2002/17778 → wz-server2 (Arland)  → http://127.0.0.1:5001 + key #2 ─┤
                                                                              └→ wz-gateway  mysql
The gateway tells the servers apart by the PORT each one calls (5000 vs 5001)
and proves it with that server's key. Both ports are ONE gateway process,
and everything right of the players is local to the box — nothing exposed.

You install the game once. /opt/reforger/server/ and the shared workshop/ mods folder serve every server. Each extra server is just a config file + profile folder + systemd unit with different numbers. To add server 2:

# its own config — new ports, its own map, its own name.
# The kit ships one pre-made (ports 2002/17778 + Arland already set):
cp /opt/wastelandz/server/server2.json.example /opt/reforger/configs/server2.json
nano /opt/reforger/configs/server2.json

In server2.json: set your server name + admin password (ports 2002/17778 and the Arland map are pre-set). Save and exit nano: Ctrl+O, Enter, Ctrl+X. The map is the scenarioId line — Wasteland-Z runs on top of any Game Master scenario, so a two-server hive can offer two different worlds:

# server1.json — Everon (the big island):
"scenarioId": "{59AD59368755F41A}Missions/21_GM_Eden.conf"

# server2.json — Arland (the smaller southern island):
"scenarioId": "{2BBBE828037C6F4B}Missions/22_GM_Arland.conf"

Any other map — including community maps — works the same way. Find its ID on the Workshop website: open the map mod’s page at reforger.armaplatform.com/workshop and click its Scenarios tab — the ScenarioId shown there (it looks like {GUID}Missions/Name.conf) is what goes in scenarioId. Add that map mod to the same server’s mods list too.

Each server starts with the same program, pointed at its own config + profile (the profile folder is created on first start):

# server 1 — Everon
/opt/reforger/server/ArmaReforgerServer -config /opt/reforger/configs/server1.json -profile /opt/reforger/profiles/server1 -addonDownloadDir /opt/reforger/workshop -maxFPS 60

# server 2 — Arland
/opt/reforger/server/ArmaReforgerServer -config /opt/reforger/configs/server2.json -profile /opt/reforger/profiles/server2 -addonDownloadDir /opt/reforger/workshop -maxFPS 60

Then wire server 2 into the hive exactly like Step 9 did for server 1:

  • Gateway: in config.py, un-comment the server-2 line in SERVERS and give it its own random api_key (Step 6 command), then sudo systemctl restart wz-gateway — it now listens on 5000 and 5001.
  • Wire it: in /opt/reforger/profiles/server2/profile/hf_wastelandz/configs/ (created on server 2’s first start), set GATEWAY_URL http://127.0.0.1:5001/ and put server 2’s key in its HFWastelandZ_secrets.conf.
  • Service: copy the kit’s /opt/wastelandz/server/wz-server2.service.example to /etc/systemd/system/wz-server2.service, change youruser, then sudo systemctl daemon-reload and sudo systemctl enable --now wz-server2. If ufw is on, open UDP 2002 + 17778.
Each server has its OWN Wasteland-Z config — edit the right copy

This is the step people get wrong. Wasteland-Z keeps a separate set of config files per server, inside that server’s own profile folder. They are created on that server’s first start, so boot server 2 once before you look for them:

/opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_server.conf
/opt/reforger/profiles/server2/profile/hf_wastelandz/configs/HFWastelandZ_server.conf

Editing server 1’s copy by mistake is the classic error — it makes server 1 start writing its data as server 2. Three settings decide whether server 2 works:

1. GATEWAY_URL — in HFWastelandZ_server.conf. The port is how the gateway knows which server is calling. Server 1 uses 5000, server 2 uses 5001. Trailing slash required.

# server 2's HFWastelandZ_server.conf
GATEWAY_URL http://127.0.0.1:5001/

2. SERVER_ID — same file, and it must differ from server 1’s. Money, bank and inventory are shared hive-wide on purpose; position, placements and money drops are split by SERVER_ID. Give both servers the same ID and players get the wrong spawn location and kept gear when they hop between them.

3. API_KEY — in HFWastelandZ_secrets.conf, in that same folder. It must match the api_key you set for that server in the gateway’s config.py.

Check all three across both servers in one go — the -H prints which file each line came from:

grep -H GATEWAY_URL /opt/reforger/profiles/server*/profile/hf_wastelandz/configs/HFWastelandZ_server.conf
grep -H SERVER_ID  /opt/reforger/profiles/server*/profile/hf_wastelandz/configs/HFWastelandZ_server.conf
grep -H API_KEY    /opt/reforger/profiles/server*/profile/hf_wastelandz/configs/HFWastelandZ_secrets.conf

You want server1 on 5000, server2 on 5001, and two different SERVER_ID values. Then restart server 2 so it picks the changes up:

sudo systemctl restart wz-server2
One key per server

Each server gets its own gateway port and its own random api_key — that’s how the gateway tells them apart. Never reuse a key across servers.

Will the box handle it?

Each Reforger server is heavy — roughly a few CPU cores and 4–8 GB RAM per server under load. Two servers want a strong 8-core machine; three want more. The gateway and MySQL are lightweight by comparison.

Start / stop / restart anything — one pattern

Every piece is a service with a name — wz-server1, wz-server2, wz-gateway, mysql — and one command pattern drives them all. Learn it once with server 1:

sudo systemctl start   wz-server1    # start it
sudo systemctl stop    wz-server1    # stop it (players get dropped)
sudo systemctl restart wz-server1    # stop + start in one go
sudo systemctl status  wz-server1    # running or not? (green "active" = good)
journalctl -u wz-server1 -f          # watch its live log — Ctrl+C stops watching, not the server

Swap the name for any other piece: bounce only server 2 = sudo systemctl restart wz-server2; stop the gateway = sudo systemctl stop wz-gateway; restart the database = sudo systemctl restart mysql. Each piece is independent — players on server 1 keep playing while you bounce server 2.

Order? After a full stop (or a reboot where something didn’t come up): mysql first, then wz-gateway, then the game servers. A briefly-down gateway doesn’t crash the game servers — saves pause and Wasteland-Z resyncs every player automatically when it comes back — but don’t leave it down long.

Big box? Give each server its own cores

RAM first — there’s nothing to divide. Linux hands out memory on demand: a full 128-player Reforger server uses roughly 8–16 GB, MySQL + the gateway a few more. Even three servers fit comfortably in 64 GB. The only setting worth adding is a per-server safety cap (below) so a memory leak can never take down the whole box.

CPU is the part worth splitting. A big AMD chip (like a 16-core Ryzen 7950X3D) is really two groups of 8 cores glued together. Each group shares one pot of very fast memory called cache — the chip’s short-term memory. A game server runs best when its group belongs to it alone; two servers squeezed into one group fight over the pot and both get slower. The fix is one line per server that says “you may only use these cores” — one server per group.

1 See your two groups

Run (looks, changes nothing):

lscpu -e

One row per CPU number. Look at the last digit on each row: rows ending in 0 are group one, rows ending in 1 are group two. On a 16-core AMD chip you’ll typically see: CPU numbers 0–7 and 16–23 end in 0 (group one), 8–15 and 24–31 end in 1 (group two). (Why 32 CPU numbers on a 16-core chip? Each core can run two things at once, so it shows up twice.)

On an “X3D” chip, one group has a much bigger memory pot — the fast one. See which:

cat /sys/devices/system/cpu/cpu0/cache/index3/size /sys/devices/system/cpu/cpu8/cache/index3/size
98304K   ← 96 MB = the BIG pot. First line is cpu0, so group one has it.
32768K   ← 32 MB = the normal pot (cpu8’s group — group two).

Give the big-pot group to your busiest server. If the 96 MB shows up on the second line instead, swap the two CPUAffinity lines in Step 2. Not an X3D chip? Both lines read the same — the split still works, there’s just no favourite group.

2 Pin each server to its group

Edit each server’s service file (the kit’s .service.example already ships these two lines commented out — you just delete the leading #):

sudo nano /etc/systemd/system/wz-server1.service   # then repeat for wz-server2.service

In the [Service] section of each file, uncomment (or add) its own pair of lines — wz-server1.service gets group one, wz-server2.service gets group two (they are separate files; never put both pairs in one). Save each (Ctrl+O, Enter, Ctrl+X):

# ── these two lines go in wz-server1.service (group one — the big-pot group) ──
CPUAffinity=0-7 16-23
MemoryMax=32G

# ── and these two go in the SEPARATE file wz-server2.service (group two) ──
CPUAffinity=8-15 24-31
MemoryMax=32G
3 Apply
sudo systemctl daemon-reload
sudo systemctl restart wz-server1 wz-server2

CPUAffinity = “this service may only run on these cores.” MemoryMax = if the server ever leaks past the cap, systemd kills it (and Restart=on-failure brings it right back) instead of the box dying. Leave the gateway and mysql unpinned — they’re lightweight and slot into whatever’s idle. Verify with htop: each server’s load stays inside its own core block. One core pegged at 100% while its neighbours idle is the engine’s normal single-thread ceiling, not a problem to fix — the levers for a struggling full server are aiLimit in serverN.json and Wasteland-Z’s mission/AI density settings.

Cheat-sheet: 1–4 servers on a 16-core box (two 8-core groups; CPU numbers per the usual AMD layout — verify yours with lscpu -e, Step 1):

SetupServer & playersCPUAffinity=MemoryMax=
1 server (up to 128)wz-server1 · 128none needed (whole chip; or 0-7 16-23 for the big-cache group)32G
2 servers (2×128)wz-server1 · 1280-7 16-2332G
wz-server2 · 1288-15 24-3132G
3 servers (2×128 + 1×64)wz-server1 · 1280-7 16-2332G
wz-server2 · 1288-15 24-3132G
wz-server3 · 64unpinned (scheduler fills the gaps)24G
4 servers (4×64 or 4×32)wz-server1 · 640-3 16-1924G
wz-server2 · 644-7 20-2324G
wz-server3 · 648-11 24-2724G
wz-server4 · 6412-15 28-3124G

The logic: 1–2 servers → a whole group each (max cache per server). 4 servers → half a group (4 cores) each — plenty at 32–64 players, and no server ever spans both groups. 3 servers is the awkward count: keep the two big ones on their own groups and let the third float. Player caps live in each serverN.json (maxPlayers); what actually loads a core is AI + player count together, so watch htop on busy nights and adjust aiLimit before touching the core map. RAM: even the 4-server row uses under half of a 128 GB box.

Different CPU? Step 1 tells you the truth about any chip. If every row’s last digit is 0, your chip is one group (most older Intels, e.g. an i9-9900K): there’s no favourite to pick — split the cores in half (first half + their twins to server 1, the rest to server 2) and expect lower comfortable player caps on older chips. Newer Intels (12th gen up, e.g. an i7-13700K) mix fast P-cores and small E-cores — in lscpu -e the P-cores appear twice (they have twins), E-cores once. Pin game servers to P-cores only and leave the E-cores unpinned — they carry the gateway, MySQL and the system.

Troubleshooting

  • “Database connection failed”DB_PASSWORD in config.py doesn’t match Step 4, or MySQL is stopped (sudo systemctl status mysql).
  • Service won’t start — run journalctl -u wz-gateway -e to see why; usually a wrong path or username in the service file.
  • status=217/USER — the service file still says User=youruser, or names a user that doesn’t exist. Fix with grep -n "User" /etc/systemd/system/wz-server1.service. Note systemd-analyze verify does not catch this — the syntax is valid, the user simply isn’t real.
  • Missing '=', ignoring line — a long line got split when you pasted it, almost always ExecStart, which must be one unbroken line. systemd keeps the first half and silently discards the rest, so the server still starts but loses -maxFPS and -addonDownloadDir with no error at all. Rejoin the line, then daemon-reload and restart.
  • start-limit-hit / “Start request repeated too quickly” — the service failed enough times that systemd stopped retrying. Fix the underlying cause first, then clear the lockout: sudo systemctl reset-failed wz-server1.
  • Your edit to a service file seems to do nothing — you restarted without reloading. systemd caches unit files; always sudo systemctl daemon-reload before restart.
  • Server won’t boot: Failed to fetch addon details from workshop API — one mod in your mods list has a new version the Workshop won’t serve, and the server refuses to start rather than use the copy it already has. The line just above the error names the culprit (Addon Download started <id> - <name>). Remove that mod’s block from mods and restart. If another mod depends on it, the engine still loads the working copy from disk, so you usually lose nothing. Put it back once the Workshop is serving it again. Note that modsRequiredByDefault: false does not rescue this case — that covers a failed download, whereas this fails earlier, at the version lookup, and kills the whole boot.
  • Port 5000 in use — change port in config.py and update GATEWAY_URL.
  • Money/gear not savingGATEWAY_URL or api_key mismatch between the game server and config.py.
  • Last location & kept gear don’t restore on relog (but money & inventory DO) — the game server’s SERVER_ID (in HFWastelandZ_server.conf) doesn’t match the server_id the gateway uses for it (its SERVERS entry / SERVER_ID). Money/inventory are hive-wide so they still work; only per-server data (position, kept-on-relog gear) needs the ids to match. Set SERVER_ID to the gateway’s id and restart. The game log prints SERVER_ID MISMATCH with both values when it detects this — grep -i "SERVER_ID MISMATCH" your log.

Add, remove & track mods

Same on Windows and Linux. There is no installer — the mods list inside your server config (server1.json) is the mod manager. On every start the server reads that list, auto-downloads anything missing from the Reforger Workshop into your workshop folder, and loads exactly what’s listed — nothing else.

Add a mod

Find it on the official Workshop site: reforger.armaplatform.com/workshop. The mod’s ID is right in its page URL — the 16 characters before the name, e.g. …/workshop/68A616565DECAB19-wasteland-z. Add one block to the mods list and restart the server:

Wasteland-Z is live on the Workshop

The kit already pre-lists it in every mods block, so you don’t need to add it by hand — but here’s its page: reforger.armaplatform.com/workshop/68A616565DECAB19 (mod ID 68A616565DECAB19).

"mods": [
    {
        "modId": "68A616565DECAB19",
        "name": "Wasteland-Z"
    },
    {
        "modId": "ABCDEF1234567890",
        "name": "Cool Helmet Pack"
    },
    {
        "modId": "0123456789ABCDEF",
        "name": "Winter Uniforms"
    }
]

The name is your own label — write whatever helps you remember what it is. The commas are the one gotcha: every mod’s closing } gets a comma except the last one — and never a semicolon, JSON doesn’t use them. So when you add a mod at the end: the block that used to be last gains a comma, the new last block has none.

Remove a mod — including a problem mod

Delete its block from the list, restart. That’s the whole procedure — the server only loads what’s listed. The mod’s downloaded files stay in the workshop folder but are completely inert; if you want the disk space back, delete the folder named with that mod’s ID inside the workshop folder.

Can’t I just comment a mod out?

Not in this file. server1.json is JSON, and JSON forbids comments — one // line and the server refuses the whole config. So in the json, deleting the block is the only off switch. The convenient middle ground is a parking file: keep a plain text file next to your configs that nothing reads, and move benched mod blocks there instead of losing them:

mods-parked.txt — your parking lot; the server never reads this file.
Copy a block back into server1.json's "mods" list to re-enable it.

{ "modId": "ABCDEF1234567890", "name": "Cool Helmet Pack" },     broke after 1.7 update, retry later
{ "modId": "0123456789ABCDEF", "name": "Winter Uniforms" },      summer season — bring back in December
{ "modId": "FEDCBA9876543210", "name": "Extra Car Pack" },       testing caused rubber-banding

(Wasteland-Z’s own .conf files are different — those are KEY VALUE format and take # comments freely. Only the game server’s .json is strict.)

Track your mods

The mods list is the single source of truth — one block per mod, nothing can load that isn’t in it. Running several servers? Each serverN.json has its own list, so server 1 and server 2 can run different mod sets. To see what a server actually loaded, its boot log prints every addon as it starts.

One game mode at a time

Wasteland-Z is a game mode — it owns the gameplay. Content mods (weapons, clothing, vehicles, buildings) sit alongside it fine; adding another game-mode mod makes the two fight. If a mod’s Workshop page says “game mode,” it doesn’t belong on a Wasteland-Z server.

Server broke after adding a mod — or after a game update?

Remove the last mod you added, restart. If it broke right after a Reforger update instead: mod authors need a few days to catch up with game patches — remove your extra mods, restart, then add them back one at a time as they’re updated.

Players, roles & reserved slots

This part is the same on Windows and Linux — it’s done with config files and in-game commands.

The five roles

Every player has a role. Higher beats lower. Roles live in one file: $profile:HFWastelandZ_admins.conf, keyed by Bohemia Identity GUID (a lowercase xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx code — not your Steam number; see Part 1 → Step 9d for how to find yours).

RoleCan do
OWNEREverything, including /admin restart. Set by editing the file only.
ADMINGame Master (Zeus), promote/demote others, save, all mission & spot editing.
MODKick, ban, mute, teleport, heal, god, announce.
TRUSTEDDonor / verified helper (reserved for future perks).
EVERYONEDefault — every normal player. No line needed.

Make someone a mod, admin, or owner

Your first admin — and the only way to set OWNER — is by editing the file. Add a line and restart:

# in $profile:HFWastelandZ_admins.conf
OWNER a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
ADMIN b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e
MOD   c3d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f

Those are Bohemia Identity GUIDs (lowercase 8-4-4-4-12 hex), one per admin — find yours via Part 1 → Step 9d. Put no trailing space after the GUID (see the gotcha in Step 9d). Anyone not listed is EVERYONE.

After you have one admin, change roles live, no restart, from in-game chat:

/admin promote <name|playerId> <EVERYONE|TRUSTED|MOD|ADMIN>
The rules

You must be ADMIN or OWNER to change roles. An ADMIN can grant any role up to and including ADMIN. OWNER is never granted in-game — it’s file-only so no one can lock you out, so seed yourself as OWNER in the file first. You can change anyone at or below your own rank (an ADMIN can demote a peer ADMIN), but never someone above you — and an OWNER can never be changed in-game by anyone.

You can also change roles in the game — open the F8 admin menu → Players tab. The right-hand STAFF list shows everyone with a role (including offline people); click a player or staff row, then a role button (twice to confirm).

See who has what role

/admin who

Lists everyone online with their role. Any MOD or higher can run it.

Change what each command needs (command tiers)

Every command has a minimum role. To run it, your role must be equal or higher. Change any command’s required role in $profile:HFWastelandZ_command_tiers.conf:

# format:  <command>.<subcommand> = <ROLE>
admin.god  = OWNER     # only owner can use god mode
admin.kick = ADMIN     # mods can no longer kick

So admins.conf sets what role each player has, and command_tiers.conf sets what role each command needs. Restart to apply changes.

Reserved login slots (staff always get in)

Keeps a couple of slots open so your staff can always join a full server, without permanently wasting seats. Set these in $profile:HFWastelandZ_server.conf:

SettingDefaultWhat it does
RESERVED_SLOTS_ENABLED0Master on/off. Set to 1 to turn it on.
RESERVED_BUFFER2Slots always kept free so staff join instantly.
RESERVED_HIGHRISK_COUNT8How many newest regular players are “bumpable.”
RESERVED_MIN_TIER20Lowest role treated as staff (20 = MOD).
RESERVED_TOTAL_SLOTS0Leave 0 on a real server (read automatically). Only set on a Workbench test host.

Setup: in the game’s config.json, set game.maxPlayers to your playable count plus the buffer, and set RESERVED_TOTAL_SLOTS to that same number. Example: 128 players → maxPlayers 130, RESERVED_BUFFER 2. ADMIN/OWNER always get in; MOD only bumps regular players.

How to see it working

It runs on the server, so watch the server console / log — set HF_DEBUG_ADMIN 1 in server.conf and you’ll see lines like ReservedSlots: ON total=130 buffer=2… and BUMP… when it acts. Players in a bumpable slot also see a “high-risk” banner in their HUD.

Wasteland-Z · Dedicated Server

Set up your server, step by step.

Everything, in the order you run it — install the Arma Reforger server, make the database, start the gateway, and wire them together with simple start/stop scripts. No prior server experience needed. Pick your operating system below and follow the numbered steps.

The game, the mod, and this kit

Arma Reforger is Bohemia Interactive’s military simulation game (PC and Xbox), built on the Enfusion engine — the platform that succeeds Arma 3. Anyone can host a dedicated server, and mods install themselves: when a player joins a modded server, the game downloads its mods automatically from the built-in Workshop.

Wasteland-Z is a hardcore PVP open-world mod for Arma Reforger, by Heavy Forge, Inc. The world after the Z — the outbreak ended, the cities emptied, and what survived now scavenges, fights, and trades for what’s left. Town-based scarcity loot, missions, gun / general / vehicle stores, a server-authoritative money economy with ATM banking, three-faction PVP, and a cross-server hive: the same money, bank and gear on every server in a cluster. It runs on top of any Game Master scenario, on any map. The gameplay concept honors the A3 Wasteland lineage, rebuilt ground-up for Reforger.

This kit is the server side — for admins who want to host a Wasteland-Z server: the guide below, the gateway program, the database schema, example configs, and start/stop scripts. Just want to play? You need nothing from here — find a Wasteland-Z server in the Reforger server browser and join. The mod installs itself.

What you're building

A working server is three programs talking to each other. You install all three once, then they run on their own.

Game Server
Arma Reforger + the Wasteland-Z mod (from the Workshop). Where people play.
Gateway
The small program in this download. It carries player data between the game and the database.
MySQL
The database. Stores money, gear, progress, and the world — so nothing is lost on restart.

What's in this download

gateway/
The gateway program. Copy this whole folder to your server and run it.
gateway/setup_database.sql
Every database table, defined in one file. The gateway applies it on every start, so you never run SQL by hand and there is no separate first-time step. It only ever adds — running it again changes nothing.
gateway/migrate.py
Applies the schema on start, then any pending data change. Records what it applied so nothing runs twice.
gateway/tests/
The tests we run against the gateway, shipped so you can run them yourself. One of them wipes a database and says so — read its README first.
gateway/config.example.py
The settings template. You copy it to config.py and fill in your password + key.
gateway/start_gateway.bat
Windows one-click start.
configs/
Reference copies of the server settings (loot, vehicles, towns, admins…) — the server writes its own working copies on first start (see below), so these are for reading and optional pre-tuning, not required drop-ins. The README inside lists each file and which ones auto-create; every setting is documented inline.
missions/
Reward templates for the mission system, plus a guide to authoring missions in-game.
server/
Game-server kit — SteamCMD install script, example server1.json, and the start/stop batch files (auto-restart loop included).
The server builds its own config files — you don’t have to

The first time Wasteland-Z starts, it writes a complete, ready-to-run set of config files into your server profile’s hf_wastelandz\configs\ folder, each pre-filled with the current defaults. You can start with no config files at all and edit the generated ones afterwards — that’s the recommended path, because the files the server writes always match the mod version you’re running (a hand-copied kit file can fall behind after an update).

Auto-created on first start: HFWastelandZ_server.conf, HFWastelandZ_secrets.conf, HFWastelandZ_admins.conf, HFWastelandZ_blacklist.conf, HFWastelandZ_command_tiers.conf, HFWastelandZ_loadouts.conf, HFWastelandZ_town_tiers.conf, HFWastelandZ_loot_spawn_filter.conf, HFWastelandZ_item_catalog.conf, HFWastelandZ_vehicle_spawn.conf, HFWastelandZ_server_message.txt, plus the per-map files under configs\map\<MapName>\ (towns, spawn points, loot pools, world loot, and the gun / general / vehicle store catalogs — these regenerate for whatever map you load).

The two you must fill in yourself: HFWastelandZ_secrets.conf is written with a placeholder API key you must replace, and HFWastelandZ_admins.conf is written empty so you must add your Bohemia Identity GUID (not your Steam number) to become an admin — see Step 9d. Everything else runs on defaults until you decide to change it.

Where everything lives on your server

After setup you have three top-level folders (Linux /opt/ shown below; Windows mirrors this under C:\reforger\ plus your gateway folder). The spot that trips people up: HF’s live config files sit under the profile’s extra profile/ subfolder.

/opt/
├── wastelandz/                # THIS KIT (git clone) — reference copies, read-only
│   ├── gateway/                 # gateway program + setup_database.sql + config.example.py
│   ├── configs/                 # reference *.example HF settings (optional pre-tuning)
│   ├── missions/                # mission reward templates + in-game authoring guide
│   └── server/                  # systemd + server1/2.json examples + SteamCMD script
│
├── wastelandz-gateway/        # the RUNNING gateway (copied out of the kit)
│   ├── config.py                # YOUR DB password + gateway key — never share or commit
│   ├── gateway.py               # the Flask service
│   ├── setup_database.sql       # builds the tables (tables only — holds NO credentials)
│   └── venv/                    # isolated Python you create (Ubuntu 24.04 / PEP 668)
│
└── reforger/                  # the game server
    ├── server/                  # ArmaReforgerServer + game files (from SteamCMD)
    ├── workshop/                # -addonDownloadDir: Workshop mods download here, shared by all servers
    │   └── addons/
    │       └── Wasteland-Z_68A616565DECAB19/   # the downloaded mod (data.pak)
    ├── configs/
    │   └── server1.json         # Reforger config: server NAME, admin password, mods, scenario
    └── profiles/
        └── server1/             # -profile dir for this server
            ├── logs/            # console.log lives here
            └── profile/         # $profile: root  <-- note the extra 'profile/' level
                └── hf_wastelandz/
                    ├── configs/  # *** HF configs: server.conf, secrets.conf, admins.conf, catalogs...
                    └── missions/ # mission reward templates go here

Key takeaway: the HF config files are at /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/ — note the profile/ level Enfusion adds. On Windows it’s the same shape under C:\reforger\profiles\server1\profile\hf_wastelandz\configs\.

Before you begin

You need a PC (or rented machine) that stays on, and this download. That’s it — the guide covers installing the game server itself, and the server downloads from Steam anonymously (no Steam account needed). The Wasteland-Z mod downloads automatically from the Reforger Workshop the first time the server starts.

“This download” = the setup kit’s GitHub repo. To grab it: open the repo page → green Code button → Download ZIP → unzip anywhere on your PC. No git and no GitHub account needed. (Linux users can skip the ZIP — the Linux tab’s Step 1 pulls it straight onto the server.)

Choose your system
Part 1

Install the game server

Three steps: get SteamCMD, download the server with it, drop in a config. The server\ folder of this download has everything ready-made.

Planning 2–3 servers?

Build server 1 first. Part 4 adds each extra server: the game installs once; every additional server is a config file with new ports (+ its own map if you want) and its own start script.

1

Make the folders and get SteamCMD

SteamCMD is Valve’s little downloader — it fetches and updates the dedicated server. Make this folder layout (one parent folder, four inside):

C:\reforger\
  steamcmd\    ← SteamCMD lives here
  server\      ← the game server gets installed here
  configs\     ← your server settings (server1.json)
  profiles\    ← each server's save data + Wasteland-Z configs
  workshop\    ← downloaded mods (shared by all your servers)

Get SteamCMD from Valve’s official page: developer.valvesoftware.com/wiki/SteamCMD — under Windows it links the steamcmd.zip download. Unzip it into C:\reforger\steamcmd\. No Steam account is needed — the dedicated server downloads anonymously.

2

Download the server

Copy the server\ folder from this download into C:\reforger\scripts\, then double-click install_or_update_server.bat. It pulls the Arma Reforger dedicated server (Steam app 1874900, a few GB) into C:\reforger\server\.

If you prefer typing it yourself, the script just runs:

C:\reforger\steamcmd\steamcmd.exe +force_install_dir C:\reforger\server ^
  +login anonymous +app_update 1874900 validate +quit
Game updated?

Whenever Reforger gets an official update, stop your servers and run this same script again. It only downloads what changed.

3

Drop in the server config

Copy server\server1.json.example to C:\reforger\configs\server1.json and open it in Notepad. Set two things:

  • name — your server’s name in the browser list.
  • passwordAdmin — a password only you know.

Everything else already works: the Wasteland-Z mod is pre-listed in mods (it auto-downloads on first start), and the map is Everon (21_GM_Eden). For Arland instead, set scenarioId to {2BBBE828037C6F4B}Missions/22_GM_Arland.conf.

Router / firewall

Players connect over UDP ports 2001 (game) and 17777 (server browser). On a home connection, forward both UDP ports to this PC in your router. On a rented server, allow them in the provider’s firewall panel.

Don’t start it yet — first set up the database and gateway below, so the server has something to save into.


Part 2

Database & gateway (one time)

Do these eight steps in order. Copy each command with the button on its right.

1

Install the two programs you need

Install MySQL 8 (the database) and Python 3.12+ (runs the gateway).

  • MySQL: get the MySQL Installer from dev.mysql.com → install MySQL Server. When it asks, set a root password and write it down.
  • Python: get it from python.org. On the first install screen, tick “Add python.exe to PATH” — this matters.

Check both installed — open Command Prompt and run:

python --version
mysql --version
2

Create the database

Open “MySQL 8.0 Command Line Client” from the Start menu (it asks for the root password from Step 1). Paste these four lines — they make an empty database and a login for the gateway:

CREATE DATABASE wastelandz;
CREATE USER 'wastelandz'@'localhost' IDENTIFIED BY 'your_password_here';
GRANT ALL PRIVILEGES ON wastelandz.* TO 'wastelandz'@'localhost';
FLUSH PRIVILEGES;
Pick a password

Replace your_password_here with a password you choose, and write it down — the gateway needs it in Step 5. Because MySQL 8’s password policy is on, it must be 8+ characters with an upper- and lower-case letter, a number, and a symbol (e.g. Wast3land_Z!) — a weaker one fails with ERROR 1819.

3

Check the login works

Prove the database and password you just made actually work, before anything depends on them. Open a normal Command Prompt, go to the folder where you unzipped this download, and run:

mysql -u wastelandz -p wastelandz < gateway\setup_database.sql

It asks for the wastelandz password (from Step 2). No error message means it worked — and it built the tables while it was there.

You are not required to run this

The gateway applies setup_database.sql itself on every start, so the tables would appear on their own the first time you launch it. This step is here because a wrong password is much easier to understand now, on its own, than three steps later mixed in with everything else the gateway does at startup.

Running it and letting the gateway run it are the same thing — the file is written so that running it twice changes nothing. There is no first-time-only path to get wrong.

4

Put the gateway in place

Copy the whole gateway folder somewhere permanent — for example C:\wastelandz-gateway. Open Command Prompt in that folder and install the parts it needs:

pip install -r requirements.txt
5

Enter your settings

First generate the gateway key — a unique API key (use a different one for every server). Copy the 64-character line it prints:

python -c "import secrets; print(secrets.token_hex(32))"

In the gateway folder, make a copy of config.example.py and name the copy config.py. Open config.py in Notepad and set two things:

  • DB_PASSWORD = the password you made in Step 2.
  • Set api_key — the GATEWAY KEY, shown in the file as CHANGE_ME_UNIQUE_KEY_1 — to the key you just generated. Each server needs its own unique gateway key.

Leave host as 127.0.0.1 and port as 5000.

Keep it private

config.py holds your password and key. Never share it or upload it anywhere public.

6

Start it and check

Double-click start_gateway.bat (or run python gateway.py). A black window opens and stays open — that’s normal, leave it running. You should see Database connection: OK and a Listening… line.

Now open a browser to:

http://127.0.0.1:5000/api/ping

You should see "status":"ok" and "database":"connected". That means the gateway and database are talking.

7

Firewall

If the gateway and the game server are on the same computer (the normal setup) — do nothing. Local connections already work.

Only if a different computer or a Discord bot needs to reach the gateway, open port 5000 to that one address:

netsh advfirewall firewall add rule name="WZ Gateway" dir=in action=allow ^
  protocol=TCP localport=5000 remoteip=THE.OTHER.IP.ADDRESS
Two hard rules

Never open port 5000 to everyone (no wide-open rule). And leave MySQL’s port 3306 closed — the gateway reaches it locally; the internet never should.

8

Connect the game server — then first start

Start the server once (double-click C:\reforger\scripts\start_server1.bat) and let it boot — it downloads the Wasteland-Z mod and creates its profile folder, then Wasteland-Z writes starter config files into C:\reforger\profiles\server1\profile\hf_wastelandz\configs\. Close the server window, then wire it up:

  • HFWastelandZ_server.confGATEWAY_URL already defaults to http://127.0.0.1:5000/ (correct for a local gateway); only change it if your gateway is on another machine/port.
  • In …\hf_wastelandz\configs\HFWastelandZ_secrets.conf, add a line: API_KEY followed by the same key you put in the gateway’s config.py in Step 5. (A ready-made template with key-generation instructions ships in this download: configs\HFWastelandZ_secrets.conf.example.)
  • Want to pre-tune settings? Take the files in this download’s configs\, remove .example from each name, and copy them into that same profile\hf_wastelandz\configs\ folder. configs\README.md lists what each file controls; every setting is documented inside its file.
  • Copy missions\rewards\ into …\profiles\server1\profile\hf_wastelandz\missions\rewards\ — these are the loot crates and vehicles missions hand out. (Missions themselves are built in-game — see missions/README.md.)

Start the server again. Player data now saves to the database. You’re live.


Part 3

Run · monitor · stop

Day-to-day, with zero server knowledge: every piece runs in its own labeled window. If a window is open, that piece is running. Close a window, that piece stops — nothing else is touched. The batch files live in C:\reforger\scripts\.

Start everything

Double-click start_all.bat. It opens the WZ-Gateway window, waits a few seconds, then opens WZ-Server-1 (and 2/3 if you enabled them).

Crash protection

Each server window runs a loop: if the game server crashes or closes, it restarts by itself in 10 seconds. The gateway just keeps running.

Stop ONE game server

Close its WZ-Server-1 window (the X), or double-click stop_server1.bat. The gateway and any other servers stay up.

Stop the gateway only

Close the WZ-Gateway window. Game servers keep running (players keep playing; saves hold in memory and it catches up when you start the gateway again — don’t leave it off long).

Stop everything

Close all the WZ-… windows. Done. (Stop servers before the gateway so last saves land.)

Watch it (monitor)

The windows are the monitors — gateway saves/loads scroll in WZ-Gateway, the game log scrolls in each server window.

Start on boot

Press Win+R, type shell:startup, Enter — then put a shortcut to start_all.bat in the folder that opens. Everything launches when you log in. (Prefer no auto-login? Use Task Scheduler → At startup instead.)

Game updated?

Stop the server windows, run install_or_update_server.bat, start again. The Wasteland-Z mod updates itself on server start.

Is it alive?

Browse http://127.0.0.1:5000/api/ping anytime. status: ok = healthy.

Restart MySQL

Win+R → type services.msc → find MySQL80 → right-click → Restart.

Back up your data (do this regularly)

mysqldump -u wastelandz -p wastelandz > wastelandz_backup.sql

Keep a copy of that file somewhere safe. It is your whole server’s save.

Troubleshooting

  • “Database connection failed” — the password in config.py doesn’t match Step 2, or MySQL isn’t running.
  • Port 5000 already in use — change port in config.py (and update GATEWAY_URL to match).
  • /api/ping won’t load — the gateway window isn’t running. Start it.
  • Money/gear not savingGATEWAY_URL or api_key don’t match between the game server and config.py.
  • Last location & kept gear don’t restore on relog (but money & inventory DO) — your game server’s SERVER_ID (in HFWastelandZ_server.conf) doesn’t match the server_id the gateway uses for it. Money/inventory are hive-wide so they still work; only per-server data (your position, kept-on-relog gear) needs the two ids to match. Set SERVER_ID to the gateway’s id and restart. The game log prints SERVER_ID MISMATCH with both values when it detects this — grep for it.

Part 4

Two or three servers on one PC

One database, one gateway, several game servers — that’s a hive. Players keep the same money, bank and gear on every server in the hive; each server keeps its own world (positions, placed objects, map). One gateway serves them all — you do not run a second gateway or database.

The finished two-server setup, and how the pieces talk:

players → UDP 2001/17777 → WZ-Server-1 (Everon)  → http://127.0.0.1:5000 + key #1 ─┐
players → UDP 2002/17778 → WZ-Server-2 (Arland)  → http://127.0.0.1:5001 + key #2 ─┤
                                                                              └→ WZ-Gateway  MySQL
The gateway tells the servers apart by the PORT each one calls (5000 vs 5001)
and proves it with that server's key. Both ports are ONE gateway window,
and everything right of the players is local to the PC — nothing exposed.

Every extra server is the same four things with different numbers. The unique-ports table:

Server 1Server 2Server 3
Game port (UDP, forward it)200120022003
Browser port (UDP, forward it)177771777817779
Gateway port (local only)500050015002
Config fileserver1.jsonserver2.jsonserver3.json
Profile folderprofiles\server1profiles\server2profiles\server3

To add server 2 (server 3 is the same pattern):

  • Config: copy this download’s server\server2.json.example to C:\reforger\configs\server2.json — ports 2002/17778 and the Arland map are pre-set; change name and the admin password. The map is the scenarioId line — Wasteland-Z runs on top of any Game Master scenario:
    Everon (the big island):
    "scenarioId": "{59AD59368755F41A}Missions/21_GM_Eden.conf"
    
    Arland (the smaller southern island):
    "scenarioId": "{2BBBE828037C6F4B}Missions/22_GM_Arland.conf"
    e.g. server 1 on Everon, server 2 on Arland — same hive, so players keep their money, bank and gear when they switch.

    Any other map — including community maps — works the same way. Find its ID on the Workshop website: open the map mod’s page at reforger.armaplatform.com/workshop and click its Scenarios tab — the ScenarioId shown there (it looks like {GUID}Missions/Name.conf) is what goes in scenarioId. Add that map mod to the same server’s mods list too.

  • Batch files: copy start_server1.batstart_server2.bat and stop_server1.batstop_server2.bat. Inside both, change every server1 to server2 and the title WZ-Server-1 to WZ-Server-2. (The install folder and workshop\ mods folder are shared — don’t copy those.)
  • Gateway: in config.py, un-comment the server-2 line in SERVERS and give it its own random api_key (Step 5 command). Restart the gateway window — it now listens on 5000 and 5001.
  • Wire it: in profiles\server2\profile\hf_wastelandz\configs\ (created on server 2’s first start), set GATEWAY_URL http://127.0.0.1:5001/ and put server 2’s key in its HFWastelandZ_secrets.conf.
  • Match the SERVER_ID (critical): in server 2’s HFWastelandZ_server.conf, set SERVER_ID server-2 so it exactly matches that server’s server_id in the gateway’s SERVERS list. If they differ, last-location and kept-gear will silently not restore on that server (money/inventory still work, since those are hive-wide). The game log prints SERVER_ID MISMATCH if it’s wrong.
  • Start it: remove the rem in front of the server-2 lines in start_all.bat. Forward UDP 2002 + 17778 in your router.
One key per server

Each server gets its own gateway port and its own random api_key — that’s how the gateway tells them apart. Never reuse a key across servers.

Will my PC handle it?

Each Reforger server is heavy — roughly a few CPU cores and 4–8 GB RAM per server under load. Two servers want a strong 8-core machine; three want more. The gateway and MySQL are lightweight by comparison.

Big box? Give each server its own cores

RAM needs no dividing — Windows hands it out on demand, and a full 128-player server uses roughly 8–16 GB. (Windows has no simple per-program RAM cap like Linux’s — skip that part.) CPU is the part worth splitting: a big AMD chip (e.g. a 16-core Ryzen 7950X3D) is really two groups of 8 cores, each sharing one pot of very fast memory (cache). Two servers squeezed into one group fight over the pot — give each server its own group.

On Windows the tool is the /affinity flag. In start_server1.bat, change the server line:

rem before:
"%SERVER_EXE%" -config "%CONFIG%" -profile "%PROFILE%" -addonDownloadDir "%ADDONS%" -maxFPS 60

rem after — server 1 pinned to the first 16 processor numbers (group one):
start "" /affinity FFFF /wait "%SERVER_EXE%" -config "%CONFIG%" -profile "%PROFILE%" -addonDownloadDir "%ADDONS%" -maxFPS 60

rem and in start_server2.bat — the next 16 (group two):
start "" /affinity FFFF0000 /wait "%SERVER_EXE%" -config "%CONFIG%" -profile "%PROFILE%" -addonDownloadDir "%ADDONS%" -maxFPS 60

The mask is a code for “which processors”. FFFF = the first 16 processor numbers; Windows usually numbers each core twice back-to-back, so that’s the first 8 cores = the first group (the big-cache one on X3D chips — give it to your busiest server). /wait keeps the auto-restart loop working. Sanity-check anytime: Task Manager → Details → right-click ArmaReforgerServer.exeSet affinity shows exactly which processors each server is allowed on.

Cheat-sheet: 1–4 servers on a 16-core PC (two 8-core groups; Windows numbers each core twice back-to-back):

SetupServer & players/affinity mask
1 server (up to 128)WZ-Server-1 · 128none needed (whole chip; or FFFF for the big-cache group)
2 servers (2×128)WZ-Server-1 · 128FFFF
WZ-Server-2 · 128FFFF0000
3 servers (2×128 + 1×64)WZ-Server-1 · 128FFFF
WZ-Server-2 · 128FFFF0000
WZ-Server-3 · 64none (floats in the gaps)
4 servers (4×64 or 4×32)WZ-Server-1 · 64FF
WZ-Server-2 · 64FF00
WZ-Server-3 · 64FF0000
WZ-Server-4 · 64FF000000

The logic: 1–2 servers → a whole group each (max cache per server). 4 servers → half a group (4 cores) each — plenty at 32–64 players, and no server ever spans both groups. 3 servers is the awkward count: pin the two big ones, let the third float. Player caps live in each serverN.json (maxPlayers); what actually loads a core is AI + player count together, so watch Task Manager on busy nights and adjust aiLimit before touching the masks.

Different CPU? Older Intels (e.g. an i9-9900K, 8 cores) are one group — no favourite half; split down the middle with FF / FF00 and expect lower comfortable player caps. Newer hybrid Intels (12th gen up, e.g. an i7-13700K) mix fast P-cores (numbered first, each twice back-to-back) and small E-cores (after them): pin game servers to P-cores (FF / FF00) and leave the E-cores unpinned — they carry the gateway, MySQL and Windows itself. See your layout anytime: Task Manager → Details → right-click the server → Set affinity lists every processor number.

Part 1

Set it up (one time)

These are Ubuntu commands. Type them into the server’s terminal (connect with SSH). Every command is explained.

Planning 2–3 servers?

Build server 1 first. Part 3 adds each extra server: the game installs once; every additional server is a config file with new ports (+ its own map if you want) and its own service. The kit ships the server-2 files pre-made.

New to Linux? The 60-second picture

Your Windows PC is the remote control; the Linux box is the machine that runs everything. You type commands into it over SSH — open PowerShell on your PC and run ssh youruser@your-server-ip (SSH is built into Windows, nothing to install). Every command below is typed into that window.

Linux has no C:\. Your personal folder is /home/youruser — written ~ for short, so ~/reforger would mean /home/youruser/reforger. Software you install yourself goes in /opt/ by convention — that’s this guide’s equivalent of making C:\reforger\, and everything lives there:

/opt/wastelandz            ← this setup kit, straight from GitHub (Step 1)
/opt/reforger              ← the game server (Step 2)
/opt/wastelandz-gateway    ← the running gateway (Step 5)
0

Accounts — you and your friends

Your rented box hands you the root login — the master account. Setting up from it is fine. First job: make an admin account for yourself and each friend who’ll help run the server — an admin can do everything in this guide (restart servers, edit configs, run game updates) without needing you.

See your admins:

getent group sudo     # the admins — every name after the last ':' is one

Add an admin (yourself, or a friend who’ll help run the server — two commands, that’s it):

sudo adduser dave           # pick any name; it asks you to set a password
sudo usermod -aG sudo dave  # make him an admin

Dave logs in with ssh dave@your-server-ip and the password you set (he can change it with passwd). He must log out and back in once for admin power to kick in — then he can do everything in this guide: restart servers, edit configs, run updates. Only add people you trust with the whole box.

Remove an admin:

sudo deluser dave sudo              # drops admin power, keeps his login
sudo deluser --remove-home dave     # deletes the account completely

Do the steps below from root or from your own admin account — the commands work either way. One thing to remember for later: the service files in Part 2 and Step 9 have a User= line — put your account name there, so the game and gateway run under your account.

1

Get the files onto the server

Everything in this kit is one GitHub repo. Two ways to get it onto the Linux box — pick one:

Option A — git (recommended). One command pulls the whole kit straight onto the server; updating later is one more command. On the server:

sudo apt install -y git
sudo git clone -b beta https://github.com/Motavar/HF_WastelandZ_SETUP.git /opt/wastelandz
sudo chown -R $USER /opt/wastelandz

Get updates later with: cd /opt/wastelandz then git pull

Option B — WinSCP (drag & drop). On your PC: open the repo page on GitHub → green Code button → Download ZIP → unzip it. Make the target folder on the server first:

sudo mkdir -p /opt/wastelandz
sudo chown $USER /opt/wastelandz

Then connect WinSCP (a free tool) to the server and drag the unzipped folder’s contents into /opt/wastelandz — it works like copying between two Explorer windows.

2

Install the game server

SteamCMD is Valve’s little downloader — it fetches and updates the dedicated server. On Ubuntu it lives in the “multiverse” package section, so enable that first:

sudo add-apt-repository multiverse
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y steamcmd

A license screen appears during install — accept it. No Steam account is needed; dedicated servers download anonymously.

Not on Ubuntu? Debian, Arch and other distros install SteamCMD differently — see Valve’s official page for yours: developer.valvesoftware.com/wiki/SteamCMD. The rest of this guide works the same once steamcmd is installed.

Make the game server’s folder layout (same idea as C:\reforger\ on Windows), then download the server into it — a few GB:

sudo mkdir -p /opt/reforger/server /opt/reforger/configs /opt/reforger/profiles /opt/reforger/workshop
sudo chown -R $USER /opt/reforger

steamcmd +force_install_dir /opt/reforger/server +login anonymous +app_update 1874900 validate +quit
Game updated?

Updated for 1.8 — 2026-08-17: you no longer have to do this by hand. The service file from Part 2 runs SteamCMD before every start, so the server brings itself up to date each time it restarts. That matters because a server on an older build than its players is unjoinable — clients auto-update through Steam whether you do or not. Keep the command above for the first install, and for repairs (the validate flag re-checks every file — useful when something is corrupt, too slow to run on every restart).

Drop in the ready-made config from the kit (Wasteland-Z is pre-listed in mods — it works as-is; open it in nano later to rename your server — save/exit with Ctrl+O, Enter, Ctrl+X):

cp /opt/wastelandz/server/server1.json.example /opt/reforger/configs/server1.json

Don’t start it yet — it needs the database and gateway first (Steps 3–8). Step 9 does the first start.

3

Install MySQL and Python

Update the package list, then install both:

sudo apt update
sudo apt install -y mysql-server python3 python3-pip

Then lock MySQL down and set the root password:

sudo mysql_secure_installation

It asks a series of questions. Recommended answers:

  • VALIDATE PASSWORD component? — optional. N is fine (you set your own strong passwords).
  • New root password — set a strong one and write it down.
  • Remove anonymous users?Y — closes password-less access.
  • Disallow root login remotely?Y — root only connects locally; the gateway uses its own wastelandz login.
  • Remove test database and access to it?Y — the default test database is open to everyone.
  • Reload privilege tables now?Y — applies it all right away.
Safe to say yes

None of these touch the wastelandz database or login you create in the next step — they only strip out MySQL’s insecure defaults. Answering Y to all four is the right call for a dedicated server.

4

Create the database

Open MySQL as admin:

sudo mysql

At the mysql> prompt, paste these four lines, then type exit:

CREATE DATABASE wastelandz;
CREATE USER 'wastelandz'@'localhost' IDENTIFIED BY 'your_password_here';
GRANT ALL PRIVILEGES ON wastelandz.* TO 'wastelandz'@'localhost';
FLUSH PRIVILEGES;
Pick a password

Replace your_password_here with one you choose and write it down — you need it in Step 6. Because you enabled MySQL’s password policy in Step 3, it must be 8+ characters with an upper- and lower-case letter, a number, and a symbol (e.g. Wast3land_Z!) — a weaker one fails with ERROR 1819.

5

Set up the gateway

Copy the gateway out of the kit into its own folder (the running copy stays separate, so kit updates never touch your live settings), then build the database tables and install its Python parts:

# the running copy gets its own folder
sudo mkdir -p /opt/wastelandz-gateway
sudo chown $USER /opt/wastelandz-gateway
cp -r /opt/wastelandz/gateway/. /opt/wastelandz-gateway/

# check the login works (also builds the tables; the gateway would do
# this itself on first start, but a wrong password is easier to spot here)
cd /opt/wastelandz-gateway
mysql -u wastelandz -p wastelandz < setup_database.sql

# create an isolated Python environment and install the gateway into it
sudo apt install -y python3-venv
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# give the whole folder (incl. the venv) to the account the service will run as
sudo chown -R youruser:youruser /opt/wastelandz-gateway

The mysql line asks for the wastelandz password you set in Step 4, then builds the tables — no output means it worked. The script itself holds no usernames or passwords; you never edit it.

That mysql line is a check, not a requirement

The gateway applies setup_database.sql itself on every start, so the tables would appear on their own the first time you launch it. It is here because a wrong password is far easier to understand on its own line than buried in the gateway’s startup output.

Running it yourself and letting the gateway run it are the same thing — the file is written so that running it again changes nothing, and so it can only ever add. A fresh install and a five-year-old one end up with the same database because both run the same file. There is no first-time-only path to get wrong.

Ownership — replace youruser

Change both youruser parts to your Linux username and group (it’s username:group). This must be the same user you’ll put in the service’s User= line in Part 2.

Not sure what yours are? Run these two:

whoami        # prints your username       (e.g. motavar)
id -gn        # prints your primary group  (e.g. motavar)

On Ubuntu/Debian your primary group is the same as your username, so the two are almost always identical. For a user named motavar the command is:

sudo chown -R motavar:motavar /opt/wastelandz-gateway

Shortcut: you can type $USER:$USER instead and the shell fills in your username automatically — the same $USER the earlier chown steps use.

Why it matters: if you created the venv while in a root shell (e.g. after a sudo -i or a sudo mysql session), the files end up owned by root, and the gateway service — which runs as your user — can’t read or run them. The chown -R above fixes that no matter who created the files, so it’s safe to always run.

Why the virtual environment (venv)?

Modern Ubuntu (24.04 / Python 3.12) blocks system-wide pip install to protect the OS Python — you’d hit error: externally-managed-environment. A venv is an isolated Python just for the gateway: the clean, recommended way. Your prompt shows (venv) once it’s active, and that’s why Step 7 and the service below run venv/bin/python.

No-venv option (older systems)

On systems without that restriction (older Ubuntu/Debian), you can skip the venv and install into system Python — then Step 7 and the service use plain python3 instead of venv/bin/python:

pip3 install -r requirements.txt
# to force it on a PEP-668 system instead of using a venv (not recommended):
pip3 install -r requirements.txt --break-system-packages
6

Enter your settings

First generate the gateway key — a unique API key (use a different one for every server). Copy the 64-character line it prints:

python3 -c "import secrets; print(secrets.token_hex(32))"

Now copy the template to the real settings file and open it in nano:

cp config.example.py config.py
nano config.py

Set DB_PASSWORD to your Step 4 password, and set api_key — the GATEWAY KEY, shown in the file as CHANGE_ME_UNIQUE_KEY_1 — to the key you just generated. Each server needs its own unique gateway key. Leave host as 127.0.0.1 and port as 5000. Save in nano: Ctrl+O, Enter, Ctrl+X.

Keep it private

config.py holds your password and key. Never share it or upload it anywhere public.

7

Test that it runs

venv/bin/python gateway.py

This runs the gateway in the foreground, so it holds this terminal — that’s normal (it’s a server, meant to keep running). Look for Database connection: OK in its output — that line is your success check. Then press Ctrl+C to stop it; Part 2 below runs it properly on its own. (venv/bin/python works whether or not the venv is “activated”; a no-venv install uses plain python3 gateway.py.)

Optional — curl check (needs a second terminal)

Because the gateway holds this terminal, the curl check has to run from a separate SSH session while the gateway is still running. Or just skip it — once it’s a service (Part 2) it runs in the background and curl works from any terminal:

curl http://127.0.0.1:5000/api/ping
8

Firewall

Gateway on the same box as the game server (normal) — nothing to do. Local traffic is already allowed.

Only for a remote server or bot, open port 5000 to that one address:

sudo ufw allow from THE.OTHER.IP.ADDRESS to any port 5000 proto tcp
Two hard rules

Never run ufw allow 5000 on its own — that opens it to the whole internet. And leave MySQL’s port 3306 closed; it only needs to be reached locally.

If you turned ufw on, also open the game’s own ports so players can join: sudo ufw allow 2001/udp and sudo ufw allow 17777/udp. (Ubuntu ships with ufw off — then there’s nothing to do.)

Ports at a glance (per server — all UDP)
  • Open to the internet: 2001 (game — players join) & 17777 (A2S / Steam query — this is the Steam port; it’s what lists your server in the browser). Server 2 = 2002/17778, etc.
  • Optional: 19999 (RCON remote admin) — only if you use it, and restrict it to your admin IP; never open it wide.
  • Keep local, never public: 5000 (gateway — open only to a remote bot’s IP if off-box) & 3306 (MySQL).

No separate “Steam” ports are needed — Reforger isn’t a Source-engine server (no 27015 etc.), and SteamCMD downloads are outbound (nothing to forward).

9

Connect your game server — then first start

Start the game server once and let it boot — it downloads the Wasteland-Z mod from the Workshop and writes its starter config files. This is the command (one long line — same program, its config, its profile folder, the shared mods folder):

cd /opt/reforger/server
./ArmaReforgerServer -config /opt/reforger/configs/server1.json -profile /opt/reforger/profiles/server1 -addonDownloadDir /opt/reforger/workshop -maxFPS 60

When it settles, press Ctrl+C to stop it. Wasteland-Z has now written its starter config files into /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/.

You’re running on defaults

Wasteland-Z ships with working defaults for everything — loot, economy, missions, weapons, vehicles. To go live you only need to set two things: the gateway URL and its key (steps a & b below). Everything else is optional tuning you can do later.

a Gateway URL — already set for a local gateway

The mod defaults GATEWAY_URL to http://127.0.0.1:5000/, which is correct when the gateway is on the same box (the normal setup) — so you can skip this. Only edit it if your gateway runs on a different machine or port:

nano /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_server.conf

Set the GATEWAY_URL line to your gateway’s address (keep the trailing slash), then save: Ctrl+O, Enter, Ctrl+X.

b Give it the gateway key

Open the secrets file:

nano /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_secrets.conf

Add a line — API_KEY then a space then the same gateway key from Step 6 (the one in config.py for this server). Save and exit the same way. A ready-made template ships at /opt/wastelandz/configs/HFWastelandZ_secrets.conf.example.

c Add the mission reward templates
cp -r /opt/wastelandz/missions/rewards /opt/reforger/profiles/server1/profile/hf_wastelandz/missions/

These are the loot crates and vehicles missions hand out. (Missions themselves are built in-game — see missions/README.md.)

Now start it again (same command). Player data saves to the database — you’re live.

+ Two optional asides (not required to go live)
Server name & admin password

Those aren’t HF settings — they live in the Reforger config server1.json (the name and passwordAdmin fields). Change them any time:

nano /opt/reforger/configs/server1.json
Optional — tune loot / economy later

To customize loot/economy/etc., rename the kit’s /opt/wastelandz/configs/*.example files (drop .example) and copy them into the profile’s hf_wastelandz/configs/ folder. configs/README.md documents each one — none of it is needed to go live.

d Make yourself the admin

Do this once it’s running. Wasteland-Z admins are keyed by Bohemia Identity GUID — a lowercase xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx code tied to your Bohemia account. It is NOT your Steam number. There are two ways to find yours:

1) In the game (easiest): launch Arma Reforger, click your profile in the upper-right corner — your Identity GUID is listed there. Copy it.

2) From the server log: join the running server once, then read it out of the log:

grep -rh "Authenticated player" /opt/reforger/profiles/server1/logs/ | tail

Copy the identityId= value on the line with your player name. (After Part 2 makes it a service, you can use sudo journalctl -u wz-server1 | grep "Authenticated player" instead.)

Add yourself as OWNER in the admin file:

nano /opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_admins.conf
OWNER a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d   # ← your GUID from the log, not this placeholder
⚠ No trailing spaces or Windows line-endings in this file

Wasteland-Z matches admins by exact ID text. A stray trailing space after the GUID, or Windows line-endings (from editing or pasting on Windows), are invisible in most editors but make the ID silently fail to match — you stay a regular player, with no error shown. After saving, strip any hidden characters and verify:

cd /opt/reforger/profiles/server1/profile/hf_wastelandz/configs
sed -i 's/[[:space:]]*$//' HFWastelandZ_admins.conf
cat -A HFWastelandZ_admins.conf

In the cat -A output your OWNER line must end in just $no ^M and no space before it. Why: RESOLVED July 5, 2026 — the mod’s config parser now strips trailing whitespace and Windows line-endings (^M) at the read boundary, so your ID matches even with hidden characters. This sed line is kept as belt-and-suspenders (and is still needed on older mod builds) — it never hurts to run. Restart the server after editing — then F8 opens the admin menu and /zeus gives you Game Master.

Next: make it run on its own

Right now you’re starting the game server by hand to confirm it works. Part 2 → Step B turns it into a background service (wz-server1) that starts on boot and restarts itself if it crashes — do that once you’ve seen it boot and connect to the gateway.


Part 2

Run · monitor · restart

Run the gateway as a service so it starts on its own and survives reboots. Built-in Linux tools only.

A

Make it a service (once)

Create the service file:

sudo nano /etc/systemd/system/wz-gateway.service

Paste this in (change youruser to your Linux username), then save and exit — in nano: Ctrl+O, Enter, then Ctrl+X:

[Unit]
Description=WastelandZ Gateway
After=network.target mysql.service

[Service]
WorkingDirectory=/opt/wastelandz-gateway
ExecStart=/opt/wastelandz-gateway/venv/bin/python /opt/wastelandz-gateway/gateway.py
Restart=on-failure
User=youruser

[Install]
WantedBy=multi-user.target

That ExecStart path is the venv — pointing systemd straight at venv/bin/python runs the gateway inside the venv automatically; you do not activate it for a service. (A no-venv install uses /usr/bin/python3 instead.) Set User= to your Linux username, and make sure that user owns /opt/wastelandz-gateway (the chown -R from Step 5). Then turn it on — this starts it now and on every reboot:

sudo systemctl daemon-reload
sudo systemctl enable --now wz-gateway

Don’t want to type it? The kit ships this file ready-made: copy /opt/wastelandz/server/wz-gateway.service.example to /etc/systemd/system/wz-gateway.service and just change youruser.

B

Make the game server a service

Same idea as Step A, but for the game server. (You did its one-time first boot back in Part 1, Step 9 — that generated its config files; this step makes it run permanently in the background.) First make sure your user owns what the server writes to (its profiles/ and logs/) — needed if you ran any of the install as root:

sudo chown -R youruser:youruser /opt/reforger

Then drop in the ready-made service file and set your username:

sudo cp /opt/wastelandz/server/wz-server1.service.example /etc/systemd/system/wz-server1.service
sudo nano /etc/systemd/system/wz-server1.service   # change User=youruser to YOUR username (save: Ctrl+O, Enter, Ctrl+X)

Now check it before you start it. These two commands catch the two mistakes that account for nearly every “my service won’t start” report (updated for 1.8 — 2026-08-17):

sudo systemd-analyze verify /etc/systemd/system/wz-server1.service   # must print NOTHING
grep -n "User" /etc/systemd/system/wz-server1.service              # must NOT still say youruser

If verify prints Missing '=', ignoring line a long line got split when you pasted — almost always ExecStart, which must be one unbroken line. Your editor wrapping it on screen is fine; a real newline in the middle is not. This one is nasty because the server still starts — systemd keeps the first half of the line and silently discards the rest, so you lose -maxFPS and -addonDownloadDir without any error.

If it still says youruser the server will fail with status=217/USER. verify does not catch this — the syntax is valid, the user just doesn’t exist.

Both clean? Start it:

sudo systemctl daemon-reload
sudo systemctl enable --now wz-server1
sudo systemctl status wz-server1                   # look for "active (running)"

The game server now starts on boot, updates the game before every start, and restarts itself if it crashes.

Any time you edit a unit file afterwards: run daemon-reload before restarting. Without it systemd re-runs the version it already had cached and your edit appears to have done nothing. And if a service has failed repeatedly, systemd locks it out with start-limit-hit — fix the cause, then clear the lockout with sudo systemctl reset-failed wz-server1 before starting again.

Your three services: wz-server1 (the game server), wz-gateway (the gateway), and mysql (the database). Every command below works on any of them — just swap the name. After a reboot they start on their own; if you ever start them by hand, the order is mysqlwz-gatewaywz-server1.

Is it running?

sudo systemctl status wz-server1

Green active (running) = good. It opens a scrollable view — press q to exit back to the prompt.

Restart / stop / start

sudo systemctl restart wz-gateway
sudo systemctl stop wz-gateway
sudo systemctl start wz-gateway

Watch live logs (monitor)

journalctl -u wz-gateway -f

Ctrl+C stops watching (the service keeps running).

Is it alive?

curl http://127.0.0.1:5000/api/ping

Restart MySQL

sudo systemctl restart mysql

Start on boot (on / off)

sudo systemctl enable wz-server1
sudo systemctl disable wz-server1

Your earlier enable --now already turned both on — they auto-start after a reboot.

Back up your data (do this regularly)

mysqldump -u wastelandz -p wastelandz > wastelandz_backup_$(date +%F).sql

That file is your whole server’s save — keep copies somewhere safe.


Part 3

Two or three servers on one box

One database, one gateway, several game servers — a hive. Players keep the same money, bank and gear on every server; each server keeps its own world (positions, placed objects, map). The Windows tab’s “Part 4” ports table applies here too: game 2001/2002/2003, browser 17777/17778/17779, gateway 5000/5001/5002.

The finished two-server setup, and how the pieces talk:

players → UDP 2001/17777 → wz-server1 (Everon)  → http://127.0.0.1:5000 + key #1 ─┐
players → UDP 2002/17778 → wz-server2 (Arland)  → http://127.0.0.1:5001 + key #2 ─┤
                                                                              └→ wz-gateway  mysql
The gateway tells the servers apart by the PORT each one calls (5000 vs 5001)
and proves it with that server's key. Both ports are ONE gateway process,
and everything right of the players is local to the box — nothing exposed.

You install the game once. /opt/reforger/server/ and the shared workshop/ mods folder serve every server. Each extra server is just a config file + profile folder + systemd unit with different numbers. To add server 2:

# its own config — new ports, its own map, its own name.
# The kit ships one pre-made (ports 2002/17778 + Arland already set):
cp /opt/wastelandz/server/server2.json.example /opt/reforger/configs/server2.json
nano /opt/reforger/configs/server2.json

In server2.json: set your server name + admin password (ports 2002/17778 and the Arland map are pre-set). Save and exit nano: Ctrl+O, Enter, Ctrl+X. The map is the scenarioId line — Wasteland-Z runs on top of any Game Master scenario, so a two-server hive can offer two different worlds:

# server1.json — Everon (the big island):
"scenarioId": "{59AD59368755F41A}Missions/21_GM_Eden.conf"

# server2.json — Arland (the smaller southern island):
"scenarioId": "{2BBBE828037C6F4B}Missions/22_GM_Arland.conf"

Any other map — including community maps — works the same way. Find its ID on the Workshop website: open the map mod’s page at reforger.armaplatform.com/workshop and click its Scenarios tab — the ScenarioId shown there (it looks like {GUID}Missions/Name.conf) is what goes in scenarioId. Add that map mod to the same server’s mods list too.

Each server starts with the same program, pointed at its own config + profile (the profile folder is created on first start):

# server 1 — Everon
/opt/reforger/server/ArmaReforgerServer -config /opt/reforger/configs/server1.json -profile /opt/reforger/profiles/server1 -addonDownloadDir /opt/reforger/workshop -maxFPS 60

# server 2 — Arland
/opt/reforger/server/ArmaReforgerServer -config /opt/reforger/configs/server2.json -profile /opt/reforger/profiles/server2 -addonDownloadDir /opt/reforger/workshop -maxFPS 60

Then wire server 2 into the hive exactly like Step 9 did for server 1:

  • Gateway: in config.py, un-comment the server-2 line in SERVERS and give it its own random api_key (Step 6 command), then sudo systemctl restart wz-gateway — it now listens on 5000 and 5001.
  • Wire it: in /opt/reforger/profiles/server2/profile/hf_wastelandz/configs/ (created on server 2’s first start), set GATEWAY_URL http://127.0.0.1:5001/ and put server 2’s key in its HFWastelandZ_secrets.conf.
  • Service: copy the kit’s /opt/wastelandz/server/wz-server2.service.example to /etc/systemd/system/wz-server2.service, change youruser, then sudo systemctl daemon-reload and sudo systemctl enable --now wz-server2. If ufw is on, open UDP 2002 + 17778.
Each server has its OWN Wasteland-Z config — edit the right copy

This is the step people get wrong. Wasteland-Z keeps a separate set of config files per server, inside that server’s own profile folder. They are created on that server’s first start, so boot server 2 once before you look for them:

/opt/reforger/profiles/server1/profile/hf_wastelandz/configs/HFWastelandZ_server.conf
/opt/reforger/profiles/server2/profile/hf_wastelandz/configs/HFWastelandZ_server.conf

Editing server 1’s copy by mistake is the classic error — it makes server 1 start writing its data as server 2. Three settings decide whether server 2 works:

1. GATEWAY_URL — in HFWastelandZ_server.conf. The port is how the gateway knows which server is calling. Server 1 uses 5000, server 2 uses 5001. Trailing slash required.

# server 2's HFWastelandZ_server.conf
GATEWAY_URL http://127.0.0.1:5001/

2. SERVER_ID — same file, and it must differ from server 1’s. Money, bank and inventory are shared hive-wide on purpose; position, placements and money drops are split by SERVER_ID. Give both servers the same ID and players get the wrong spawn location and kept gear when they hop between them.

3. API_KEY — in HFWastelandZ_secrets.conf, in that same folder. It must match the api_key you set for that server in the gateway’s config.py.

Check all three across both servers in one go — the -H prints which file each line came from:

grep -H GATEWAY_URL /opt/reforger/profiles/server*/profile/hf_wastelandz/configs/HFWastelandZ_server.conf
grep -H SERVER_ID  /opt/reforger/profiles/server*/profile/hf_wastelandz/configs/HFWastelandZ_server.conf
grep -H API_KEY    /opt/reforger/profiles/server*/profile/hf_wastelandz/configs/HFWastelandZ_secrets.conf

You want server1 on 5000, server2 on 5001, and two different SERVER_ID values. Then restart server 2 so it picks the changes up:

sudo systemctl restart wz-server2
One key per server

Each server gets its own gateway port and its own random api_key — that’s how the gateway tells them apart. Never reuse a key across servers.

Will the box handle it?

Each Reforger server is heavy — roughly a few CPU cores and 4–8 GB RAM per server under load. Two servers want a strong 8-core machine; three want more. The gateway and MySQL are lightweight by comparison.

Start / stop / restart anything — one pattern

Every piece is a service with a name — wz-server1, wz-server2, wz-gateway, mysql — and one command pattern drives them all. Learn it once with server 1:

sudo systemctl start   wz-server1    # start it
sudo systemctl stop    wz-server1    # stop it (players get dropped)
sudo systemctl restart wz-server1    # stop + start in one go
sudo systemctl status  wz-server1    # running or not? (green "active" = good)
journalctl -u wz-server1 -f          # watch its live log — Ctrl+C stops watching, not the server

Swap the name for any other piece: bounce only server 2 = sudo systemctl restart wz-server2; stop the gateway = sudo systemctl stop wz-gateway; restart the database = sudo systemctl restart mysql. Each piece is independent — players on server 1 keep playing while you bounce server 2.

Order? After a full stop (or a reboot where something didn’t come up): mysql first, then wz-gateway, then the game servers. A briefly-down gateway doesn’t crash the game servers — saves pause and Wasteland-Z resyncs every player automatically when it comes back — but don’t leave it down long.

Big box? Give each server its own cores

RAM first — there’s nothing to divide. Linux hands out memory on demand: a full 128-player Reforger server uses roughly 8–16 GB, MySQL + the gateway a few more. Even three servers fit comfortably in 64 GB. The only setting worth adding is a per-server safety cap (below) so a memory leak can never take down the whole box.

CPU is the part worth splitting. A big AMD chip (like a 16-core Ryzen 7950X3D) is really two groups of 8 cores glued together. Each group shares one pot of very fast memory called cache — the chip’s short-term memory. A game server runs best when its group belongs to it alone; two servers squeezed into one group fight over the pot and both get slower. The fix is one line per server that says “you may only use these cores” — one server per group.

1 See your two groups

Run (looks, changes nothing):

lscpu -e

One row per CPU number. Look at the last digit on each row: rows ending in 0 are group one, rows ending in 1 are group two. On a 16-core AMD chip you’ll typically see: CPU numbers 0–7 and 16–23 end in 0 (group one), 8–15 and 24–31 end in 1 (group two). (Why 32 CPU numbers on a 16-core chip? Each core can run two things at once, so it shows up twice.)

On an “X3D” chip, one group has a much bigger memory pot — the fast one. See which:

cat /sys/devices/system/cpu/cpu0/cache/index3/size /sys/devices/system/cpu/cpu8/cache/index3/size
98304K   ← 96 MB = the BIG pot. First line is cpu0, so group one has it.
32768K   ← 32 MB = the normal pot (cpu8’s group — group two).

Give the big-pot group to your busiest server. If the 96 MB shows up on the second line instead, swap the two CPUAffinity lines in Step 2. Not an X3D chip? Both lines read the same — the split still works, there’s just no favourite group.

2 Pin each server to its group

Edit each server’s service file (the kit’s .service.example already ships these two lines commented out — you just delete the leading #):

sudo nano /etc/systemd/system/wz-server1.service   # then repeat for wz-server2.service

In the [Service] section of each file, uncomment (or add) its own pair of lines — wz-server1.service gets group one, wz-server2.service gets group two (they are separate files; never put both pairs in one). Save each (Ctrl+O, Enter, Ctrl+X):

# ── these two lines go in wz-server1.service (group one — the big-pot group) ──
CPUAffinity=0-7 16-23
MemoryMax=32G

# ── and these two go in the SEPARATE file wz-server2.service (group two) ──
CPUAffinity=8-15 24-31
MemoryMax=32G
3 Apply
sudo systemctl daemon-reload
sudo systemctl restart wz-server1 wz-server2

CPUAffinity = “this service may only run on these cores.” MemoryMax = if the server ever leaks past the cap, systemd kills it (and Restart=on-failure brings it right back) instead of the box dying. Leave the gateway and mysql unpinned — they’re lightweight and slot into whatever’s idle. Verify with htop: each server’s load stays inside its own core block. One core pegged at 100% while its neighbours idle is the engine’s normal single-thread ceiling, not a problem to fix — the levers for a struggling full server are aiLimit in serverN.json and Wasteland-Z’s mission/AI density settings.

Cheat-sheet: 1–4 servers on a 16-core box (two 8-core groups; CPU numbers per the usual AMD layout — verify yours with lscpu -e, Step 1):

SetupServer & playersCPUAffinity=MemoryMax=
1 server (up to 128)wz-server1 · 128none needed (whole chip; or 0-7 16-23 for the big-cache group)32G
2 servers (2×128)wz-server1 · 1280-7 16-2332G
wz-server2 · 1288-15 24-3132G
3 servers (2×128 + 1×64)wz-server1 · 1280-7 16-2332G
wz-server2 · 1288-15 24-3132G
wz-server3 · 64unpinned (scheduler fills the gaps)24G
4 servers (4×64 or 4×32)wz-server1 · 640-3 16-1924G
wz-server2 · 644-7 20-2324G
wz-server3 · 648-11 24-2724G
wz-server4 · 6412-15 28-3124G

The logic: 1–2 servers → a whole group each (max cache per server). 4 servers → half a group (4 cores) each — plenty at 32–64 players, and no server ever spans both groups. 3 servers is the awkward count: keep the two big ones on their own groups and let the third float. Player caps live in each serverN.json (maxPlayers); what actually loads a core is AI + player count together, so watch htop on busy nights and adjust aiLimit before touching the core map. RAM: even the 4-server row uses under half of a 128 GB box.

Different CPU? Step 1 tells you the truth about any chip. If every row’s last digit is 0, your chip is one group (most older Intels, e.g. an i9-9900K): there’s no favourite to pick — split the cores in half (first half + their twins to server 1, the rest to server 2) and expect lower comfortable player caps on older chips. Newer Intels (12th gen up, e.g. an i7-13700K) mix fast P-cores and small E-cores — in lscpu -e the P-cores appear twice (they have twins), E-cores once. Pin game servers to P-cores only and leave the E-cores unpinned — they carry the gateway, MySQL and the system.

Troubleshooting

  • “Database connection failed”DB_PASSWORD in config.py doesn’t match Step 4, or MySQL is stopped (sudo systemctl status mysql).
  • Service won’t start — run journalctl -u wz-gateway -e to see why; usually a wrong path or username in the service file.
  • status=217/USER — the service file still says User=youruser, or names a user that doesn’t exist. Fix with grep -n "User" /etc/systemd/system/wz-server1.service. Note systemd-analyze verify does not catch this — the syntax is valid, the user simply isn’t real.
  • Missing '=', ignoring line — a long line got split when you pasted it, almost always ExecStart, which must be one unbroken line. systemd keeps the first half and silently discards the rest, so the server still starts but loses -maxFPS and -addonDownloadDir with no error at all. Rejoin the line, then daemon-reload and restart.
  • start-limit-hit / “Start request repeated too quickly” — the service failed enough times that systemd stopped retrying. Fix the underlying cause first, then clear the lockout: sudo systemctl reset-failed wz-server1.
  • Your edit to a service file seems to do nothing — you restarted without reloading. systemd caches unit files; always sudo systemctl daemon-reload before restart.
  • Server won’t boot: Failed to fetch addon details from workshop API — one mod in your mods list has a new version the Workshop won’t serve, and the server refuses to start rather than use the copy it already has. The line just above the error names the culprit (Addon Download started <id> - <name>). Remove that mod’s block from mods and restart. If another mod depends on it, the engine still loads the working copy from disk, so you usually lose nothing. Put it back once the Workshop is serving it again. Note that modsRequiredByDefault: false does not rescue this case — that covers a failed download, whereas this fails earlier, at the version lookup, and kills the whole boot.
  • Port 5000 in use — change port in config.py and update GATEWAY_URL.
  • Money/gear not savingGATEWAY_URL or api_key mismatch between the game server and config.py.
  • Last location & kept gear don’t restore on relog (but money & inventory DO) — the game server’s SERVER_ID (in HFWastelandZ_server.conf) doesn’t match the server_id the gateway uses for it (its SERVERS entry / SERVER_ID). Money/inventory are hive-wide so they still work; only per-server data (position, kept-on-relog gear) needs the ids to match. Set SERVER_ID to the gateway’s id and restart. The game log prints SERVER_ID MISMATCH with both values when it detects this — grep -i "SERVER_ID MISMATCH" your log.

Add, remove & track mods

Same on Windows and Linux. There is no installer — the mods list inside your server config (server1.json) is the mod manager. On every start the server reads that list, auto-downloads anything missing from the Reforger Workshop into your workshop folder, and loads exactly what’s listed — nothing else.

Add a mod

Find it on the official Workshop site: reforger.armaplatform.com/workshop. The mod’s ID is right in its page URL — the 16 characters before the name, e.g. …/workshop/68A616565DECAB19-wasteland-z. Add one block to the mods list and restart the server:

Wasteland-Z is live on the Workshop

The kit already pre-lists it in every mods block, so you don’t need to add it by hand — but here’s its page: reforger.armaplatform.com/workshop/68A616565DECAB19 (mod ID 68A616565DECAB19).

"mods": [
    {
        "modId": "68A616565DECAB19",
        "name": "Wasteland-Z"
    },
    {
        "modId": "ABCDEF1234567890",
        "name": "Cool Helmet Pack"
    },
    {
        "modId": "0123456789ABCDEF",
        "name": "Winter Uniforms"
    }
]

The name is your own label — write whatever helps you remember what it is. The commas are the one gotcha: every mod’s closing } gets a comma except the last one — and never a semicolon, JSON doesn’t use them. So when you add a mod at the end: the block that used to be last gains a comma, the new last block has none.

Remove a mod — including a problem mod

Delete its block from the list, restart. That’s the whole procedure — the server only loads what’s listed. The mod’s downloaded files stay in the workshop folder but are completely inert; if you want the disk space back, delete the folder named with that mod’s ID inside the workshop folder.

Can’t I just comment a mod out?

Not in this file. server1.json is JSON, and JSON forbids comments — one // line and the server refuses the whole config. So in the json, deleting the block is the only off switch. The convenient middle ground is a parking file: keep a plain text file next to your configs that nothing reads, and move benched mod blocks there instead of losing them:

mods-parked.txt — your parking lot; the server never reads this file.
Copy a block back into server1.json's "mods" list to re-enable it.

{ "modId": "ABCDEF1234567890", "name": "Cool Helmet Pack" },     broke after 1.7 update, retry later
{ "modId": "0123456789ABCDEF", "name": "Winter Uniforms" },      summer season — bring back in December
{ "modId": "FEDCBA9876543210", "name": "Extra Car Pack" },       testing caused rubber-banding

(Wasteland-Z’s own .conf files are different — those are KEY VALUE format and take # comments freely. Only the game server’s .json is strict.)

Track your mods

The mods list is the single source of truth — one block per mod, nothing can load that isn’t in it. Running several servers? Each serverN.json has its own list, so server 1 and server 2 can run different mod sets. To see what a server actually loaded, its boot log prints every addon as it starts.

One game mode at a time

Wasteland-Z is a game mode — it owns the gameplay. Content mods (weapons, clothing, vehicles, buildings) sit alongside it fine; adding another game-mode mod makes the two fight. If a mod’s Workshop page says “game mode,” it doesn’t belong on a Wasteland-Z server.

Server broke after adding a mod — or after a game update?

Remove the last mod you added, restart. If it broke right after a Reforger update instead: mod authors need a few days to catch up with game patches — remove your extra mods, restart, then add them back one at a time as they’re updated.

Players, roles & reserved slots

This part is the same on Windows and Linux — it’s done with config files and in-game commands.

The five roles

Every player has a role. Higher beats lower. Roles live in one file: $profile:HFWastelandZ_admins.conf, keyed by Bohemia Identity GUID (a lowercase xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx code — not your Steam number; see Part 1 → Step 9d for how to find yours).

RoleCan do
OWNEREverything, including /admin restart. Set by editing the file only.
ADMINGame Master (Zeus), promote/demote others, save, all mission & spot editing.
MODKick, ban, mute, teleport, heal, god, announce.
TRUSTEDDonor / verified helper (reserved for future perks).
EVERYONEDefault — every normal player. No line needed.

Make someone a mod, admin, or owner

Your first admin — and the only way to set OWNER — is by editing the file. Add a line and restart:

# in $profile:HFWastelandZ_admins.conf
OWNER a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
ADMIN b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e
MOD   c3d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f

Those are Bohemia Identity GUIDs (lowercase 8-4-4-4-12 hex), one per admin — find yours via Part 1 → Step 9d. Put no trailing space after the GUID (see the gotcha in Step 9d). Anyone not listed is EVERYONE.

After you have one admin, change roles live, no restart, from in-game chat:

/admin promote <name|playerId> <EVERYONE|TRUSTED|MOD|ADMIN>
The rules

You must be ADMIN or OWNER to change roles. An ADMIN can grant any role up to and including ADMIN. OWNER is never granted in-game — it’s file-only so no one can lock you out, so seed yourself as OWNER in the file first. You can change anyone at or below your own rank (an ADMIN can demote a peer ADMIN), but never someone above you — and an OWNER can never be changed in-game by anyone.

You can also change roles in the game — open the F8 admin menu → Players tab. The right-hand STAFF list shows everyone with a role (including offline people); click a player or staff row, then a role button (twice to confirm).

See who has what role

/admin who

Lists everyone online with their role. Any MOD or higher can run it.

Change what each command needs (command tiers)

Every command has a minimum role. To run it, your role must be equal or higher. Change any command’s required role in $profile:HFWastelandZ_command_tiers.conf:

# format:  <command>.<subcommand> = <ROLE>
admin.god  = OWNER     # only owner can use god mode
admin.kick = ADMIN     # mods can no longer kick

So admins.conf sets what role each player has, and command_tiers.conf sets what role each command needs. Restart to apply changes.

Reserved login slots (staff always get in)

Keeps a couple of slots open so your staff can always join a full server, without permanently wasting seats. Set these in $profile:HFWastelandZ_server.conf:

SettingDefaultWhat it does
RESERVED_SLOTS_ENABLED0Master on/off. Set to 1 to turn it on.
RESERVED_BUFFER2Slots always kept free so staff join instantly.
RESERVED_HIGHRISK_COUNT8How many newest regular players are “bumpable.”
RESERVED_MIN_TIER20Lowest role treated as staff (20 = MOD).
RESERVED_TOTAL_SLOTS0Leave 0 on a real server (read automatically). Only set on a Workbench test host.

Setup: in the game’s config.json, set game.maxPlayers to your playable count plus the buffer, and set RESERVED_TOTAL_SLOTS to that same number. Example: 128 players → maxPlayers 130, RESERVED_BUFFER 2. ADMIN/OWNER always get in; MOD only bumps regular players.

How to see it working

It runs on the server, so watch the server console / log — set HF_DEBUG_ADMIN 1 in server.conf and you’ll see lines like ReservedSlots: ON total=130 buffer=2… and BUMP… when it acts. Players in a bumpable slot also see a “high-risk” banner in their HUD.

Wasteland-Z · Saving & sharing

Hives.

Your players' money, bank and gear get saved on your server, not on their PCs. A hive is where that save lives. This page covers what a hive is, what it shares, and what to watch out for.

The short version

  • A hive is where your players' money, bank and gear are saved. It is basically their account.
  • Every server you point at the same hive shares all of it. A player hops between them and nothing changes.
  • A server in a different hive is a different account. Nothing crosses between them.

The picture

One hive, however many maps and servers you run. The lines show where each map saves your players' stuff.

Maps in your rotation Everon Arland Any other map Buddy’s Map YOUR HIVE one save for everything MONEY · BANK · GEAR all together, on this release every map above shares all of it BUDDY’S HIVE his money, bank and gear a separate save yours is untouched
  • Every map pointed at your hive shares one save — money, bank and gear all together.
  • A map pointed at somebody else's hive uses their save. Yours sits untouched until you rotate back.
⚠ One hive means one shared wardrobe as well as one bank

On this release there is no way to share money but keep gear separate — a hive shares both. So every server in a hive should run the same mods. Gear is saved as a list of item references, and an item whose mod is missing on the destination cannot be restored there.

Hives

One gateway, one database, several hives

Two settings decide what your players carry between servers, and they are not the same thing. Getting them the wrong way round is how somebody opens an empty bank.

SettingControlsScope
Hive (HIVE_ID)Money, bank and gear. Everything a player owns.Every server in that hive, on this release.

A single gateway serves several hives at once from one database. Each server entry names its own HIVE_ID, so a hosting provider — or a few friends splitting a box — can run completely separate economies side by side. Servers that omit it join the gateway default, so nothing changes for a single-hive setup.

One hive means one shared wardrobe as well as one bank

On this release there is no way to share money but keep gear separate — a hive shares both. So every server in a hive should run the same mod set. Gear is stored as item references, and an item whose mod is missing on the destination cannot be restored.

Separating the two — one bank, several gear pools — is what the next release adds. See the Beta channel if you want to read ahead; do not run it on a live server.

Per-map hive moves the bank too

A map can point at a different hive entirely — its own gateway URL, port and password, which may well be a friend’s server. Rotate to that map and you are in their economy: their bank, their gear.

That is a real and useful thing to do deliberately. It is not a way to vary loadouts per map, because a different hive means a different wallet and bank, and a player who rotates in and opens an empty ATM will report it as data loss. It is not — their money is sitting safely in the other hive — but you will spend the evening explaining that.

Use per-map hive for a test hive, a deliberately separate ruleset, or a shared event with another community.

What you can build with both
  • Maps that run completely alone — group PRIVATE, nothing shared.
  • Maps that share gear with some of your servers and not others.
  • Maps that join an entirely different hive for the duration.
  • Garage on for one map, off for another, with its own sharing group.

All of it set per map, so a rotation changes the rules with the world.

Splitting gear from money is coming

On this release a hive shares both the bank and the wardrobe — there is no way to have one without the other. The next release adds gear groups, which split gear into separate pools inside one hive, so servers can share an economy while running different mod sets.

Switch to the Beta channel above to read how that works. Do not run it on a live server.

Wasteland-Z · Saving & sharing

Hives & gear groups.

Your players' money and gear get saved on your server, not on their PCs. This covers where they get saved, what a player keeps when they change map or server, and how to set it up.

Hives

Hives & gear groups

Your players' money and gear get saved on your server, not on their PCs. These settings decide who shares what.

The short version

  • A hive is where money and banks are saved. It is basically the account.
  • A gear group is where loadouts are saved. One hive can hold as many gear groups as you want.
  • Every map has its own settings file, and that file picks which hive and which gear group the map uses.

A gear group is what the settings file calls GEAR_SHARE_GROUP. You will see it called a realm too. Same thing.

The picture

One server, four maps in the rotation. The lines show where each map saves your players' stuff.

Maps in your rotation Everon Arland Saturday Event Buddy’s Map YOUR HIVE your money + your bank ALPHA your gear shared by both maps ZULU event gear everybody starts empty BUDDY’S HIVE his money + his bank HIS GROUP his gear
  • Everon and Arland both point at ALPHA, so a loadout follows the player between them.
  • Saturday Event points at ZULU. Everybody starts that night with nothing, and their normal gear is sitting untouched in ALPHA waiting for Sunday.
  • All three are inside your hive, so money never changes no matter which map is up.
  • Buddy's Map points at a whole different hive. Over there they use his money and his gear.
Why you would want this

Monday you join a friend's hive and play his maps. Tuesday you are back on your own rotation with your own economy. Saturday is an event — different map, fresh gear, everybody starting level. Sunday you are back to normal. Next month it is a different event with a different set of friends.

That is all one mod and one server. Every map already carries its own answer, so nothing gets reconfigured between rotations.

⚠ A different hive means a different wallet AND bank

Not just different gear. Rotating onto a map on somebody else's hive and back again does not lose anything — the money is sitting safely at home the whole time — but a player who walks up to an ATM over there and sees zero is going to think he got robbed, and you will spend the evening explaining it.

So use it for what it is for: a shared event with another community, a test hive, or a deliberately separate ruleset. Do not use it just to change loadouts between your own maps — that is what the gear group is for, and it leaves the bank alone.

The settings that do this

The first time your server loads a map, it writes a settings file for that map. Three lines in it matter here.

this map's settings file — HFWastelandZ_server_<MapName>.conf
GEAR_SHARE_GROUP
which gear groupWhere loadouts are saved. Anybody running a map set to the same group shares it.starts at ALPHA · takes a restart to change
GARAGE_SHARE_GROUP
which garageWhere parked vehicles are saved. Its own setting on purpose, so you can share gear without sharing cars.starts at ALPHA
PER_MAP_HIVE_ENABLED
+ _URL / _PORT / _KEY
which hiveWhere money and banks are saved. Leave it off and the map just uses your own hive, which is what almost everybody wants.starts off · takes a restart to change

The file lives here, one folder per map:

…/profile/hf_wastelandz/configs/map/<MapName>/HFWastelandZ_server_<MapName>.conf

Or set it in the game: F8 → SERVER → HIVE CONFIG. That writes the same file for you.

A new map starts at ALPHA, every time

The gear group is only read from that map's own file, and there is no server-wide setting for it. So a map you just added does not pick up what your other maps use — it comes up ALPHA no matter what.

If your whole rotation is supposed to share gear, the group has to go in every map's file. Nothing warns you at the time. What you see later is players arriving on the new map with nothing — and they have not lost it. It is sitting in the group they were in, and fixing the file brings it right back.

Wasteland-Z watches for this. When several players in a row load with no gear, the server log says so and names the group it is reading. That message is almost always this mistake.

Gear group names you can use

NATO alphabet, or PRIVATE. It is a fixed list, not free text — a typo would quietly split your players into two pools and nobody would notice until somebody lost a loadout. Anything not on the list gets rejected and falls back to ALPHA.

ALPHA  BRAVO  CHARLIE  DELTA    ECHO     FOXTROT  GOLF   HOTEL  INDIA
JULIET KILO   LIMA     MIKE     NOVEMBER OSCAR    PAPA   QUEBEC ROMEO
SIERRA TANGO  UNIFORM  VICTOR   WHISKEY  XRAY     YANKEE ZULU
PRIVATE   ← shares with nobody — just this one map

Two things that will bite you

  • Everybody sharing a gear group should run the same mods. Gear is saved as a list of item references, so an item from a mod the other server does not have cannot come back. F8 → SERVER → HIVE CONFIG lists everyone in your group and flags anybody whose mods do not match yours.
  • Changing the group takes a restart. You can change it whenever you want, but the server keeps using the one it started with. That is on purpose — swapping it mid-round would dump gear somebody earned in one group into another group's pile on their next save.
Where each setting lives
  • Hive — the gateway's config.py. One gateway can serve several hives out of one database, so a host, or a few friends splitting a box, can run completely separate economies side by side. A server that names no hive just joins the default, so a normal single-hive setup needs nothing here.
  • Gear group, garage group, per-map hive — the map's own HFWastelandZ_server_<MapName>.conf, or F8 → SERVER → HIVE CONFIG.
Wasteland-Z · Admin Guide

Missions & the Mission Editor

Build your own missions in-game — no files to hand-write. Place enemies and structures with Zeus, chain them into a flow with logic steps, and let the pool spawn them on a timer. This is how it all fits together, in the order you actually do it.

A mission is Logic + Events

Two building blocks, and that’s the whole system:

Event
A scene you place in the world with Zeus — the enemies, the buildings, and markers showing where vehicles, loot and reinforcements appear. Events are the mission content.
+
Logic
The flow that drives the mission — show a message, wait until the enemies are dead, hand out a reward, clean up. Logic runs the mission.

A reward is the third piece — the thing players get (a loot crate or a vehicle). You build rewards once and reuse them across any mission.

What a mission looks like

A mission is an ordered list of steps — some are events (spawn a scene), the rest are logic (show a message, wait for a condition, hand out a reward, end the mission). A common shape: show a heads-up → spawn the event → wait until the enemies are dead → spawn a reward → complete. You arrange these steps in the Mission Editor.

Grab the example missions

You don’t have to start from a blank page. Download the example missions from the Wasteland-Z SETUP repo (the missions/ folder — copy events/, templates/ and rewards/ into your server’s <profile>/hf_wastelandz/missions/), then open them in the editor to see how a working mission is put together — then build your own the same way. More examples get added over time, so check back.

The pool runs it on a timer

You don’t spawn missions by hand for players. Finished missions go into the Mission Pool, which spawns them automatically — on a per-tier timer, away from players, with cooldowns so the same spot doesn’t reused instantly. You just build missions and turn the pool on.


Before you start

One-time setup

Do these once. After that, everything is in-game in the F8 admin menu.

A

Download & install the templates

Rewards are what missions hand out — loot crates and vehicles. Download the ready-made mission & reward templates from the Wasteland-Z SETUP GitHub, then copy the rewards into your server profile:

FROM:  missions/rewards/     # from the SETUP download
TO:    <profile>/hf_wastelandz/missions/rewards/

<profile> is wherever your server’s -profile points. Then in-game: F8 → MISSIONS → REWARDS → RELOAD — the templates appear in the list. If the panel shows 0 templates, the _index.json file didn’t get copied — copy it in too.

B

Turn on mission markers, and drop spawn markers on the map

The pool needs to know where missions may appear. You place spawn markers on the map — each one is a candidate location.

  • Turn on Debug / Mission markers so you can see them.
  • Drop a spawn marker and tag it EASY, MEDIUM, or HARD. Drop as many as you like — more markers means more variety in where missions land.
  • A marker with no tier tag accepts any tier. A tagged marker only accepts missions of that tier.
Keep them apart, keep them clear

The pool won’t spawn a mission within ~50 m of a player, and each spot goes on a ~10-minute cooldown after its mission ends. Spread markers around the map and away from busy areas.


Part 1

Build the Event (the scene)

The event is what players actually see and fight. You build it live with Zeus.

1

Create the event and set its tier

F8 → MISSIONS → EVENTS. Type a name, pick the tier / type, and click NEW EVENT. You’re now in event-edit mode.

2

Place the arrow, then build in Zeus

Press F8 to close the admin menu. An arrow appears in the world — that’s the scene’s reference point and facing.

  • Move the arrow to an open area where the scene should sit.
  • Open Zeus (hold Y) and place your AI soldiers and buildings / cover.
  • The first thing you place becomes the anchor — everything else is stored relative to it, so the whole scene keeps its shape wherever the mission spawns.
The AI stay frozen while you build

Enemies you place are automatically suspended and made damage-proof during editing — they won’t shoot you and you can’t accidentally kill them. They wake up only when the mission spawns for real.

3

Drop your markers

Back in the EVENTS tab, use the DROP buttons to mark where things go. Each marker faces the way you’re looking when you drop it — so stand where it goes, face the right direction, then click.

ButtonMarks where…
DROP VEHa vehicle spawns (point the way the nose should face).
DROP LOOTa loot crate / reward drops.
DROP WAVEa wave of enemies spawns (reinforcements, defenders).
DROP PATROL coming soona patrol route waypoint. (Route-following is being finished — see below.)
4

Save the event

Press F8 and click SAVE EVENT. The scene disappears from the world — that’s expected. It’s saved now; you can select it and spawn it again to check it, or stop it.


Part 2

Build the Mission (the flow)

5

Create the mission and load it into the editor

F8 → MISSIONS. Type a mission name, pick a tier (EASY / MEDIUM / HARD), and save — the name now appears in the list. Select it and click LOAD to open the full-screen Mission Editor.

Tip: open an existing mission first and use it as a reference for how the logic is laid out.

6

Add the event, the logic, and the rewards

  • Add the event: pick it from the EVENT LIBRARY and click ADD EVENT.
  • Add logic: click a logic type, set its options (a value box, condition buttons, or a picker — the panel changes to match), then click ADD LOGIC. See the logic reference below.
  • Add rewards: use SPAWN_REWARD and pick the reward from the list.
  • Reorder or fix steps with MOVE UP / MOVE DOWN, REPLACE SEL, and REMOVE NODE.
If adding logic glitches

Sometimes adding a logic step hangs the editor — a node won’t add, or the list stops responding. If that happens, exit the Mission Editor and go back in, then keep going. It’s harmless and you won’t lose saved work.

7

Save the mission

Click SAVE MISSION in the footer. Your flow is written to disk and is ready to test or pool.


Part 3

Pool it & run

8

Add the mission to the pool and start it

  • In the Mission Pool panel, select your mission and ADD it to the rotation.
  • Click START (or START ALL). The pool now spawns pooled missions on their tier timers.
  • Pause / resume per tier or all at once, any time.
TierDefault spawn interval
EASYevery 15 minutes
MEDIUMevery 30 minutes
HARDevery 60 minutes

One mission per tier spawns per interval (a steady trickle, not a burst). Intervals and the master on/off are tunable in active_pool.json. To test a single mission immediately, spawn it directly from the editor with SPAWN MISSION HERE.


Logic steps

The editor builds every step for you — you click the type, then set its options with a value box, condition buttons, or a picker. These are the ones you’ll use most.

StepWhat it doesYou set
Popup messageShows a message to everyone, then continues.The message text.
Wait until AI deadPauses the mission until the targeted enemies are gone.Click the event, ADD TARGET the group(s), pick ALL / ANY / LEADER.
Wait (duration)Pauses for a set number of seconds.Seconds.
Spawn rewardDrops a reward template at a loot marker.ADD TARGET the loot marker, pick the reward.
Spawn lootDrops a loot crate filled from a loot pool.ADD TARGET a marker, name the pool.
Spawn vehicleSpawns a drivable vehicle at a vehicle marker.ADD TARGET the marker, pick a vehicle. More →
Set vehicle optionsLocks / heals / fuels / god-modes a mission vehicle.ADD TARGET the vehicle, cycle the buttons. More →
Spawn waveSpawns a hostile AI group at a wave marker.ADD TARGET the wave marker, name the group prefab.
Wait until hackPauses until players hack a terminal you placed.Pick the terminal, set TIME / RADIUS / STARTUP / DECAY. More →
ObjectiveA goal gate: wait for a condition, optionally show a toast.A condition (kill / reach a spot / hold for N seconds).
Unlock rewardOpens a locked reward (e.g. a reward vehicle) so players can use it.ADD TARGET the reward’s marker.
Complete / FailEnds the mission as a win or a loss.Nothing.
Complete if / Fail ifEnds the mission the moment a condition is met (checked constantly).ADD TARGET + condition.
Mission timeoutFails the mission after N seconds. A safety net.Seconds.
Clear eventDespawns an event’s entities mid-mission.The event.
Call missionChains another mission at the same spot.The mission name.

Zeus AI & waiting for the fight to end

The bread-and-butter mission: place enemies, wait until they’re dead, hand out the reward.

Placing the enemies

Anything you drop in Zeus during event editing becomes part of the event — individual soldiers or whole groups. They spawn hostile to every player faction (nobody can befriend them), and by default they hold their ground near where you placed them, so they don’t wander off and leave a straggler somewhere across the map.

Waiting until they’re dead

Add a Wait until AI dead step, click the event, and ADD TARGET the group(s) you want to wait on. Then choose:

  • ALL — every member of the group is dead (the usual choice).
  • ANY — at least one has died.
  • LEADER — the group’s leader is dead.

You can target more than one group — the step waits for all of them. Once satisfied, the mission moves to the next step (spawn the reward, complete, etc.).

Reinforcements & objectives

  • Spawn wave drops a fresh hostile group at a WAVE marker — use it for defenders or a second push. Kill them with a Complete if … all dead or another Wait until AI dead.
  • Objective supports kill, reach a spot (any player within range of a marker), and hold for N seconds, with an optional on-screen toast when met.

Vehicles

A mission vehicle comes from one of two places, and that changes how you control it:

  • Placed in Zeus — part of the event, already sitting there when the mission spawns.
  • Spawned by logic — a Spawn vehicle step creates one at a VEH marker, at any point you choose.

Vehicles you place in Zeus

Drop the vehicle in Zeus like any other object while building the event. To change how it behaves, add a Set vehicle options step and ADD TARGET the vehicle itselfyou do not need a vehicle marker to change the behavior of a Zeus-placed vehicle.

Because logic steps run in order, you can condition the same vehicle at the start of the mission and again at the end. For example:

  • Lock it at the start, then Unlock it on mission complete — the vehicle becomes the payoff for finishing.
  • Set it out of fuel or god-moded at the start, then refuel or release it at the end.

Spawning a vehicle with logic

  1. In the event, DROP VEH where it should appear — face the way the nose should point.
  2. Add a Spawn vehicle step, ADD TARGET that marker, and pick a vehicle from the catalog (grouped by category: Cars, Vans, Armored, Helicopters…).

Put the step wherever you want it to fire — up front, or as a reward on mission complete. The vehicle spawns drivable and automatically faces where the marker points. A VEH marker is required here, because you’re creating a brand-new vehicle at that spot.

Set vehicle options — the controls

Whichever way the vehicle got there, Set vehicle options conditions it. ADD TARGET the vehicle, then cycle the buttons (no typing):

ButtonEffect
LOCKSeals the vehicle for all players until you unlock it.
GODMakes it invincible.
FUELSets fuel: 0 / 25 / 50 / 75 / 100%.
HEALTH100% = fully repaired; below 100 = visibly damaged.
AMMO coming soonRearm isn’t wired yet — leave on NO CHANGE.
Vehicle health, honestly

Setting HEALTH 100 fully repairs. Any value below 100 damages the vehicle but isn’t an exact percentage, and 0 doesn’t make a quiet wreck — it explodes (a proper burnt-out wreck is still being built). For a beat-up-but-usable vehicle, set a mid HEALTH and lock/god as needed.

Reward vehicles & locking

A vehicle can also be a reward — spawned locked (and optionally out of fuel) as the objective, then opened with an Unlock reward step once players earn it. A reward vehicle destroyed while still locked respawns, so it can’t be griefed before it’s claimed. Lock behavior is the lock_flags field in the reward template — combine flags by adding their numbers:

lock_flagsMeaning
0Unlocked from the moment it spawns.
1Locked to the mission faction until unlocked.
2Locked to the single player who unlocks it.
4Fuel drained on spawn.
5Faction-locked + no fuel (1 + 4).

The no-ammo and damaged lock flags exist in the template but aren’t functional yet — see coming soon.


Hack terminals

A hold-the-ground objective: players must stand in a circle around a terminal and hold it while a timer counts down.

Place the terminal

  1. In the event, click PLACE HACK ITEM — the terminal (a laptop) spawns into your hands.
  2. Walk it to the spot and place it. SAVE EVENT.

Wire it into the mission

Add a Wait until hack step, pick the terminal from the event’s items, then cycle the four config buttons:

ButtonSetsValues
TIMETotal hack countdown.1 / 2 / 5 / 10 / 15 min
RADIUSHow big the hold-circle is.10 / 15 / 20 / 25 / 30 m
STARTUPOptional commit hold before the main timer.Off / 3 / 5 / 10 s
DECAYWhat happens when the circle empties mid-hack.Off (pause) / Slow / Med / Hard

How it plays

Players approach the terminal, use it to START, and hold the circle. The countdown pauses when the circle is empty and resumes when someone steps back in (or decays backward if you set DECAY). Anyone who leaves gets a warning to get back in. When the timer hits zero with someone inside, the hack completes and the mission continues.

Always pair a hack with a timeout

A hack has no built-in failure — if players never finish it, the mission waits forever. Add a Mission timeout step (or a global timer) so an unfinished hack fails cleanly.


Advanced & coming soon

These are in the editor but not fully live yet. You may see the buttons — here’s the honest status so you don’t build a demo around them:

FeatureStatus
Patrol routes (DROP PATROL → patrol path)coming soon Waypoints drop, but route-following needs a Workbench step to finish. AI hold position for now.
Helicopter spawns (Spawn heli)coming soon Crewed-heli logic is written; awaiting a prefab hookup and flight testing.
Escort objectivescoming soon Placeholder — not usable yet.
Silent vehicle wrecks (health 0)coming soon A 0-health vehicle currently explodes; a proper burnt wreck is in progress.
Vehicle rearm / exact repair%coming soon AMMO and partial-repair are not wired yet.

Gotchas & where files live

Common snags
  • Adding logic hangs the editor → exit and re-enter the Mission Editor, then continue.
  • A marker points the wrong way → you faced the wrong direction when you dropped it. Stand on it, face the right way, drop again.
  • REWARDS shows 0 templates_index.json is missing from the rewards folder.
  • A hack mission never ends → you forgot a Mission timeout.
  • The reward never dropped → the earlier Wait until AI dead is still waiting. Confirm every targeted group actually dies.

Where mission files live on your server

<profile>/hf_wastelandz/missions/
├── rewards/            # the templates from this kit (+ any you add)
├── events/             # saved scenes        (*.event.json)
├── templates/          # saved missions      (*.mission.json)
├── <MapName>/spots/     # per-map spawn markers
└── active_pool.json    # pool timers + on/off switch

Everything except rewards/ is created and managed by the in-game tools — you don’t hand-edit these. To fine-tune spawn cadence, active_pool.json can be edited by hand; the server re-reads it on restart.

Wasteland-Z · Admin Guide

Missions & the Mission Editor

Build your own missions in-game — no files to hand-write. Place enemies and structures with Zeus, chain them into a flow with logic steps, and let the pool spawn them on a timer. This is how it all fits together, in the order you actually do it.

A mission is Logic + Events

Two building blocks, and that’s the whole system:

Event
A scene you place in the world with Zeus — the enemies, the buildings, and markers showing where vehicles, loot and reinforcements appear. Events are the mission content.
+
Logic
The flow that drives the mission — show a message, wait until the enemies are dead, hand out a reward, clean up. Logic runs the mission.

A reward is the third piece — the thing players get (a loot crate or a vehicle). You build rewards once and reuse them across any mission.

What a mission looks like

A mission is an ordered list of steps — some are events (spawn a scene), the rest are logic (show a message, wait for a condition, hand out a reward, end the mission). A common shape: show a heads-up → spawn the event → wait until the enemies are dead → spawn a reward → complete. You arrange these steps in the Mission Editor.

Grab the example missions

You don’t have to start from a blank page. Download the example missions from the Wasteland-Z SETUP repo (the missions/ folder — copy events/, templates/ and rewards/ into your server’s <profile>/hf_wastelandz/missions/), then open them in the editor to see how a working mission is put together — then build your own the same way. More examples get added over time, so check back.

The pool runs it on a timer

You don’t spawn missions by hand for players. Finished missions go into the Mission Pool, which spawns them automatically — on a per-tier timer, away from players, with cooldowns so the same spot doesn’t reused instantly. You just build missions and turn the pool on.


Before you start

One-time setup

Do these once. After that, everything is in-game in the F8 admin menu.

A

Download & install the templates

Rewards are what missions hand out — loot crates and vehicles. Download the ready-made mission & reward templates from the Wasteland-Z SETUP GitHub, then copy the rewards into your server profile:

FROM:  missions/rewards/     # from the SETUP download
TO:    <profile>/hf_wastelandz/missions/rewards/

<profile> is wherever your server’s -profile points. Then in-game: F8 → MISSIONS → REWARDS → RELOAD — the templates appear in the list. If the panel shows 0 templates, the _index.json file didn’t get copied — copy it in too.

B

Turn on mission markers, and drop spawn markers on the map

The pool needs to know where missions may appear. You place spawn markers on the map — each one is a candidate location.

  • Turn on Debug / Mission markers so you can see them.
  • Drop a spawn marker and tag it EASY, MEDIUM, or HARD. Drop as many as you like — more markers means more variety in where missions land.
  • A marker with no tier tag accepts any tier. A tagged marker only accepts missions of that tier.
Keep them apart, keep them clear

The pool won’t spawn a mission within ~50 m of a player, and each spot goes on a ~10-minute cooldown after its mission ends. Spread markers around the map and away from busy areas.


Part 1

Build the Event (the scene)

The event is what players actually see and fight. You build it live with Zeus.

1

Create the event and set its tier

F8 → MISSIONS → EVENTS. Type a name, pick the tier / type, and click NEW EVENT. You’re now in event-edit mode.

2

Place the arrow, then build in Zeus

Press F8 to close the admin menu. An arrow appears in the world — that’s the scene’s reference point and facing.

  • Move the arrow to an open area where the scene should sit.
  • Open Zeus (hold Y) and place your AI soldiers and buildings / cover.
  • The first thing you place becomes the anchor — everything else is stored relative to it, so the whole scene keeps its shape wherever the mission spawns.
The AI stay frozen while you build

Enemies you place are automatically suspended and made damage-proof during editing — they won’t shoot you and you can’t accidentally kill them. They wake up only when the mission spawns for real.

3

Drop your markers

Back in the EVENTS tab, use the DROP buttons to mark where things go. Each marker faces the way you’re looking when you drop it — so stand where it goes, face the right direction, then click.

ButtonMarks where…
DROP VEHa vehicle spawns (point the way the nose should face).
DROP LOOTa loot crate / reward drops.
DROP WAVEa wave of enemies spawns (reinforcements, defenders).
DROP PATROL coming soona patrol route waypoint. (Route-following is being finished — see below.)
4

Save the event

Press F8 and click SAVE EVENT. The scene disappears from the world — that’s expected. It’s saved now; you can select it and spawn it again to check it, or stop it.


Part 2

Build the Mission (the flow)

5

Create the mission and load it into the editor

F8 → MISSIONS. Type a mission name, pick a tier (EASY / MEDIUM / HARD), and save — the name now appears in the list. Select it and click LOAD to open the full-screen Mission Editor.

Tip: open an existing mission first and use it as a reference for how the logic is laid out.

6

Add the event, the logic, and the rewards

  • Add the event: pick it from the EVENT LIBRARY and click ADD EVENT.
  • Add logic: click a logic type, set its options (a value box, condition buttons, or a picker — the panel changes to match), then click ADD LOGIC. See the logic reference below.
  • Add rewards: use SPAWN_REWARD and pick the reward from the list.
  • Reorder or fix steps with MOVE UP / MOVE DOWN, REPLACE SEL, and REMOVE NODE.
If adding logic glitches

Sometimes adding a logic step hangs the editor — a node won’t add, or the list stops responding. If that happens, exit the Mission Editor and go back in, then keep going. It’s harmless and you won’t lose saved work.

7

Save the mission

Click SAVE MISSION in the footer. Your flow is written to disk and is ready to test or pool.


Part 3

Pool it & run

8

Add the mission to the pool and start it

  • In the Mission Pool panel, select your mission and ADD it to the rotation.
  • Click START (or START ALL). The pool now spawns pooled missions on their tier timers.
  • Pause / resume per tier or all at once, any time.
TierDefault spawn interval
EASYevery 15 minutes
MEDIUMevery 30 minutes
HARDevery 60 minutes

One mission per tier spawns per interval (a steady trickle, not a burst). Intervals and the master on/off are tunable in active_pool.json. To test a single mission immediately, spawn it directly from the editor with SPAWN MISSION HERE.


Logic steps

The editor builds every step for you — you click the type, then set its options with a value box, condition buttons, or a picker. These are the ones you’ll use most.

StepWhat it doesYou set
Popup messageShows a message to everyone, then continues.The message text.
Wait until AI deadPauses the mission until the targeted enemies are gone.Click the event, ADD TARGET the group(s), pick ALL / ANY / LEADER.
Wait (duration)Pauses for a set number of seconds.Seconds.
Spawn rewardDrops a reward template at a loot marker.ADD TARGET the loot marker, pick the reward.
Spawn lootDrops a loot crate filled from a loot pool.ADD TARGET a marker, name the pool.
Spawn vehicleSpawns a drivable vehicle at a vehicle marker.ADD TARGET the marker, pick a vehicle. More →
Set vehicle optionsLocks / heals / fuels / god-modes a mission vehicle.ADD TARGET the vehicle, cycle the buttons. More →
Spawn waveSpawns a hostile AI group at a wave marker.ADD TARGET the wave marker, name the group prefab.
Wait until hackPauses until players hack a terminal you placed.Pick the terminal, set TIME / RADIUS / STARTUP / DECAY. More →
ObjectiveA goal gate: wait for a condition, optionally show a toast.A condition (kill / reach a spot / hold for N seconds).
Unlock rewardOpens a locked reward (e.g. a reward vehicle) so players can use it.ADD TARGET the reward’s marker.
Complete / FailEnds the mission as a win or a loss.Nothing.
Complete if / Fail ifEnds the mission the moment a condition is met (checked constantly).ADD TARGET + condition.
Mission timeoutFails the mission after N seconds. A safety net.Seconds.
Clear eventDespawns an event’s entities mid-mission.The event.
Call missionChains another mission at the same spot.The mission name.

Zeus AI & waiting for the fight to end

The bread-and-butter mission: place enemies, wait until they’re dead, hand out the reward.

Placing the enemies

Anything you drop in Zeus during event editing becomes part of the event — individual soldiers or whole groups. They spawn hostile to every player faction (nobody can befriend them), and by default they hold their ground near where you placed them, so they don’t wander off and leave a straggler somewhere across the map.

Waiting until they’re dead

Add a Wait until AI dead step, click the event, and ADD TARGET the group(s) you want to wait on. Then choose:

  • ALL — every member of the group is dead (the usual choice).
  • ANY — at least one has died.
  • LEADER — the group’s leader is dead.

You can target more than one group — the step waits for all of them. Once satisfied, the mission moves to the next step (spawn the reward, complete, etc.).

Reinforcements & objectives

  • Spawn wave drops a fresh hostile group at a WAVE marker — use it for defenders or a second push. Kill them with a Complete if … all dead or another Wait until AI dead.
  • Objective supports kill, reach a spot (any player within range of a marker), and hold for N seconds, with an optional on-screen toast when met.

Vehicles

A mission vehicle comes from one of two places, and that changes how you control it:

  • Placed in Zeus — part of the event, already sitting there when the mission spawns.
  • Spawned by logic — a Spawn vehicle step creates one at a VEH marker, at any point you choose.

Vehicles you place in Zeus

Drop the vehicle in Zeus like any other object while building the event. To change how it behaves, add a Set vehicle options step and ADD TARGET the vehicle itselfyou do not need a vehicle marker to change the behavior of a Zeus-placed vehicle.

Because logic steps run in order, you can condition the same vehicle at the start of the mission and again at the end. For example:

  • Lock it at the start, then Unlock it on mission complete — the vehicle becomes the payoff for finishing.
  • Set it out of fuel or god-moded at the start, then refuel or release it at the end.

Spawning a vehicle with logic

  1. In the event, DROP VEH where it should appear — face the way the nose should point.
  2. Add a Spawn vehicle step, ADD TARGET that marker, and pick a vehicle from the catalog (grouped by category: Cars, Vans, Armored, Helicopters…).

Put the step wherever you want it to fire — up front, or as a reward on mission complete. The vehicle spawns drivable and automatically faces where the marker points. A VEH marker is required here, because you’re creating a brand-new vehicle at that spot.

Set vehicle options — the controls

Whichever way the vehicle got there, Set vehicle options conditions it. ADD TARGET the vehicle, then cycle the buttons (no typing):

ButtonEffect
LOCKSeals the vehicle for all players until you unlock it.
GODMakes it invincible.
FUELSets fuel: 0 / 25 / 50 / 75 / 100%.
HEALTH100% = fully repaired; below 100 = visibly damaged.
AMMO coming soonRearm isn’t wired yet — leave on NO CHANGE.
Vehicle health, honestly

Setting HEALTH 100 fully repairs. Any value below 100 damages the vehicle but isn’t an exact percentage, and 0 doesn’t make a quiet wreck — it explodes (a proper burnt-out wreck is still being built). For a beat-up-but-usable vehicle, set a mid HEALTH and lock/god as needed.

Reward vehicles & locking

A vehicle can also be a reward — spawned locked (and optionally out of fuel) as the objective, then opened with an Unlock reward step once players earn it. A reward vehicle destroyed while still locked respawns, so it can’t be griefed before it’s claimed. Lock behavior is the lock_flags field in the reward template — combine flags by adding their numbers:

lock_flagsMeaning
0Unlocked from the moment it spawns.
1Locked to the mission faction until unlocked.
2Locked to the single player who unlocks it.
4Fuel drained on spawn.
5Faction-locked + no fuel (1 + 4).

The no-ammo and damaged lock flags exist in the template but aren’t functional yet — see coming soon.


Hack terminals

A hold-the-ground objective: players must stand in a circle around a terminal and hold it while a timer counts down.

Place the terminal

  1. In the event, click PLACE HACK ITEM — the terminal (a laptop) spawns into your hands.
  2. Walk it to the spot and place it. SAVE EVENT.

Wire it into the mission

Add a Wait until hack step, pick the terminal from the event’s items, then cycle the four config buttons:

ButtonSetsValues
TIMETotal hack countdown.1 / 2 / 5 / 10 / 15 min
RADIUSHow big the hold-circle is.10 / 15 / 20 / 25 / 30 m
STARTUPOptional commit hold before the main timer.Off / 3 / 5 / 10 s
DECAYWhat happens when the circle empties mid-hack.Off (pause) / Slow / Med / Hard

How it plays

Players approach the terminal, use it to START, and hold the circle. The countdown pauses when the circle is empty and resumes when someone steps back in (or decays backward if you set DECAY). Anyone who leaves gets a warning to get back in. When the timer hits zero with someone inside, the hack completes and the mission continues.

Always pair a hack with a timeout

A hack has no built-in failure — if players never finish it, the mission waits forever. Add a Mission timeout step (or a global timer) so an unfinished hack fails cleanly.


Advanced & coming soon

These are in the editor but not fully live yet. You may see the buttons — here’s the honest status so you don’t build a demo around them:

FeatureStatus
Patrol routes (DROP PATROL → patrol path)coming soon Waypoints drop, but route-following needs a Workbench step to finish. AI hold position for now.
Helicopter spawns (Spawn heli)coming soon Crewed-heli logic is written; awaiting a prefab hookup and flight testing.
Escort objectivescoming soon Placeholder — not usable yet.
Silent vehicle wrecks (health 0)coming soon A 0-health vehicle currently explodes; a proper burnt wreck is in progress.
Vehicle rearm / exact repair%coming soon AMMO and partial-repair are not wired yet.

Gotchas & where files live

Common snags
  • Adding logic hangs the editor → exit and re-enter the Mission Editor, then continue.
  • A marker points the wrong way → you faced the wrong direction when you dropped it. Stand on it, face the right way, drop again.
  • REWARDS shows 0 templates_index.json is missing from the rewards folder.
  • A hack mission never ends → you forgot a Mission timeout.
  • The reward never dropped → the earlier Wait until AI dead is still waiting. Confirm every targeted group actually dies.

Where mission files live on your server

<profile>/hf_wastelandz/missions/
├── rewards/            # the templates from this kit (+ any you add)
├── events/             # saved scenes        (*.event.json)
├── templates/          # saved missions      (*.mission.json)
├── <MapName>/spots/     # per-map spawn markers
└── active_pool.json    # pool timers + on/off switch

Everything except rewards/ is created and managed by the in-game tools — you don’t hand-edit these. To fine-tune spawn cadence, active_pool.json can be edited by hand; the server re-reads it on restart.

Wasteland-Z · Keeping current

Update the gateway.

The mod updates itself from the Workshop on server start — you never touch it. The gateway is the one piece you host, and this is how you update it. Five steps, the same every release.

Update the gateway

Five steps. Same every release.

Any file can change in a release, so these steps replace the whole folder every time. The kit is your clone (/opt/wastelandz, D:\wastelandz); the gateway folder is what runs (/opt/wastelandz-gateway, C:\wastelandz-gateway).

1

Stop the gateway and back up the whole folder

The whole folder — a one-file backup restores a mix of old and new.

# Linux
sudo systemctl stop wz-gateway
cp -r /opt/wastelandz-gateway /opt/wastelandz-gateway.bak
cp /opt/wastelandz-gateway/config.py ~/config.py.bak

# Windows — close the WZ-Gateway window first
xcopy /E /I /Y C:\wastelandz-gateway C:\wastelandz-gateway.bak
copy C:\wastelandz-gateway\config.py %USERPROFILE%\config.py.bak

config.py holds your password and key; its copy goes outside the folder you are about to overwrite.

2

Get the new kit

Make sure you are on the main branch before you pull — that is what decides which gateway you get.

# Linux
cd /opt/wastelandz
git checkout main
git pull
grep GATEWAY_VERSION gateway/gateway.py

# Windows
cd D:\wastelandz
git checkout main
git pull

# confirm what you just pulled
findstr GATEWAY_VERSION gateway\gateway.py

That last line prints the version now sitting in the kit. Nothing has been copied to your gateway folder yet — that is step 3.

No git? Download the ZIP and unpack it over the same folder. Same result.

3

Copy every file across

Your config.py is not in the repo, so nothing overwrites it.

# Linux
cp -r /opt/wastelandz/gateway/. /opt/wastelandz-gateway/

# Windows
xcopy /E /Y D:\wastelandz\gateway C:\wastelandz-gateway\
4

Install the requirements

Every time — it does nothing when nothing changed.

# Linux
cd /opt/wastelandz-gateway
source venv/bin/activate
pip install -r requirements.txt

# Windows
cd C:\wastelandz-gateway
pip install -r requirements.txt
5

Start it and check

# Linux
sudo systemctl start wz-gateway
sudo systemctl status wz-gateway --no-pager

# Windows — double-click start_gateway.bat

# then, either platform
curl http://127.0.0.1:5000/api/ping

You want status: ok from the ping, and the banner naming v0.7.1:

  WastelandZ Gateway v0.7.1
  Hive ID:   default
  Database:  localhost:3306/wastelandz
[GATEWAY] Database connection: OK

Wrong version? You copied from the wrong place — step 3 must point at the gateway folder of a main-branch clone. On Linux, watch the live log with sudo journalctl -u wz-gateway -f.

If a release changes the database, the release note says so and gives you the mysql line. Nothing here runs SQL for you.

Roll back — restores every file, not just one
# Linux
sudo systemctl stop wz-gateway
cp -r /opt/wastelandz-gateway.bak/. /opt/wastelandz-gateway/
sudo systemctl start wz-gateway

# Windows — close the window, then
xcopy /E /Y C:\wastelandz-gateway.bak C:\wastelandz-gateway\

Lost config.py too? Restore ~/config.py.bak from step 1.

Before you run it

Read the source first. All of it is public.

You are about to run code on a machine that holds your players’ data and your database password. You should not have to take our word for any of it. Every file is readable in the browser before you download anything — no account, no clone.

FileWhat it isWhat it can reach
gateway.py The whole program. A Flask web service: it listens on the ports you configure and answers the game server’s requests about players, money, gear and bans. Your MySQL database, and the ports in your config.py. It makes no outbound connections — nothing phones home, and there is no update check.
setup_database.sql Every table, defined once. You run it by hand when you first set up the database. Your database only. It is written so that it can only add — there is no DROP or DELETE anywhere in it, and the gateway refuses to start if one ever appears.
hf_crypto.py Verifies the signed tokens the game server sends, so a stranger who finds your port cannot write to your database. Nothing external. Pure computation on the key already in your config.py.
requirements.txt The Python packages. Three, all mainstream: Flask, the MySQL connector, psutil. PyPI, at install time only.
config.example.py The template you copy to config.py. Every setting is commented. Nothing. It is a template and holds no real credentials.
Have an AI read it for you — genuinely, do this

You do not need to read Python to check this. Paste the link into any capable assistant and ask it directly. It costs you two minutes and it is a real check, not a ritual.

Works with: Claude, ChatGPT, Gemini, Copilot, Grok. For the ones that cannot open a link, open the Raw button on GitHub and paste the text instead.

Something like this:

Review this file for anything malicious or risky before I run it
on a server that holds a database password.

https://raw.githubusercontent.com/Motavar/HF_WastelandZ_SETUP/main/gateway/gateway.py

Specifically tell me:
 - does it make any outbound network connections, and to where?
 - does it read or send any credentials, keys, or environment variables
   anywhere other than the local database?
 - does it execute anything downloaded at runtime (eval, exec, pickle,
   subprocess with a remote source)?
 - does it delete or overwrite files outside its own folder?
 - anything else you would not want running on a server you own.

The honest answers are: no outbound connections, credentials go only to your own MySQL, nothing is executed from a download, and the only file it writes outside its folder is a database backup you asked for. If an assistant tells you otherwise, do not run it — tell us instead.

config.py

It holds your database password and gateway key, and it is the one file here that is yours — everything else can be re-downloaded. It is not in the repo, so no update overwrites it.

If you lose it: copy config.example.py to config.py and fill in your MySQL password and key. The api_key must match API_KEY in that server’s HFWastelandZ_secrets.conf character for character — that file is where you can read it back. If they differ the gateway rejects every request and nothing saves, while the server looks healthy.

What is in the database

You never need to touch these — setup_database.sql creates them. This is here so you know what your server stores, and what a backup contains.

The SQL step is yours on this release

On this release you apply setup_database.sql yourself, with the mysql line from setup. It only ever adds, so re-running it is safe.

That file is written so that running it again changes nothing, and so that it can only ever add — it never drops a table or deletes a row. Applying it to a database that is already current is a no-op; applying it to an old one brings it forward. A brand-new install and a five-year-old one end up with exactly the same database, because both run the same file.

Nothing in it removes anything, so there is no destructive case to worry about on this release.

Player data

TableHoldsScope
playersMoney, bank, faction, identity, and gearHive-wide — follows the player to every server in the hive
player_sessionsPosition, stance, alive state, /recover tokenPer server — where you logged off
transactionsEvery money movement, append-onlyAudit trail
player_stats_dailyKills, deaths, playtimePer server, per day
player_marker_prefsMap / HUD marker choicesHive-wide

Server & world

TableHolds
hf_placementsPlayer-placed objects and admin fixtures
money_dropsDropped cash — session-only, wiped on boot
blacklist / security_eventsBans and the audit log
What a hive shares on this release

Every server that points at the same database with the same HIVE_ID is one hive, and a player’s money, bank and gear follow them across all of it. There is no way to share money but not gear — that dial does not exist yet.

Position, placements and money drops stay per server, so where you logged off is still where you log back in.

Servers in one hive should run the same mods

Gear is stored as item references. If a player carries an item from a server that has a mod to one that does not, that item cannot be restored there. Since gear is shared hive-wide on this release, keeping one mod set across the hive is what prevents it.

Splitting gear into separate groups while keeping one bank — and the addon list that lets you compare servers before you do — arrives with the next release. See the Beta channel.

Wasteland-Z · Keeping current

Update the gateway.

The mod updates itself from the Workshop on server start — you never touch it. The gateway is the one piece you host, and this is how you update it. Five steps, the same every release.

Update the gateway

Five steps. Same every release.

Any file can change in a release, so these steps replace the whole folder every time. The kit is your clone (/opt/wastelandz, D:\wastelandz); the gateway folder is what runs (/opt/wastelandz-gateway, C:\wastelandz-gateway).

1

Stop the gateway and back up the whole folder

The whole folder — a one-file backup restores a mix of old and new.

# Linux
sudo systemctl stop wz-gateway
cp -r /opt/wastelandz-gateway /opt/wastelandz-gateway.bak
cp /opt/wastelandz-gateway/config.py ~/config.py.bak

# Windows — close the WZ-Gateway window first
xcopy /E /I /Y C:\wastelandz-gateway C:\wastelandz-gateway.bak
copy C:\wastelandz-gateway\config.py %USERPROFILE%\config.py.bak

config.py holds your password and key; its copy goes outside the folder you are about to overwrite.

2

Get the new kit

Make sure you are on the beta branch before you pull — that is what decides which gateway you get.

# Linux
cd /opt/wastelandz
git checkout beta
git pull
grep GATEWAY_VERSION gateway/gateway.py

# Windows
cd D:\wastelandz
git checkout beta
git pull

# confirm what you just pulled
findstr GATEWAY_VERSION gateway\gateway.py

That last line prints the version now sitting in the kit. Nothing has been copied to your gateway folder yet — that is step 3.

No git? Download the ZIP and unpack it over the same folder. Same result.

3

Copy every file across

Your config.py is not in the repo, so nothing overwrites it.

# Linux
cp -r /opt/wastelandz/gateway/. /opt/wastelandz-gateway/

# Windows
xcopy /E /Y D:\wastelandz\gateway C:\wastelandz-gateway\
4

Install the requirements

Every time — it does nothing when nothing changed.

# Linux
cd /opt/wastelandz-gateway
source venv/bin/activate
pip install -r requirements.txt

# Windows
cd C:\wastelandz-gateway
pip install -r requirements.txt
5

Start it and check

# Linux
sudo systemctl start wz-gateway
sudo systemctl status wz-gateway --no-pager

# Windows — double-click start_gateway.bat

# then, either platform
curl http://127.0.0.1:5000/api/ping

You want status: ok from the ping, and the banner naming v0.9.0:

  WastelandZ Gateway v0.9.0
[SCHEMA] setup_database.sql applied (idempotent; no-op when current).
[MIGRATE] Schema up to date (version 90).
[GATEWAY] Database connection: OK

Wrong version? You copied from the wrong place — step 3 must point at the gateway folder of a beta-branch clone. On Linux, watch the live log with sudo journalctl -u wz-gateway -f.

Database changes apply themselves on this release — the gateway runs the schema on start and records what it applied. You do not run SQL.

Roll back — restores every file, not just one
# Linux
sudo systemctl stop wz-gateway
cp -r /opt/wastelandz-gateway.bak/. /opt/wastelandz-gateway/
sudo systemctl start wz-gateway

# Windows — close the window, then
xcopy /E /Y C:\wastelandz-gateway.bak C:\wastelandz-gateway\

Lost config.py too? Restore ~/config.py.bak from step 1.

Before you run it

Read the source first. All of it is public.

You are about to run code on a machine that holds your players’ data and your database password. You should not have to take our word for any of it. Every file is readable in the browser before you download anything — no account, no clone.

FileWhat it isWhat it can reach
gateway.py The whole program. A Flask web service: it listens on the ports you configure and answers the game server’s requests about players, money, gear and bans. Your MySQL database, and the ports in your config.py. It makes no outbound connections — nothing phones home, and there is no update check.
migrate.py Brings the database up to date on start. Applies setup_database.sql, then any pending data migration. Your database, and mysqldump when it takes a backup. Refuses to remove anything unless you start it with --allow-destructive.
setup_database.sql Every table, defined once. Applied on every start. Your database only. It is written so that it can only add — there is no DROP or DELETE anywhere in it, and the gateway refuses to start if one ever appears.
hf_crypto.py Verifies the signed tokens the game server sends, so a stranger who finds your port cannot write to your database. Nothing external. Pure computation on the key already in your config.py.
requirements.txt The Python packages. Four, all mainstream: Flask, the MySQL connector, psutil, waitress. PyPI, at install time only.
config.example.py The template you copy to config.py. Every setting is commented. Nothing. It is a template and holds no real credentials.
gateway/tests/ The tests we run against it, shipped so you can run them too. A database you point them at. One of them wipes that database and says so loudly — read its README first.
Have an AI read it for you — genuinely, do this

You do not need to read Python to check this. Paste the link into any capable assistant and ask it directly. It costs you two minutes and it is a real check, not a ritual.

Works with: Claude, ChatGPT, Gemini, Copilot, Grok. For the ones that cannot open a link, open the Raw button on GitHub and paste the text instead.

Something like this:

Review this file for anything malicious or risky before I run it
on a server that holds a database password.

https://raw.githubusercontent.com/Motavar/HF_WastelandZ_SETUP/beta/gateway/gateway.py

Specifically tell me:
 - does it make any outbound network connections, and to where?
 - does it read or send any credentials, keys, or environment variables
   anywhere other than the local database?
 - does it execute anything downloaded at runtime (eval, exec, pickle,
   subprocess with a remote source)?
 - does it delete or overwrite files outside its own folder?
 - anything else you would not want running on a server you own.

The honest answers are: no outbound connections, credentials go only to your own MySQL, nothing is executed from a download, and the only file it writes outside its folder is a database backup you asked for. If an assistant tells you otherwise, do not run it — tell us instead.

config.py

It holds your database password and gateway key, and it is the one file here that is yours — everything else can be re-downloaded. It is not in the repo, so no update overwrites it.

If you lose it: copy config.example.py to config.py and fill in your MySQL password and key. The api_key must match API_KEY in that server’s HFWastelandZ_secrets.conf character for character — that file is where you can read it back. If they differ the gateway rejects every request and nothing saves, while the server looks healthy.

What is in the database

You never need to touch these — setup_database.sql creates them and the gateway keeps them current. This is here so you know what your server stores, and what a backup contains.

The database updates itself — there is no SQL step

From gateway 0.9.0 the gateway applies setup_database.sql on every start. Update the file, restart, done. You do not run SQL by hand, and there is no migration step to forget.

That file is written so that running it again changes nothing, and so that it can only ever add — it never drops a table or deletes a row. Applying it to a database that is already current is a no-op; applying it to an old one brings it forward. A brand-new install and a five-year-old one end up with exactly the same database, because both run the same file.

Anything that removes something is kept separate and is never applied automatically — the gateway stops and tells you the exact command instead, after taking a backup. See Troubleshooting if you ever see REFUSING TO START.

Player data

TableHoldsScope
playersMoney, bank, faction, identityHive-wide — follows the player to every server
player_dataGear, and any future player-owned systemPer gear group — see “How new features get stored” below
player_sessionsPosition, stance, alive state, /recover tokenPer server — where you logged off
transactionsEvery money movement, append-onlyAudit trail
player_stats_dailyKills, deaths, playtimePer server, per day
player_marker_prefsMap / HUD marker choicesHive-wide

Server & world

TableHolds
hive_serversEach server’s map, gear group, mod version and full addon list — what lets you spot a mod mismatch before it costs a player their gear
hive_share_groupsLabels for the A–Z gear groups
hf_placementsPlayer-placed objects and admin fixtures
hf_bases / hf_base_accessBase ownership and door access (schema only for now)
money_dropsDropped cash — session-only, wiped on boot
blacklist / security_eventsBans and the audit log
hive_dataAnything that belongs to the hive rather than a player — supporter tiers, announcements, seasonal state. Same idea as player_data, without a player.
schema_migrationsWhich schema updates have been applied — what stops one running twice
Gear groups — one bank, several wardrobes

Money and bank live in players and are always shared across your whole hive. Gear lives in player_data and is shared according to a gear group, set per server and per map.

Every server defaults to group ALPHA, so out of the box the whole hive shares gear exactly as before. Put servers 1–3 in ALPHA and 4–6 in BRAVO and you get two independent gear pools that still share one bank. A server set to PRIVATE keeps its gear to itself.

Set it in HFWastelandZ_server_<MapName>.conf, or live from F8 → SERVER. It is per-map on purpose: a server that rotates to a different map may also run different mods, and gear from one mod set cannot be restored where those mods are missing.

Servers sharing gear must share mods

Gear is stored as item references. If a player carries an item from a server that has a mod to one that does not, that item cannot be restored there. The gateway records every server’s addon list precisely so you can compare them before putting servers in the same gear group.

Reading it from outside the game — for bots and admin tools

Every namespace is readable over the API, so a Discord bot or dashboard follows the game into new data without the gateway changing again. All of these need your api_key.

EndpointReturns
GET /api/data/<uid>/<ns>Every group that player has in one namespace. The right call when you do not know their gear group.
GET /api/data/<uid>/<ns>/<group>One row by its full key. PUT/DELETE the same path to write or remove it.
GET /api/hivedata/<ns>/<scope>One hive-level row. GET /api/hivedata/<ns> lists every scope.
GET /api/namespace/<ns>Every player’s rows in one namespace — leaderboards and exports. Capped at 500; page with limit/offset.
GET /api/namespacesWhat is actually stored, player and hive, with counts. Use this instead of hardcoding names.

There is deliberately no endpoint that takes a filter or a query fragment. That would be the general answer, and it would hand anyone holding the API key an arbitrary read of your economy database over a key that travels in a URL.

How new features get stored — and why you won’t keep updating the gateway

Every player has a set of labelled boxes in player_data. The label is called a namespace, and inside each box is one chunk of data the gateway stores without ever looking inside it. Today exactly one box is in use, labelled inventory, and it holds gear.

When Wasteland-Z adds a stored feature — perks, a garage, saved settings — it picks a new label and starts using it. There is no list of valid labels anywhere in the gateway or the database. So a new feature costs you nothing: no schema update, no new table, no migration, and nothing for you to run.

That is the point of the design. Gateway updates are the part admins are rightly wary of, so the schema is built to need them as rarely as possible.

What is storedShared how far
Money and bankYour whole hive, always. These are real columns, not a box — the database has to do the arithmetic itself so two purchases at the same moment can never duplicate currency.
GearPer gear group (GEAR_SHARE_GROUP)
Stored vehiclesPer garage group (GARAGE_SHARE_GROUP) — a separate dial, so you can share gear but keep vehicles local, or the reverse
Account-level things (perks, settings)Your whole hive
Anything genuinely map-specificPer map
Does storing it this way slow the server down?

No. Every lookup goes straight to one row by its exact key — the database never searches through anyone else’s data to find it, so it does not get slower as your player count grows. Measured on a test database of ten thousand rows: 0.14 milliseconds per read, roughly seven thousand reads a second on a single connection. Ten servers do not come close to that.

Wasteland-Z · Keeping current

Upgrade the game and the mod.

Three moving parts, and only two of them are yours to move. The mod updates itself from the Workshop every time your server starts. The game server you update yourself, and it must be current or the mod will not load. The gateway has its own page.

Only one of these needs your hands

PartWho updates itWhat you do
The mod (Wasteland-Z)Reforger Workshop, on every server startNothing. Restart the server and you have the current build.
The game server (Arma Reforger)You, via SteamCMDRun the update before you restart. See below.
The databaseThe gateway, on its ownNothing. It applies schema changes itself and records what it applied, so it can never run twice.
The gatewayYou, one fileSee the Update Gateway tab.
Order matters: game first, then restart

The mod downloads on server start and is compiled against whatever game version it finds. Update the game before the restart that pulls the new mod, not after.

Game server

Update Arma Reforger with SteamCMD

App ID 1874900 is the dedicated server. This is the same command you used to install it — run it again and it updates in place, keeping your .json configs and profile folder.

1

Stop the server

Updating files under a running server is how you get a half-patched install.

# Linux — systemd service
sudo systemctl stop wz-server

# Windows — close the server window, or stop the service
2

Run the update

# Linux
steamcmd +force_install_dir /opt/reforger +login anonymous +app_update 1874900 validate +quit

# Windows
steamcmd +force_install_dir C:\reforger +login anonymous +app_update 1874900 validate +quit

validate re-checks every file against Steam’s manifest. It is slower, and it is what repairs a partial download from a previous run.

3

Confirm the version

The version prints in the first few lines of the server log on startup.

# Linux
grep -m1 "version" /opt/reforger/profile/logs/*/console.log

# or just watch it boot
sudo journalctl -u wz-server -f

You are looking for a line naming the build, e.g. version 1.8.0.10. If it is older than the version the current mod requires, the mod will not compile — see Troubleshooting.

4

Start the server — the mod updates itself here

On start, Reforger pulls the current Wasteland-Z build from the Workshop before loading it. Watch for the mod appearing in the log with its version.

sudo systemctl start wz-server

What each release expects

A release note that raises a requirement says so at the top. These are the ones that have moved so far.

Full release history, with what each one needed and how to move between them: RELEASES.md. Documentation for an older release is the repo at that release’s tag — the guide as it stood at 1.0.16 is at blob/v1.0.16/index.html.

FromRequirementIf you skip it
2026-08-16 onwardArma Reforger 1.8.0.10The Game script module fails to compile and the server does not start. There is no 1.7-compatible build.
2026-07-16 onwardGateway 0.7.1Admin /money drops are not written to the database and vanish on restart. Everything else keeps working.
Current (mod 1.0.16)Gateway 0.7.1This is the current requirement and it has not moved since 2026-07-16. If your gateway already says v0.7.1 at startup, there is nothing to do.
A gateway and database change is coming — it is not live yet

The next release moves gear into a new table and requires gateway 0.9.0. It is not on the Workshop yet, and you should not install it. When it ships, the mod and the gateway have to be updated in one sitting — the order does not matter, but the gap does.

Your bank will not be at risk in that upgrade. Money and bank live in the same columns, in the same table, before and after. The upgrade does not read them, move them or rewrite them. Gear is copied into the new table, not moved.

You can read the whole procedure now under the Beta channel. Do not run it against mod 1.0.16 — gear saves silently stop being read back.

Config files are never rewritten out from under you

New settings are added to your existing .conf files on first boot after an update, with defaults. Your values are kept. You do not need to delete anything, and there has never been a release that required you to.

Wasteland-Z · Keeping current

Upgrade the game and the mod.

Three moving parts, and only two of them are yours to move. The mod updates itself from the Workshop every time your server starts. The game server you update yourself, and it must be current or the mod will not load. The gateway has its own page.

Only one of these needs your hands

PartWho updates itWhat you do
The mod (Wasteland-Z)Reforger Workshop, on every server startNothing. Restart the server and you have the current build.
The game server (Arma Reforger)You, via SteamCMDRun the update before you restart. See below.
The databaseThe gateway, on its ownNothing. It applies schema changes itself and records what it applied, so it can never run twice.
The gatewayYou, one fileSee the Update Gateway tab.
Order matters: game first, then restart

The mod downloads on server start and is compiled against whatever game version it finds. Update the game before the restart that pulls the new mod, not after.

Game server

Update Arma Reforger with SteamCMD

App ID 1874900 is the dedicated server. This is the same command you used to install it — run it again and it updates in place, keeping your .json configs and profile folder.

1

Stop the server

Updating files under a running server is how you get a half-patched install.

# Linux — systemd service
sudo systemctl stop wz-server

# Windows — close the server window, or stop the service
2

Run the update

# Linux
steamcmd +force_install_dir /opt/reforger +login anonymous +app_update 1874900 validate +quit

# Windows
steamcmd +force_install_dir C:\reforger +login anonymous +app_update 1874900 validate +quit

validate re-checks every file against Steam’s manifest. It is slower, and it is what repairs a partial download from a previous run.

3

Confirm the version

The version prints in the first few lines of the server log on startup.

# Linux
grep -m1 "version" /opt/reforger/profile/logs/*/console.log

# or just watch it boot
sudo journalctl -u wz-server -f

You are looking for a line naming the build, e.g. version 1.8.0.10. If it is older than the version the current mod requires, the mod will not compile — see Troubleshooting.

4

Start the server — the mod updates itself here

On start, Reforger pulls the current Wasteland-Z build from the Workshop before loading it. Watch for the mod appearing in the log with its version.

sudo systemctl start wz-server

What each release expects

A release note that raises a requirement says so at the top. These are the ones that have moved so far.

FromRequirementIf you skip it
2026-08-16 onwardArma Reforger 1.8.0.10The Game script module fails to compile and the server does not start. There is no 1.7-compatible build.
2026-07-16 onwardGateway 0.7.1Admin /money drops are not written to the database and vanish on restart. Everything else keeps working.
When the next release lands: update the mod first

The order is not symmetric. Do the mod, then the gateway.

Mod first — the new mod calls an endpoint 0.7.1 does not have, so gear stops saving until the gateway catches up. Money and bank are unaffected, nothing already stored is lost, and it resumes on the next autosave once you update. You can see it happening.

Gateway first — everything looks fine, and that is the problem. The old mod keeps writing the legacy column and the new gateway accepts it, but the upgrade has already copied your gear into the new table. That copy then sits still while players keep playing. Update the mod days later and their gear appears to roll back to the day you upgraded the gateway.

Your bank is not at risk either way — money and bank are the same columns in the same table, before and after, and the upgrade never touches them.

Config files are never rewritten out from under you

New settings are added to your existing .conf files on first boot after an update, with defaults. Your values are kept. You do not need to delete anything, and there has never been a release that required you to.

Wasteland-Z · When it goes wrong

Troubleshooting.

Real failures from real upgrades, with the symptom you will actually see first. If your server stopped starting right after an update, start at the top — that one accounts for most of it.

The server stops starting after an update

Symptom: Can’t compile "Game" script module! and the server exits

Cause: the game server is older than the mod requires. From the 2026-08-16 release the mod targets Arma Reforger 1.8.0.10. That version removed 137 script API members with no deprecation cycle — Bohemia’s deprecation list was byte-identical to the previous version’s, so nothing warned in advance. Three of those removals sat in mod code paths.

Fix: update the game server (see the Upgrade tab) and restart. There is no older-compatible build to fall back to — the mod either compiles against the current game or the server does not start.

Symptom: GATEWAY VERSION MISMATCH banner in the server console

Cause: the gateway is older than the mod expects. The mod pings the gateway every 60 seconds and prints this until they line up.

Fix: replace gateway.py and restart the gateway — see the Update Gateway tab. This is a warning, not a block: the game keeps running while you sort it out.

The banner names both versions. The one after “Expected” is the gateway your mod needs — on mod 1.0.16 that is 0.7.1. If it says the gateway is newer than expected, you have installed the beta gateway by mistake: copy from gateway/ instead.

If you installed gateway 0.9.0 by mistake

Gear keeps saving — 1.0.16 writes the legacy column and 0.9.0 still honours it — so nothing breaks today. The catch is that starting 0.9.0 copies your gear into its new table once, and that copy then goes stale while players carry on. Update the mod later and gear appears to roll back to the day you started 0.9.0.

Fix: go back to the 0.7.1 kit — copy gateway/ from a main-branch clone over your gateway folder and restart. Do it before the next mod release, not after.

What a healthy gateway start looks like

The banner at the top names the version, the hive and the database. Check the version first — it is the fastest way to catch a gateway copied from the wrong folder.

  WastelandZ Gateway v0.7.1
  Hive ID:   default
  Database:  localhost:3306/wastelandz
[GATEWAY] Database connection: OK

On this release the gateway does not create or update tables — you run setup_database.sql yourself during setup. If it says v0.9.0, you have the beta gateway installed; see the mismatch entry above.

Symptom: REFUSING TO START — schema is INCOMPLETE

Cause: a database change that removes something is waiting, and the gateway never applies those unattended. It stops rather than serve a half-updated database, because a half-updated database is how an upgrade turns into lost data.

Fix: the message names the exact command. It will be:

python gateway.py --allow-destructive

A backup is taken automatically before anything is removed, and if that backup cannot be written the change is refused instead. Back up first anyway.

Normal upgrades never hit this — they only ever add. If you see it without having been told to expect it in a release note, ask before running it.

Symptom: REFUSING TO START — the gateway is older than the database

Cause: you rolled the gateway back further than the database. An older gateway writing to a newer database corrupts data quietly, so it refuses loudly instead.

Fix: put the newer gateway.py back, or restore the database backup that matches the older one. Do not try to force past it.

Symptom: gateway.py is 0 bytes after an update

Cause: a download that failed after the shell had already truncated the file. Redirecting output to a file empties it before the download runs, so a failed fetch leaves nothing behind.

Fix: restore your backup — cp gateway.py.bak gateway.py (Windows: copy gateway.py.bak gateway.py) — then re-download using curl -fL. The -f is what makes curl fail loudly instead of saving an error page over your file.

Saves failing, or the gateway falling behind

Symptom: requests fail in bursts — typically right after a restart

Cause: this release serves HTTP on the development server Flask provides, which starts dropping requests when a lot arrive at once — such as a full server reconnecting after a restart. A dropped save is a lost save.

Fix: there is no fix on this release; stagger restarts if you can. A production HTTP server ships with the next release and removes this ceiling.

Symptom: HTTP 503 database unavailable under load

Cause: the connection pool is exhausted. The gateway lends one database connection per in-flight request.

Fix: keep HTTP_THREADS × number_of_servers ≤ DB_POOL_SIZE. With the maximum DB_POOL_SIZE of 32: 2 servers → 16 threads, 6 servers → 5, 10 servers → 3, 16+ → 2. The gateway warns at startup when these do not line up.

Symptom: the gateway cannot connect to MySQL at all once you run several

Cause: MySQL’s default max_connections is 151, which allows roughly four gateway processes at a pool size of 32.

Fix: raise it in my.cnf:

[mysqld]
max_connections = 500
Wasteland-Z · When it goes wrong

Troubleshooting.

Real failures from real upgrades, with the symptom you will actually see first. If your server stopped starting right after an update, start at the top — that one accounts for most of it.

The server stops starting after an update

Symptom: Can’t compile "Game" script module! and the server exits

Cause: the game server is older than the mod requires. From the 2026-08-16 release the mod targets Arma Reforger 1.8.0.10. That version removed 137 script API members with no deprecation cycle — Bohemia’s deprecation list was byte-identical to the previous version’s, so nothing warned in advance. Three of those removals sat in mod code paths.

Fix: update the game server (see the Upgrade tab) and restart. There is no older-compatible build to fall back to — the mod either compiles against the current game or the server does not start.

Symptom: GATEWAY VERSION MISMATCH banner in the server console

Cause: the gateway is older than the mod expects. The mod pings the gateway every 60 seconds and prints this until they line up.

Fix: replace gateway.py and restart the gateway — see the Update Gateway tab. This is a warning, not a block: the game keeps running while you sort it out. Check the release note for that version to see what specifically is degraded in the meantime.

From gateway 0.9.0: what is degraded is gear saving — the older gateway has no endpoint for it. Money, bank and everything else keep working normally, and nothing already saved is lost. Update the gateway and gear resumes saving on the next autosave.

What a healthy gateway start looks like — from 0.9.0

Two lines near the top tell you the database is correct. If you see both, the schema is current and you can stop reading.

[SCHEMA] setup_database.sql applied (idempotent; no-op when current).
[MIGRATE] Schema up to date (version 90).

On the first start after upgrading you will see the migration run instead, which is normal and happens once:

[MIGRATE] 1 migration(s) pending; schema is at 0.
[MIGRATE] applying 0090_backfill_inventory ...
[MIGRATE]   ok -> schema now at 90

The gateway now applies the schema file itself on every start. It is written so that running it again changes nothing, and so that it can only ever add — it never drops or deletes. You do not run any SQL by hand, and there is no step to forget.

Symptom: REFUSING TO START — schema is INCOMPLETE

Cause: a database change that removes something is waiting, and the gateway never applies those unattended. It stops rather than serve a half-updated database, because a half-updated database is how an upgrade turns into lost data.

Fix: the message names the exact command. It will be:

python gateway.py --allow-destructive

A backup is taken automatically before anything is removed, and if that backup cannot be written the change is refused instead. Back up first anyway.

Normal upgrades never hit this — they only ever add. If you see it without having been told to expect it in a release note, ask before running it.

Symptom: REFUSING TO START — the gateway is older than the database

Cause: you rolled the gateway back further than the database. An older gateway writing to a newer database corrupts data quietly, so it refuses loudly instead.

Fix: put the newer gateway.py back, or restore the database backup that matches the older one. Do not try to force past it.

Symptom: gateway.py is 0 bytes after an update

Cause: a download that failed after the shell had already truncated the file. Redirecting output to a file empties it before the download runs, so a failed fetch leaves nothing behind.

Fix: restore your backup — cp gateway.py.bak gateway.py (Windows: copy gateway.py.bak gateway.py) — then re-download using curl -fL. The -f is what makes curl fail loudly instead of saving an error page over your file.

Saves failing, or the gateway falling behind

Symptom: requests fail in bursts — typically right after a restart

Cause: waitress is not installed, so the gateway falls back to a development web server. Measured: it dropped 39 of 320 requests at 32 concurrent, where waitress dropped none. Day to day a busy hive only needs about 40 requests a second, so the ceiling never shows — until 128 players reconnect at once.

Fix: pip install waitress and restart. It is auto-detected; there is no config change.

Symptom: HTTP 503 database unavailable under load

Cause: the connection pool is exhausted. The gateway lends one database connection per in-flight request.

Fix: keep HTTP_THREADS × number_of_servers ≤ DB_POOL_SIZE. With the maximum DB_POOL_SIZE of 32: 2 servers → 16 threads, 6 servers → 5, 10 servers → 3, 16+ → 2. The gateway warns at startup when these do not line up.

Symptom: the gateway cannot connect to MySQL at all once you run several

Cause: MySQL’s default max_connections is 151, which allows roughly four gateway processes at a pool size of 32.

Fix: raise it in my.cnf:

[mysqld]
max_connections = 500
Wasteland-Z · Running the server

Server administration.

Things your admins do from inside the game. On mod 1.0.16 that is the in-game admin tools and /admin restart; the out-of-band server actions described in the Beta channel are not in this release.

Server actions are not in mod 1.0.16

The server actions system — rebooting the machine, swapping maps or restarting the gateway from the in-game F8 panel, via a watchdog reading a command file — was built after 1.0.16 shipped. It arrives with the next release.

Nothing in this release reads a command file, and the wz_watchdog scripts in the kit have nothing to talk to yet. Installing the watchdog now does no harm, but it will sit idle.

You can read the full design and setup under the Beta channel. Do not run the beta mod or gateway on a live server.

Restarting the session

Wasteland-Z restarts its own session with /admin restart — countdown announcements, an autosave at the end, no watchdog and no gateway involved. That covers the ordinary case, and it is in 1.0.16.

What it cannot reach is the machine itself: a hung process, a reboot, a map swap or a gateway restart. Those still need SSH or your host’s control panel on this release.

Keeping the game server up

The kit ships start/stop scripts with an auto-restart loop — see Server Setup. If the game server process dies, the loop brings it back. That is independent of the mod version and works on 1.0.16.

Wasteland-Z · Running the server

Server administration.

Things your admins do from inside the game — including rebooting the machine, swapping maps or restarting the gateway, without anyone alt-tabbing to a control panel or opening SSH.

Server actions

Wasteland-Z Server Action Commands

Perform low-level server functions from inside Wasteland-Z. You decide which ones.

What this system does

1 · YouWrite a list of commands in a config file.
2 · The modReads your list on startup and shows it in F8 → SERVER.
3 · Your adminPicks one from the list and submits it.
4 · The modWrites SERVER_COMMAND.txt and stops there.
5 · Your watchdog scriptSees the file, deletes it, does the work.

That is the whole system. Wasteland-Z reads your list and writes one word to a file. Every action that actually happens is code you wrote, running as a user you chose.

Why you would want it

Your gateway dies at 2am. Your Discord bot hangs. A process wedges and the server needs a real reboot. Right now that means finding a laptop, opening SSH or a control panel, and logging in — while your players sit there.

This lets a trusted admin fix it from inside the game, in about five seconds, without you handing anybody a shell account.

What it can do

  • Reboot the game server
  • Cold reboot the machine itself
  • Restart the gateway
  • Restart a crashed Discord bot
  • Run a database cleanup or backup script
  • Load a different world or mission
  • Anything else you can run from a command line

Three steps

1

Pick your commands

Wasteland-Z writes this file for you on first start, already filled in with examples:

…/hf_wastelandz/configs/HFWastelandZ_server_action_commands.conf

# COMMAND           | LABEL              | DESCRIPTION

REBOOT_SERVER       | Reboot Server      | Restarts the Arma process and restarts the game.
RESTART_GATEWAY     | Restart Gateway    | Restart the database hive gateway.
DATABASE_MAINT      | Database Maintenance | Perform SQL maintenance on the database.
COLD_REBOOT_SERVER  | Cold Reboot Server | Hard reboot of the server hardware.

Keep what you want, delete the rest, add your own. COMMAND is the word your script matches on. LABEL is what the admin sees. DESCRIPTION explains it to them. A # at the start of a line switches that command off.

2

Check the two settings

Added to HFWastelandZ_server.conf automatically, with these defaults:

SERVER_ACTIONS_ENABLED 1        # 0 = feature off entirely
SERVER_ACTIONS_MIN_TIER ADMIN   # OWNER, ADMIN or MOD
3

Write your watchdog — and know what it is looking for

When an admin submits a command, Wasteland-Z creates one file:

…/hf_wastelandz/configs/SERVER_COMMAND.txt

Same folder as your other config files. It contains exactly this:

# HF server command — consume and delete
COMMAND REBOOT_SERVER
BY      Motavar (76561198...)
AT      2026-08-24 09:14:22
END

Your watchdog watches for that file, reads the COMMAND line, deletes it, and runs whatever you decided that command means. How to read it — and why each step is there — is explained under the scripts below.

Pick a language below, change the paths, put it on a timer.

Ten lines, and nothing to download

Read it, understand it, type it yourself

Both scripts below are short enough to read in under a minute and use nothing but what is already on your server — POSIX shell, or Python with two standard imports. No packages to install, no URL to fetch, nothing downloaded at runtime.

You do not have to trust anything we shipped. Retype these into your own file and you have audited your entire supply chain.

Bash

#!/bin/sh
F=/opt/reforger/profile/hf_wastelandz/configs/SERVER_COMMAND.txt

[ -f "$F" ] || exit 0                        # no command waiting, done
grep -q '^END' "$F" || exit 0                # still being written, come back later

CMD=$(awk '/^COMMAND/{print $2}' "$F" | tr -d '\r')
rm -f "$F"                                   # delete BEFORE doing the work

case "$CMD" in
  REBOOT_SERVER)      systemctl restart wz-server1 ;;
  RESTART_GATEWAY)    systemctl restart wz-gateway ;;
  COLD_REBOOT_SERVER) shutdown -r now ;;
  *)                  logger "unknown HF command: $CMD" ;;
esac

Run it every 10 seconds from cron, or a systemd timer:

* * * * * /opt/wz/watchdog.sh

Python

import os, subprocess

F = "/opt/reforger/profile/hf_wastelandz/configs/SERVER_COMMAND.txt"

ACTIONS = {
    "REBOOT_SERVER":      ["systemctl", "restart", "wz-server1"],
    "RESTART_GATEWAY":    ["systemctl", "restart", "wz-gateway"],
    "COLD_REBOOT_SERVER": ["shutdown", "-r", "now"],
}

if not os.path.isfile(F):
    raise SystemExit                                  # nothing waiting

lines = open(F, encoding="utf-8").read().splitlines()
if "END" not in [l.strip() for l in lines]:
    raise SystemExit                                  # still being written

cmd = next((l.split()[1] for l in lines if l.startswith("COMMAND")), "")
os.remove(F)                                          # delete BEFORE the work

if cmd in ACTIONS:
    subprocess.run(ACTIONS[cmd])

On Windows, swap the actions for ["powershell", "-c", "Restart-Service wz-server1"] and run it from Task Scheduler.

How to read the file

Five lines. Only one of them matters to your script:

# HF server command — consume and delete   <- comment, skip it
COMMAND REBOOT_SERVER                       <- THIS is what you want
BY      Motavar (76561198...)                <- who submitted it, for your logs
AT      2026-08-24 09:14:22                  <- when, for your logs
END                                          <- the file is finished

Getting the command is one line in any language — find the line starting with COMMAND, take the second word:

# bash
CMD=$(awk '/^COMMAND/{print $2}' "$F" | tr -d '\r')

# python
cmd = next(l.split()[1] for l in lines if l.startswith("COMMAND"))

# windows batch
for /f "tokens=2" %%A in ('findstr /b /c:"COMMAND" "%FILE%"') do set "CMD=%%A"

Why you must check for END first

Because the file is written in place, not swapped in finished.

Normally a program writes a temporary file and renames it over the top. Renaming is instant, so a reader sees either the old file or the complete new one — never a half-written one. The game engine has no rename function, so Wasteland-Z has to write the real file line by line.

If your watchdog looks during that write, it can read this:

COMMAND REBOOT_SER

…and act on a command that does not exist — or worse, one that happens to be a prefix of a real one. END is written last, so its presence is the file saying “I am complete, safe to read”.

No END means come back in a few seconds. The file is not broken; it is not finished.

Why strip the carriage return

If the file ever picks up Windows line endings, the command you read is REBOOT_SERVER followed by an invisible \r. It prints identically, compares unequal to REBOOT_SERVER, and matches nothing.

Nothing errors. The admin clicks the button, the file disappears, and the server does not reboot. One tr -d removes the entire problem.

Windows batch does not need itfor /f strips them itself. Bash and Python do.

Why delete before doing the work

If you act first and your script dies mid-reboot, the file is still sitting there and the command runs again next pass. Delete first, and a crash at the wrong moment loses one command instead.

Losing a reboot is a shrug. Repeating a database wipe is not.

If the delete fails — permissions, file locked — do nothing at all. A file you cannot remove gets picked up on every single pass, which reboots the machine in a loop.

Match the exact filename

Look for SERVER_COMMAND.txt exactly, never a wildcard like SERVER_COMMAND*. The example file sitting beside it contains a real command block, and a wildcard matches that too.

The full versions, if you want them

All three do the same job and ship in this download, in server/. They add logging, argument checking and clearer errors to the ten-line versions above — still no dependencies, still nothing fetched at runtime. Click to read before you run.

Bash / POSIX shell — full file (79 lines)

Uses only sh, grep, awk, tr and rm. Run from cron or a systemd timer, or with --loop. View or download on GitHub →

#!/bin/sh
# Wasteland-Z server action watchdog — POSIX shell.
#
#   ./wz_watchdog.sh /opt/reforger/profile/hf_wastelandz/configs
#   ./wz_watchdog.sh /path/to/configs --loop
#
# Runs one pass and exits, so cron or a systemd timer can drive it. --loop
# stays resident and checks every 5 seconds.
#
# EDIT ONE THING: the run_command() case block. Nothing else.
#
# No dependencies. Uses sh, grep, awk, tr and rm — all already on your server.

CONFIG_DIR="$1"
[ -n "$CONFIG_DIR" ] || {
    echo "usage: $0 <configs-folder> [--loop]" >&2
    exit 1
}
FILE="$CONFIG_DIR/SERVER_COMMAND.txt"

log() { echo "[wz-watchdog] $*"; }

# ---------------------------------------------------------------------------
# EDIT HERE. Each command name from HFWastelandZ_server_action_commands.conf
# gets a branch. Anything not listed is logged and ignored — an unknown command
# should do nothing, never guess.
# ---------------------------------------------------------------------------
run_command() {
    case "$1" in
        REBOOT_SERVER)       systemctl restart wz-server1 ;;
        RESTART_GATEWAY)     systemctl restart wz-gateway ;;
        RESTART_DISCORD_BOT) systemctl restart wz-discord ;;
        COLD_REBOOT_SERVER)  shutdown -r now ;;

        # Your own — point at a script and put the logic there:
        # MAP_LOAD_PLUNDER)  /opt/wz/load_map.sh plunder ;;
        # DATABASE_MAINT)    /opt/wz/db_maint.sh ;;

        *) log "unknown command '$1' — ignored" ; return 0 ;;
    esac
    log "$1 finished with status $?"
}

one_pass() {
    [ -f "$FILE" ] || return 0

    # No END line means the mod is still writing it. The game cannot rename a
    # file atomically, so it writes in place and a fast poll can catch it
    # half-finished. Not corrupt — just not done. Look again next pass.
    grep -q '^END' "$FILE" || return 0

    # tr -d '\r' is not optional. If the file ever picks up Windows line
    # endings the command becomes "REBOOT_SERVER" plus a carriage return: it
    # prints identically, matches nothing, and every branch falls through with
    # no error at all.
    cmd=$(awk '/^COMMAND/{print $2; exit}' "$FILE" | tr -d '\r')

    # DELETE BEFORE ACTING. If this script dies after acting but before
    # deleting, the command runs again on the next pass. Losing a reboot is
    # cheap; repeating a database wipe is not.
    if ! rm -f "$FILE"; then
        log "could not delete $FILE — refusing to act, or it would repeat forever"
        return 1
    fi

    [ -n "$cmd" ] || { log "no COMMAND line found — ignored"; return 0; }
    log "running $cmd"
    run_command "$cmd"
}

if [ "$2" = "--loop" ]; then
    log "watching $FILE"
    while true; do
        one_pass
        sleep 5
    done
else
    one_pass
fi
Python 3 — full file (157 lines)

Standard library only - os, subprocess, sys, time. Same on Linux and Windows. View or download on GitHub →

#!/usr/bin/env python3
"""Wasteland-Z server action watchdog.

Watches for SERVER_COMMAND.txt, runs whatever YOU decide the command means,
deletes the file. Works on Linux and Windows. Standard library only.

    python3 wz_watchdog.py /path/to/profile/hf_wastelandz/configs

Run it from cron, a systemd timer, or Task Scheduler - it does one pass and
exits, so there is no daemon to babysit. Add --loop if you would rather it
stay resident.

WHAT THE MOD DOES
    An admin picks an action in F8 -> SERVER. The mod writes SERVER_COMMAND.txt
    into the configs folder. That is all it does - it never runs anything. The
    word it wrote means whatever this script decides it means.

WHAT YOU EDIT
    ACTIONS, below. Nothing else. Keep the keys identical to the COMMAND column
    in HFWastelandZ_server_action_commands.conf.
"""

import os
import subprocess
import sys
import time

# ---------------------------------------------------------------------------
# EDIT THIS. Keys must match the COMMAND column in the .conf exactly.
# Anything not listed here is logged and ignored, which is the safe default:
# an unknown command should do nothing, not guess.
#
# Values are argv LISTS, never strings, and are run WITHOUT a shell. That is
# deliberate - with shell=False nothing in the file can be interpreted as shell
# syntax, so a malformed or hostile command cannot become an injection.
# ---------------------------------------------------------------------------
ACTIONS = {
    # --- Linux examples -----------------------------------------------------
    "REBOOT_SERVER":       ["systemctl", "restart", "wz-server1"],
    "RESTART_GATEWAY":     ["systemctl", "restart", "wz-gateway"],
    "RESTART_DISCORD_BOT": ["systemctl", "restart", "wz-discord"],
    "COLD_REBOOT_SERVER":  ["shutdown", "-r", "now"],

    # --- Windows equivalents: swap the lines above for these ---------------
    # "REBOOT_SERVER":      ["powershell", "-c", "Restart-Service wz-server1"],
    # "RESTART_GATEWAY":    ["powershell", "-c", "Restart-Service wz-gateway"],
    # "COLD_REBOOT_SERVER": ["shutdown", "/r", "/t", "0"],

    # --- Your own: point at a script and put the logic there ---------------
    # "MAP_LOAD_PLUNDER":  ["/opt/wz/load_map.sh", "plunder"],
    # "DATABASE_MAINT":    ["/opt/wz/db_maint.sh"],
}

FILENAME = "SERVER_COMMAND.txt"


def log(msg):
    print(f"[wz-watchdog] {msg}", flush=True)


def read_command(path):
    """Return the COMMAND word, or None if the file is not ready or not valid.

    Two guards, both load-bearing:

    1. NO 'END' LINE MEANS STILL BEING WRITTEN. The game engine cannot rename a
       file atomically, so the mod writes in place and a watchdog polling every
       second can catch it half-finished. A file without END is not corrupt -
       it is simply not done yet, so we leave it and look again next pass.

    2. STRIP CARRIAGE RETURNS. If the file ever picks up Windows line endings,
       the command becomes "REBOOT_SERVER\\r", which prints identically to
       "REBOOT_SERVER", compares unequal to it, and silently matches nothing.
       Nothing errors; the admin just watches their file vanish and nothing
       happen. One strip removes the entire class of problem.
    """
    try:
        with open(path, "r", encoding="utf-8", errors="replace") as fh:
            lines = [ln.rstrip("\r\n") for ln in fh]
    except OSError as exc:
        log(f"cannot read {path}: {exc}")
        return None

    if not any(ln.strip() == "END" for ln in lines):
        return None                      # still being written

    for ln in lines:
        if ln.startswith("COMMAND"):
            parts = ln.split()
            if len(parts) >= 2:
                return parts[1].strip()
    return None


def run_once(folder):
    path = os.path.join(folder, FILENAME)
    if not os.path.isfile(path):
        return

    cmd = read_command(path)
    if cmd is None:
        return                           # incomplete; try again next pass

    # DELETE BEFORE ACTING, and the order matters.
    #
    # Delete-then-act can lose a command if this process dies in between.
    # Act-then-delete can REPEAT one. For a reboot a repeat is survivable; for
    # anything destructive it is not, and a lost command is always the cheaper
    # mistake. The mod makes the same call when it disarms a pending wipe
    # before performing it.
    try:
        os.remove(path)
    except OSError as exc:
        log(f"could not delete {path} ({exc}) - refusing to act, or it would "
            f"run again on every pass")
        return

    argv = ACTIONS.get(cmd)
    if not argv:
        log(f"unknown command {cmd!r} - ignored. Add it to ACTIONS if you want it.")
        return

    log(f"running {cmd}: {' '.join(argv)}")
    try:
        # shell=False (the default with a list) is the security boundary here.
        result = subprocess.run(argv, shell=False, timeout=120)
        log(f"{cmd} exited {result.returncode}")
    except subprocess.TimeoutExpired:
        log(f"{cmd} timed out after 120s")
    except OSError as exc:
        log(f"{cmd} failed to start: {exc}")


def main():
    args = [a for a in sys.argv[1:] if a != "--loop"]
    loop = "--loop" in sys.argv
    if not args:
        print(__doc__)
        return 1

    folder = args[0]
    if not os.path.isdir(folder):
        log(f"not a folder: {folder}")
        return 1

    if not loop:
        run_once(folder)
        return 0

    log(f"watching {os.path.join(folder, FILENAME)}")
    while True:
        run_once(folder)
        time.sleep(5)


if __name__ == "__main__":
    sys.exit(main())
Windows batch — full file (89 lines)

Uses only findstr, del and net. Point Task Scheduler at it on a one-minute trigger. View or download on GitHub →

@echo off
setlocal EnableDelayedExpansion
rem ==========================================================================
rem  Wasteland-Z server action watchdog - Windows batch.
rem
rem    wz_watchdog.cmd C:\reforger\profile\hf_wastelandz\configs
rem
rem  Runs one pass and exits. Point Task Scheduler at it on a 1-minute
rem  trigger, or call it from a loop of your own.
rem
rem  EDIT ONE THING: the :run_command labels near the bottom. Nothing else.
rem
rem  No dependencies. Uses only findstr, del and net - all built in.
rem ==========================================================================

if "%~1"=="" (
    echo usage: %~nx0 ^<configs-folder^>
    exit /b 1
)

set "CONFIG_DIR=%~1"
set "FILE=%CONFIG_DIR%\SERVER_COMMAND.txt"

rem Nothing waiting. This is the normal case on almost every run.
if not exist "%FILE%" exit /b 0

rem No END line means the mod is still writing the file. The game cannot rename
rem atomically, so it writes in place and a fast poll can catch it half done.
rem Not corrupt - just not finished. Come back next time.
findstr /b /c:"END" "%FILE%" >nul 2>&1
if errorlevel 1 exit /b 0

rem Pull the second token off the COMMAND line.
rem
rem NOTE: 'for /f' strips carriage returns by itself, so the CRLF problem that
rem bites shell scripts does not arise here. Mentioned because the bash and
rem Python versions need an explicit strip and someone comparing them will ask.
set "CMD="
for /f "tokens=2" %%A in ('findstr /b /c:"COMMAND" "%FILE%"') do set "CMD=%%A"

rem DELETE BEFORE ACTING. If this script dies after acting but before deleting,
rem the command runs again on the next pass. Losing a reboot is cheap;
rem repeating a database wipe is not.
del /f /q "%FILE%" >nul 2>&1
if exist "%FILE%" (
    echo [wz-watchdog] could not delete "%FILE%" - refusing to act, or it would repeat forever
    exit /b 1
)

if "!CMD!"=="" (
    echo [wz-watchdog] no COMMAND line found - ignored
    exit /b 0
)

echo [wz-watchdog] running !CMD!
call :run_command "!CMD!"
exit /b 0

rem ==========================================================================
rem  EDIT HERE. One block per command from
rem  HFWastelandZ_server_action_commands.conf. Anything not listed is logged
rem  and ignored - an unknown command should do nothing, never guess.
rem ==========================================================================
:run_command
set "C=%~1"

if /i "%C%"=="REBOOT_SERVER" (
    net stop wz-server1 & net start wz-server1
    goto :eof
)
if /i "%C%"=="RESTART_GATEWAY" (
    net stop wz-gateway & net start wz-gateway
    goto :eof
)
if /i "%C%"=="RESTART_DISCORD_BOT" (
    net stop wz-discord & net start wz-discord
    goto :eof
)
if /i "%C%"=="COLD_REBOOT_SERVER" (
    shutdown /r /t 0
    goto :eof
)

rem Your own - point at a script and put the logic there:
rem if /i "%C%"=="MAP_LOAD_PLUNDER" ( call C:\wz\load_map.cmd plunder & goto :eof )
rem if /i "%C%"=="DATABASE_MAINT"   ( call C:\wz\db_maint.cmd        & goto :eof )

echo [wz-watchdog] unknown command "%C%" - ignored
goto :eof
Edit one thing in each

The command list — run_command() in bash, ACTIONS in Python, :run_command in batch. Every one is at the top or clearly marked. Nothing else needs touching.

A test file is waiting for you already

SERVER_COMMAND.example appears in the same folder on first start, with a real command block in it. Copy it to SERVER_COMMAND.txt to test your watchdog before anyone submits anything for real.

Running it day to day

QuestionAnswer
Nothing happened when I submittedYour watchdog is not running, or does not know that command. F8 says still pending rather than claiming success, and the file will still be sitting in the configs folder.
Can I cancel?REMOVE deletes the file — but only helps if your watchdog has not read it yet.
Two admins submit at onceCannot happen. One command waits at a time; the second is refused until the first is cleared.
Who can use it?SERVER_ACTIONS_MIN_TIER. Below MOD is refused — these reboot machines.
What runs as root?Only what you choose. Your watchdog runs as whatever user you start it as; Wasteland-Z has no say in it.
I just want to restart the game/admin restart already does that, with a countdown and a save. None of this needed.
If you would rather not write one

A fuller version ships in this download at server/wz_watchdog.py — same logic, plus logging, timeouts and a --loop mode. Standard library only. The ten-line version above does the same job if you prefer something you can read at a glance.

Ready-made

A watchdog that ships with the kit

You do not have to write one from scratch. server/wz_watchdog.py is in this download — standard library only, works on Linux and Windows, about 100 lines and most of that is comments.

# one pass, then exits — run it from cron, a systemd timer, or Task Scheduler
python3 wz_watchdog.py /opt/reforger/profile/hf_wastelandz/configs

# or leave it resident, checking every 5 seconds
python3 wz_watchdog.py /opt/reforger/profile/hf_wastelandz/configs --loop

You edit exactly one thing — the ACTIONS dictionary at the top. Keys must match the COMMAND column in your .conf:

ACTIONS = {
    "REBOOT_SERVER":       ["systemctl", "restart", "wz-server1"],
    "RESTART_GATEWAY":     ["systemctl", "restart", "wz-gateway"],
    "COLD_REBOOT_SERVER":  ["shutdown", "-r", "now"],

    # Windows equivalents
    # "REBOOT_SERVER": ["powershell", "-c", "Restart-Service wz-server1"],

    # your own — point at a script and put the logic there
    # "MAP_LOAD_PLUNDER": ["/opt/wz/load_map.sh", "plunder"],
}
Why the values are lists, not strings

They are run with shell=False, so nothing in the command file can ever be interpreted as shell syntax. That is the security boundary: even a malformed or hostile SERVER_COMMAND.txt cannot become a command injection, because no shell is involved to inject into.

A command not listed in ACTIONS is logged and ignored. An unknown action should do nothing, never guess.

If the delete fails, it refuses to act

A file it cannot remove would be picked up again on every single pass — rebooting the machine in a loop. It logs the failure and does nothing instead.

Submitting, cancelling, and what you are told

  • One command at a time. If one is already pending, submitting another is refused until it is removed — so two admins cannot stack requests without knowing which one lands.
  • Cancel is best-effort. REMOVE deletes the file, which works only if the watchdog has not already read it. Once it has, the reboot is coming and deleting the file changes nothing.
  • Confirmation is the file disappearing. F8 says waiting for the watchdog, then warns if it is still pending after a minute. It never claims success it cannot verify.
Restarting the game does not need any of this

Wasteland-Z already restarts its own session with /admin restart — countdown announcements, an autosave at the end, no watchdog and no gateway involved. Server actions are for what that cannot reach: a hung process, the machine itself, a map swap, or an upgrade.