Your server runs
You already start the game and it accepts players. Guard doesn't manage the game process for you.
You already run an AA, Spearhead, or Breakthrough server on Linux. This guide adds
Guard on top of it: paste a few cvars into server.cfg and run one installer
that sets the Python sidecar up as a systemd service — it
survives reboots, restarts itself, and logs to journald. Nothing about your existing
server changes — not the launcher, not the binaries, not your firewall. Guard needs no
game files either: since Guard 1.0.0 the AC badge is drawn in the players' own game. Private
stats are optional and take two small PK3s from the bundle.
You already start the game and it accepts players. Guard doesn't manage the game process for you.
The sidecar is Python and the optional firewall layer uses nft. Ubuntu 24.04 is the tested target.
rconPassword "..." is set in server.cfg. The sidecar uses it to kick; the password never leaves your box.
You extract the bundle anywhere (your home directory is fine) and run the installer from there. The installer copies the sidecar to a fixed system location and keeps your secrets in a root-only config file — never inside the game folder.
/home/your-user/
moharena-guard-server-owner/ # extracted bundle — run the installer from here
install-systemd.sh # the one script you run (as root)
server-owner-install.sh # apt packages helper (the installer calls apt too)
sidecar.py # copied to /opt by the installer
moharena-guard@.service # systemd unit template
SERVER_OWNER_GUIDE.md # offline copy of this guide
stats/ # optional private stats PK3s, one folder per game and runtime
your-existing-game-server/
main/ | mainta/ | maintt/
qconsole.log # game log stays with the game
/opt/moharena-guard/sidecar.py # installed sidecar (self-updates live here)
/etc/moharena/guard-<game>.env # your settings — root-only, mode 600
/etc/systemd/system/moharena-guard@.service
aa-original, aa-openmohaa, sh-original, sh-openmohaa, bt-original, bt-openmohaa.203.0.113.10:12205. The token mtc generates is bound to this exact address.7. You'll paste it as MOHARENA_GUARD_SERVER_ID.mohg_…. Shown on the website — treat it like a password.<ip>:<port> you sent — mtc echoes it back so you paste the exact string.
The bundle contains the installer (install-systemd.sh), the systemd unit
template, sidecar.py, the optional stats PK3s and an offline copy of this guide. Download it straight onto
the VPS (as whatever user already runs your game):
server.cfg
The sidecar reads qconsole.log, so the engine has to write the connect / chatter lines it
parses. Paste these at the end of your existing server.cfg (logfile only
controls flushing). Kicks and the rest of the policy are set on the Guard website, not in
server.cfg.
logfile values: 0 = off, 1 = on but
buffered (the newest lines can be lost if the server crashes), 2 = on and
flushed after every line. Any value above 1 behaves the same as 2,
so 2 and 3 are equivalent. Use 2 (or 3) so Guard
sees events in real time — 1 works but can lag behind.
qconsole.log must be where Guard expects it. Point
MOHARENA_QCONSOLE (next step) at the file the engine actually writes — usually your game's
main folder (main/ for AA, mainta/ for SH, maintt/
for BT). If your engine writes it somewhere else (e.g. the server root), symlink it into place so both
paths point at the same file:
ln -s /home/mohaa/server2/qconsole.log /home/mohaa/server2/main/qconsole.log
Upgrading from an older Guard setup? Since Guard 1.0.0 the AC badge is drawn in the
players' own game, so the old server AC HUD can go at your next game server restart: delete
zz_moharena_ac_hud*.pk3, remove the exec global/ac_player_hud.scr line if you added it
to a precache script of your own, and drop the moh_ac_hud, moh_ac_active and
moh_ac_status* lines from server.cfg. A zzzzzz_stats*.pk3 or
zzzzzzzzzz_DMPrecacheFix*.pk3 from an older Guard bundle goes too; to keep private stats, use both
PK3s from the new bundle instead (see Optional: private stats). The other
moh_ac_* lines, such as moh_ac_kick_no_ac, do nothing on Guard servers (the
Policy tab decides), so they can go too. Until the HUD PK3 is gone, keep its badge off with
seta moh_ac_hud "0"; the Config tab can edit server.cfg.
Optional: round countdown. Players who run the MoH Arena native client see a short countdown at every round start and are held still until it ends. It is off by default; set a length from 1 to 10 seconds to turn it on. It can be changed while the server runs.
Pass the three values from mtc plus your existing RCON password, qconsole.log path, and game
as flags. It installs and starts moharena-guard@<game> — which survives
reboots, restarts itself after crashes and self-updates, and logs to journald.
--server-cfg-path points at your server.cfg. Set it now —
it's what powers the Config tab on the website (view and edit your live config from the dashboard).
Without it that tab can't fetch anything.
Recovery copies. The first time you save an edit from the Config tab, Guard keeps two backups
next to your server.cfg so you can recover over SSH if an edit ever breaks the server:
server.cfg.moharena-bak (the pristine original) and server.cfg.moharena-prev
(the version from just before your last edit).
--runtime is original for the retail game (the default) or
openmohaa if you run the OpenMoHAA engine. It must match how you registered the server with mtc,
and it also picks your folder in stats/ if you add private stats.
Prefer not to type flags? Open install-systemd.sh, fill in the
CONFIG block at the top (same field names), and run sudo ./install-systemd.sh
with no flags. Migrating from the old tmux wrapper? See MIGRATION.md in the bundle.
CPU safety limit. New installations cap Guard at 30% of one CPU core. For an
existing service, apply the same persistent limit immediately with
sudo systemctl set-property moharena-guard@sh.service CPUQuota=30%, replacing
sh with its instance name and repeating for each Guard instance. Sidecar 1.1.13 also
lowers its own scheduler priority to nice level 10, which reaches existing clients through the normal
Guard update and makes Guard yield CPU to the game without needing root.
Several servers on one box? Run the installer once per server. Different games are
automatic (each --game is its own service). For two servers of the same game
(e.g. two aa servers), give each a distinct --instance name — otherwise the
installer stops to stop you overwriting the first:
sudo ./install-systemd.sh --game aa --instance aa1 --server-id 7 …
sudo ./install-systemd.sh --game aa --instance aa2 --server-id 8 …
About the firewall layer. Guard blocks players two ways: Layer 1 RCON-kicks
(always on, no setup), and Layer 2 kernel-level packet drops via nftables.
With the systemd service this just works: the unit grants the sidecar the one kernel capability
nft needs (CAP_NET_ADMIN), so there is no sudoers entry and no running as
root. Set MOHARENA_GUARD_FIREWALL_BLOCKS=0 in the env file if you want RCON kicks only.
All your settings live in /etc/moharena/guard-<game>.env (root-only,
mode 600) — the same options the old wrapper had, now with comments. Edit any value, then apply
it with sudo systemctl restart moharena-guard@<game>:
Open your server's page on Guard. The Overview tab shows the last heartbeat (should refresh every few
seconds). Connect a player and the Players tab fills in within 10 seconds. Players with Guard 1.0.0
or later see AC Active - N/M in their own game.
The badge counts come from your sidecar's player list on the Guard website. If players see
AC status unavailable, the website has no fresh player list from your server: check that
the sidecar runs and that the heartbeat is recent.
Private stats fill the Stats tab of your server page on the Guard website. Only you and the Guard
owners see them, unless you pick Public opt-in under Server analytics on the Policy
tab to show them on the public Live Servers page too. Guard works the same without them. They take two PK3s from
the stats/ folder of the bundle, in the folder for your game and runtime (the same
--game and --runtime you gave the installer):
moharena-guard-server-owner/stats/
aa-original/ # AA, retail game zzzzzz_stats_aa.pk3 + zzzzzzzzzz_DMPrecacheFix_aa.pk3
sh-original/ # SH, retail game zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
bt-original/ # BT, retail game zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
aa-openmohaa/ # AA on OpenMoHAA zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
sh-openmohaa/ # SH on OpenMoHAA zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
bt-openmohaa/ # BT on OpenMoHAA zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
README.txt
-openmohaa folder for their game: its
zzzzzz_stats.pk3 is the OpenMoHAA version. Every other server uses the -original folder.main/ for AA, mainta/
for SH, maintt/ for BT. Delete any older zzzzzz_stats*.pk3 and
zzzzzzzzzz_DMPrecacheFix*.pk3 there first, so only these copies load.
Your server already runs its own global/DMprecache.scr, for example from
another mod? Keep it, copy only the stats PK3, and add these two lines at the end of that script, after
level waittill spawn. The precache PK3 does the same for servers without one.
The stats PK3 also replaces global/obj_dm.scr to log bomb plants and defuses. The sidecar sends
stats in batches: every 30 seconds, or sooner once 100 events are waiting
(MOHARENA_GUARD_STATS_FLUSH_INTERVAL and MOHARENA_GUARD_STATS_MAX_BUFFER in the env
file). To turn stats off, set Guard mode back to AC only and delete both PK3s at the next
restart. Verification, blocks, player lists and kicks work either way.
Here are some useful commands that you might want to use.
systemctl status moharena-guard@shIs the sidecar running? Swap sh for aa/bt on those games.journalctl -u moharena-guard@sh -fFollow the live sidecar log.journalctl -u moharena-guard@sh | grep FIREWALL_LAYERDid the Layer 2 nftables probe succeed at startup?sudo nft list table inet moharena_guardShow the live kernel set of dropped IPs/CIDRs.sudo systemctl restart moharena-guard@shApply changes after editing /etc/moharena/guard-sh.env or rotating the token.sudo systemctl stop moharena-guard@shStop the sidecar. start brings it back; it also starts on boot.Paste the logging cvars into server.cfg and run install-systemd.sh from the bundle with
the values mtc sends you; the sidecar does the rest. Game files are only for optional private stats.
No. Setup is entirely self-serve. mtc only registers a Guard record on the website and DMs you three values. Nothing on your machine talks to mtc directly — the sidecar talks only to guard.moharena.com.
No. The password stays in /etc/moharena/guard-<game>.env on your VPS — readable by root only. The sidecar uses it locally to issue kicks. The website never sees it.
Only if you tell it to. New servers start with "log only, no kicks". You can change the policy on the website's Policy tab. Changes apply within ~30 seconds without restarting anything.
Blocks are exact-match, per-player or per-IP, manual entries (one button on the Players tab). Rules are patterns — IP exact, IP range, CIDR, wildcard prefix, country code, ASN, or "every VPN/proxy IP". A matching rule with action Permanent block auto-creates a block.
Their game gets no fresh player list for your server from Guard. Check that the sidecar runs
(systemctl status moharena-guard@<game>) and that the Overview tab shows a recent heartbeat.
The badge comes back on its own once the sidecar is sending again.
Since Guard 1.0.0 the badge is drawn by Guard in each player's own game, not by your server, so players need Guard 1.0.0 or later running. Players without it see no badge; no file or cvar on your server changes that.
Check that Guard mode on the Policy tab is AC + private stats, that both PK3s
from your stats/ folder are in the game folder with no older copies next to them, and that the game
server was restarted after you copied them. OpenMoHAA servers need the -openmohaa folder. See
Optional: private stats.
Your client has the auth_token cvar saved from a previous launcher session. The sidecar now only marks players as verified when the launcher is actively heartbeating — if you see this, make sure you are on the latest sidecar (mtc can push it remotely, or update by hand) and run sudo systemctl restart moharena-guard@<game>.
No. The installer runs as root once (to write the system files), but the service itself runs as your game-server user. The nftables firewall layer works anyway: the systemd unit grants the one kernel capability nft needs (CAP_NET_ADMIN) — no sudoers entry, no root process.
MOHARENA_GUARD_FIREWALL_LAYER mode=rcon-only?The sidecar tried nft at startup and couldn't call it. RCON-kick still works fine; you just don't get the kernel-level packet drop. With the systemd service this shouldn't happen (the unit grants the capability and the right PATH) — check nft --version is installed, or set MOHARENA_GUARD_FIREWALL_BLOCKS=0 in the env file to silence the message.
Usually you don't have to do anything — mtc pushes releases from the website, the sidecar swaps its
own file, and systemd restarts it on the new code within seconds. To update by hand: replace
/opt/moharena-guard/sidecar.py with the latest from the bundle
and run sudo systemctl restart moharena-guard@<game>. To refuse remote pushes entirely, set
MOHARENA_SIDECAR_AUTO_UPDATE=0 in the env file.
Run sudo systemctl disable --now moharena-guard@<game>. Your server is back to its pre-Guard
state. The logging cvars in server.cfg are harmless when the sidecar isn't running. If you copied the
stats PK3s, or an older bundle had you copy Guard PK3s or precache lines, you can delete those too.
Send the output of journalctl -u moharena-guard@<game> -n 50 --no-pager, the Guard server ID, and whatever the in-game HUD reads. Don't paste your mohg_... token or the RCON password.