RivalityFX Docsrivalityfx.com ↗

Deploy on Linux

This page takes a Linux machine from nothing to a live RivalityFX server players can connect to. It targets a plain Linux VPS (OVH, Hetzner, a cloud VM…) — the game server only. The public website + leaderboard are a separate, optional layer (§9).

The repo ships a small Makefile that automates the three setup chores — fetch the runtime, create the secrets file, run — so most of this page is four make commands and a systemd unit.

your players ──(UDP/TCP :30120)──▶  Linux VM  ───────────────┐
                                    FXServer (run.sh)         │ optional, outbound HTTPS
                                    └─ RivalityFX resources   └──▶ stats API + website (§9)

Port note. This repo uses FiveM's default 30120 (TCP + UDP). If you change it in your server.cfg endpoint_add_* lines, use that port wherever you see 30120 below — it must match your firewall rule.


1. Prerequisites

  • A Linux VM. The FXServer Linux artifact is a self-contained build (it ships its own alpine/ runtime), so the host distro barely matters — Debian 12 / Ubuntu 22.04+ are the well-trodden path. Size for your peak: 2 vCPU / 4–8 GB RAM comfortably runs a handful of concurrent scenes (rfx_max_scenes, default 8).
  • A cfx.re server license key — free, from portal.cfx.re. Required to go public. Lock it to your server's static IP in the portal.
  • The RivalityFX resources on the box (this repository — resources/, server.cfg, boot.cfg, Makefile, server_secrets.cfg.example).
  • Shell tools the Makefile uses to fetch the runtime:
    sudo apt update && sudo apt install -y curl jq xz-utils ca-certificates make

2. Put the code on the box

Ship the repo to a working directory — /opt/rivalityfx is a good choice. You only need the server-side tree; leave the build runtime and the website behind:

# from your workstation — exclude the things the game server never reads
rsync -a --exclude alpine/ --exclude cache/ --exclude fx.tar.xz \
         --exclude www/ --exclude services/ --exclude node_modules/ --exclude .git/ \
         ./  user@your-vm:/opt/rivalityfx/

The pieces that matter at the root:

File / dirRole
resources/Every resource ([platform]/, [modes]/, [packs]/, [systems]/).
server.cfgThe one file FXServer reads at boot. The full reference is Server configuration.
boot.cfgexec'd last — opens your public arenas (§7).
server_secrets.cfg.exampleTemplate for the gitignored secrets file (next step).
Makefileruntime / secrets / run automate setup.

3. Download the FXServer runtime

cd /opt/rivalityfx
make runtime

This resolves the latest recommended Linux FXServer build, downloads it, and extracts alpine/ + run.sh next to your config. Re-running is a no-op once present; to force a fresh build later, make clean then make runtime. Pick a different channel with FX_CHANNEL (recommended · latest · optional · critical):

make runtime FX_CHANNEL=latest

Pin the game build, not just the runtime. server.cfg sets sv_enforceGameBuild (this repo: 3258) so every client runs the GTA assets/natives the arenas were built against. Bumping the FXServer runtime and bumping the game build are independent — change the game build only deliberately, and re-test maps after.


4. Set your secrets

make secrets        # copies server_secrets.cfg.example → server_secrets.cfg (if missing)

Then edit server_secrets.cfg and set your license key. This file is gitignored — secrets never live in server.cfg:

sv_licenseKey "cfxk_…"          # from portal.cfx.re — lock it to this VM's IP
set steam_webApiKey ""          # optional; "" or "none" is fine
#set rcon_password "CHANGE_ME"  # only if you use RCON
#set rfx_api_token "CHANGE_ME"  # only when rfx_store=api (the website layer — §9)

You can stop here for a public server: the license key is the only required secret. The API token is for the optional stats stack (§9).


5. A minimal server.cfg

The repo ships a complete, annotated server.cfg — the full line-by-line reference is Server configuration. The smallest file that boots a playable 1v1 server (KVP store, no website) is:

endpoint_add_tcp "0.0.0.0:30120"
endpoint_add_udp "0.0.0.0:30120"
sv_hostname "RivalityFX | 1v1 Duel"
sv_enforceGameBuild 3258
set onesync on
sv_maxclients 32
 
# CFX backbone
ensure chat
ensure mapmanager
ensure spawnmanager
ensure sessionmanager
ensure baseevents
ensure hardcap
 
# Platform (sdk FIRST) + one mode + one arena pack
ensure sdk
ensure ui
ensure lobby
ensure engine
ensure mode_duel
ensure battleground_pack_base
 
# Owner (your license — see /rfx whoami after connecting)
add_ace group.admin group.admin allow
add_ace group.admin command      allow
add_ace group.admin command.quit deny
add_principal identifier.license:xxxxxxxx group.admin
 
exec server_secrets.cfg     # sv_licenseKey lives here

ensure order is not cosmetic. sdk registers the mode/pack/arena registries that later resources consume; the engine reads its store/identity convars at first boot. The Server configuration page covers the full ordering and the few convars that must sit above ensure engine.


6. Run it

make run        # = ./run.sh +exec server.cfg

A healthy boot prints the SDK registering each resource:

[sdk] mode registered: duel (1v1 Duel) from mode_duel
[sdk] battleground pool: N battlegrounds registered from battleground_pack_base

Connect from the FiveM client — open the F8 console and connect <your-ip>:30120, or use the in-game direct connect. You spawn in the lobby; press M for the menu.

Nothing is matchmade yet. A fresh server boots with zero open arenas — see the next step. As a quick solo smoke test, set rfx_lab true and use /rfx lab helpers (then turn it back off for production).


7. Open your public arenas

Public arenas live in the engine's memory and are wiped on every restart, so they're (re)opened from boot.cfgexec'd last, after all resources are up. The shipped file is fully commented; uncomment what you want:

# boot.cfg — ONE Quick Play queue + ONE ranked queue per mode.
# A queue is a template: one menu card, instances materialise on demand.
rfx queue open duel 5 s0                      # Quick Play duel, RANDOM weapon, best-of 5
rfx ranked open duel 5 s0                     # the ranked ladder for the same mode
rfx queue open battle_royale s0               # royale — fills, then launches on a countdown

The complete command — scoring, best-of, map, stamina, private codes — and the operating model are in Operations & ranked. For copy-paste server shapes (pure ladder, casual party, royale night…), see Configuration recipes.


8. Run it in production (systemd)

make run is fine for testing, but a real server should auto-start on boot, restart on crash, and log to the journal. Create a systemd unit:

# /etc/systemd/system/rivalityfx.service
[Unit]
Description=RivalityFX FiveM server
After=network-online.target
Wants=network-online.target
 
[Service]
Type=simple
User=fivem
WorkingDirectory=/opt/rivalityfx
# Wrap FXServer in a pseudo-terminal (util-linux `script`) — see the note below.
ExecStart=/usr/bin/script -qec "/opt/rivalityfx/run.sh +exec server.cfg" /dev/null
Restart=always
RestartSec=5
LimitNOFILE=65536
 
[Install]
WantedBy=multi-user.target
sudo useradd --system --home /opt/rivalityfx fivem   # a dedicated, unprivileged user
sudo chown -R fivem:fivem /opt/rivalityfx
sudo systemctl daemon-reload
sudo systemctl enable --now rivalityfx
journalctl -u rivalityfx -f                           # follow the boot / live logs

Why script wraps run.sh — the #1 systemd gotcha. Launched directly (no txAdmin) under systemd, FXServer reads its console stdin, hits EOF on systemd's null stdin, and reads that as "Ctrl-C pressed in server console" — so it self-quits right after a clean boot, and Restart=always turns it into a crash loop. The pseudo-terminal keeps stdin open so it stays up; -e makes script exit with the server's own code, so Restart= still catches real crashes. (tmux/screen work too — script is the smallest systemd-native option. Not needed if you front the server with txAdmin, which owns the process itself.)

LimitNOFILE=65536 raises the open-file limit FXServer needs under load — don't omit it. The dedicated fivem user keeps the server off root.

Day-to-day. Under systemd the server runs headless — there's no interactive console, so apply changes with a full sudo systemctl restart rivalityfx (which replays boot.cfg, so your arenas come back). The per-resource restart <name> / refresh commands (no full restart) are console commands — available when you run attached via make run, or in-game to an admin via /rfx reload when rfx_dev_commands is on (keep it off in production).

Boot loop? Two symptoms people hit here:

  • Quitting: Ctrl-C pressed in server console right after a clean boot → the stdin/pty issue above. Check your ExecStart uses the script wrapper.
  • HTTP 429: Too Many Requests while "Authenticating server license key" → cfx.re rate-limited you because a crash loop re-checked the license every few seconds. It clears on its own once the server stops restarting — fix the loop, wait a minute, retry.

9. Networking — ports & a clean connect address

  1. Open the game port — TCP and UDP — on both the cloud firewall and the host:
    sudo ufw allow 30120/tcp
    sudo ufw allow 30120/udp
    Match this to your endpoint_add_* port. On a cloud provider, add the same rule to the instance's firewall/security group.
  2. Declare your connect address. The server can't detect its own public address, so set rfx_connect in server.cfg (IP:port, a domain, or a cfx join code) — it's shown on the website join card and the in-game UI. See Server configuration §5.
  3. Clean connect address. On the default port 30120, players connect to a bare play.example.com (no :port) as soon as an A record points it at your static IP. An _cfx._udp SRV record is only needed to hide a non-default port:
    _cfx._udp.play.example.com.  SRV  0 0 30120 play.example.com.

10. Going further — website, leaderboard & store

Everything above runs a complete, self-contained server on the kvp store (no database). The optional layer adds a cross-player leaderboard + public website and is what makes selling VIP / packs possible:

  • Switch rfx_store to api and point rfx_api_url at a deployed stats API (rfx_api_token goes in server_secrets.cfg). Without api, KVP can't build a leaderboard. See Server configuration §5.
  • The stats stack (a FastAPI service + a Next.js site, designed for Cloud Run + Datastore) is its own deployment. The Tebex purchase pipeline rides on rfx_store=api — read Selling VIP & packs for what it unlocks and how to wire it.

Checklist

[ ] make runtime                 # FXServer downloaded (alpine/ + run.sh)
[ ] make secrets + sv_licenseKey # cfx.re key set, IP-locked
[ ] server.cfg                   # endpoint, ensure block, your admin principal
[ ] systemd unit enabled         # auto-start + restart-on-crash + journal logs
[ ] firewall: TCP+UDP open       # same port as endpoint_add_*
[ ] rfx_connect set              # players know where to connect
[ ] boot.cfg                     # the public arenas you want at boot

Before real traffic: run through Security & hardening — the dedicated service user, secret permissions, player-IP privacy, SSH and firewall lockdown.