Tutorial 168: Operating the Gamer Services server
What you’ll learn: how to run the server with TLS, provision titles, accounts and content, back it up, upgrade and monitor it, what its limits are, and what it does not protect against.
Read this first. cna-gamer-services-server is CNA’s own service, not Xbox LIVE. No public instance is operated, and public-Internet deployment has not been independently qualified: running it for other people is your decision and your responsibility. Every command below is quoted from or built on the server’s README and source at commit 9bd5173; none was run for this page. Connecting a game is Tutorial 164.
What you are running
One C++23 process (MIT licence) serves the whole CNA Gamer Services protocol on one TLS port: the control API (POST /cna/v1), downloads of hash-addressed files (GET /cna/v1/files/<sha256>), the account event channel (WebSocket /cna/v1/events) and the relay that carries online NetworkSession traffic (WebSocket /cna/relay/v1). It keeps everything in one SQLite database in WAL mode, with one writer and in-memory hubs for relay and event channels. Its dependencies come from the system: OpenSSL 3 or newer, Boost 1.74 or newer with Beast, SQLite 3.38 or newer and nlohmann/json 3.11 or newer. Linux is the tested host; Windows and macOS builds are unvalidated.
Not provided, by design: Xbox LIVE compatibility of any kind; self-registration or password changes by players (the admin tool creates accounts and revokes credentials, but has no command to change a password); a store, partner tokens or title-update delivery; TrueSkill; time windows for the ...Recent leaderboard keys; direct peer-to-peer connections; any cluster or multi-server operation.
1. Build and run
cmake -S . -B build -G Ninja && cmake --build build --parallel
build/cna-gamer-services-server --database service.sqlite3 --listen 0.0.0.0 --port 47831 \
--cert certificate.pem --key private-key.pem
# Development only, plain HTTP on a numeric loopback address:
build/cna-gamer-services-server --database service.sqlite3 --insecure-loopback
The defaults are --listen 127.0.0.1 and --port 47831. A second server process on the same database refuses to start with DATABASE_IN_USE: the first holds an operating-system lock on <database>.lock, released when it ends, crash included. SIGINT and SIGTERM stop it.
The README’s checklist for a public deployment asks for an unprivileged account that owns only the database directory and the TLS key, and, under systemd, these settings. A sketch of a unit, not a file from the repository:
[Service]
User=cna-gs
WorkingDirectory=/srv/cna-gs
ExecStart=/srv/cna-gs/cna-gamer-services-server --database /srv/cna-gs/service.sqlite3 --listen 0.0.0.0 --port 47831 --cert /srv/cna-gs/certificate.pem --key /srv/cna-gs/private-key.pem
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/srv/cna-gs
Restart=on-failure
2. TLS, keys and secrets
- Certificate. Clients require TLS 1.2 or later and verify the chain and the host name, against the system trust store or the CA bundle a title ships (
caBundle). Use a public certificate for the DNS name your games use, with its intermediates in the chain file. A renewed certificate needs a restart; clients and relays reconnect by themselves, and live sessions resume their relay connections. - One port, no TLS-terminating proxy. Control, relay and events share the TLS port, and the server terminates TLS itself. Put an L4 (TCP) filter in front, not a proxy that terminates TLS, which would also make every player appear to come from the proxy’s address and share its per-address limits.
- Secrets on disk. The database (with its
-waland-shmfiles) and the TLS key (mode 0600) belong to the service account only. The server stores scrypt password verifiers and only hashes of access tokens; access tokens last an hour and refresh credentials rotate over 30 days, with a replayed refresh credential revoking its whole family. - Logs. The admin tool never prints a credential. The server’s only regular output is one statistics line a minute on standard output, without credentials, addresses or request bodies.
3. Titles, accounts and content
build/cna-gamer-services-admin <database> <command> works on the same database as a running server. Passwords and JSON documents are read from standard input:
DB=service.sqlite3
build/cna-gamer-services-admin $DB title my.game "My Game"
build/cna-gamer-services-admin $DB title-minimum-version my.game 1.0.0 # older or unversioned games must update
read -rs PASSWORD # 8 to 256 characters; keep shell tracing off
printf '%s\n' "$PASSWORD" | build/cna-gamer-services-admin $DB user alice Alice
unset PASSWORD
printf '%s' '{"key":"first-steps","name":"First Steps","description":"Finish the tutorial.","howToEarn":"Complete the first level.","score":10}' \
| build/cna-gamer-services-admin $DB achievement my.game
printf '%s' '{"key":"BestScoreLifeTime","mode":0,"ascending":false,"aggregation":"best","arbitrated":false,"columns":{}}' \
| build/cna-gamer-services-admin $DB leaderboard my.game
# avatars: import every catalog CNA ships, then give an account one
for v in v1 v2 v3; do
build/cna-gamer-services-admin $DB avatar-catalog "$CNA/modules/gamer-services/assets/avatars/$v"
done
build/cna-gamer-services-admin $DB avatar alice random
Here $CNA is a CNA checkout. Other commands, all in the README: asset <title> image/png <file> imports a PNG (at most 512×512, 512 KiB) and prints its hash — pass that hash to picture <user> <hash> for a gamer picture, or as an achievement’s picture field; privilege <user> <name> <value> sets an XNA privilege (communication, profileViewing or userContent to everyone, friends or blocked; trade, purchase or premium to allowed or blocked) — the operator’s stand-in for parental controls; game-defaults <user> sets an account’s XNA GameDefaults; revoke-user revokes every credential of an account; inspect and inspect-online <title> print counts only; reset-earned and reset-online clear a title’s earned achievements or its online sessions. A title may have up to 128 leaderboards, each with up to 32 typed columns; the leaderboard key and game mode must match what the game passes to LeaderboardIdentity::Create.
4. Backups, updates and monitoring
- Backups. Use SQLite’s online backup, which is consistent under WAL, never a copy of the file while the server runs:
sqlite3 service.sqlite3 ".backup service-backup.sqlite3". Schedule it, and try a restore once. - Durability. Everything a player would notice losing — credentials and revocations, awards, scores, friends, messages, profile and avatar edits — commits with
synchronous=FULL; leases, presence and request bookkeeping commit withNORMAL, so a power cut may roll back their last moments. - Updating. Opening a database migrates it, in a transaction, to the schema of the server binary; migrations only go forward, and a database from a newer server is refused rather than altered. Take a backup before every upgrade.
- Monitoring. The per-minute line counts responses by code, connections refused by admission, open control connections, attached relay machines and open event channels. Alert on
INTERNAL_ERROR(storage failed: a full disk, a locked database), onrefusedrising and on bursts ofRATE_LIMITED.
5. Limits and capacity
| Limit | Value |
|---|---|
| Control connections (checked before the TLS handshake) | 256 in all; 32 held and 600 new a minute per source address; a handshake must finish within 10 s |
| Sign-ins and refreshes | 10 a minute per address; an account’s oldest sign-in is signed out past 32 live ones |
| Relays and event channels | 1,024 relay machines; 8 event channels per account, 4,096 per server |
| Recorded requests | 20,000 a day per account and title (kept in memory, reset by a restart) |
| Social | 200 messages an hour; 64 pending invitations per recipient, 32 sent an hour; 1,024 live sessions per title |
A game that meets a limit sees the service as unavailable for that call, through XNA’s usual exceptions. Players behind one NAT address share its 32 connections and 10 sign-ins a minute. The README’s committed benchmark measured about 1,200 requests a second (steady mix, p99 118 ms) on one loaded laptop over loopback, while an idle player costs about two requests a minute; it is one machine’s figure at one moment, not a capacity promise.
6. Trust boundaries
Achievements and scores are authenticated, validated client claims. The server checks the account, the title, provisioned IDs, schemas, ranges and ownership, but it cannot see gameplay: a modified or colluding client can submit plausible fake results, and there is no anti-cheat. Ranked rows count when a strict majority of the reporting machines agree, which colluding machines can satisfy. Do not run competitive events with prizes on these numbers.
- Hostile traffic. The limits above stop a single address. Nothing limits many addresses together — there is no server-wide handshake rate — so a distributed flood can fill the connection pool or burn CPU. A firewall, connection-rate limiting or a DDoS filter in front is required; the server does not claim DDoS protection.
- Relay and voice. Relay access needs one-use, short-lived tickets bound to the members, title, session and machine, and the server stamps the sender’s identity on every relayed frame. It does not inspect game data or voice: the session host is trusted to relay game data, and privacy rules for voice are applied by the cooperating CNA clients, with blocks and privileges refreshed on the 30-second heartbeat.
- Accounts. The operator creates every account and sets its privileges; there is no self-service. Treat the database and its backups as containing personal data.
7. Testing your build
ctest --test-dir build --output-on-failure runs the server’s own suites, a TLS end-to-end test with Python clients and a relay test (Python websockets, pinned in tests/requirements.txt). The tests that drive real CNA processes need CNA’s harness programs from a CNA build, named by CNA_SERVICE_*_HARNESS variables as the README lists; without them they skip (exit 77), which is not a pass. The NAT-isolated variants additionally need unprivileged user namespaces and a slirp4netns binary, which is executed, never linked.
Evidence and qualification
- CNA’s final audit run of this work (2 October 2026, Linux): the GamerServices suite 649 passed and the Net suite 524 passed, with no failures; the server’s 40 registered tests: 33 passed, 7 network-isolation tests skipped for lack of a test tool (
slirp4netns), none failed. These are CNA-recorded runs; this site did not re-run them. - Earlier NAT-isolation runs used separate network namespaces on one Linux host: evidence about isolation, not about public-Internet latency, loss or failover. CNA’s manual qualification plan for real networks and devices has not been executed.
- Not qualified: Windows and macOS server hosts and clients (the database lock and the Windows credential store were exercised under Wine only), a real wide-area network with NAT, voice on physical audio devices, and avatar pixels on Direct3D and Metal.
- Browser builds have no service, relay or voice, so browser players cannot use a server at all.
Next
This is the last tutorial of the Gamer Services series. Start again from Tutorial 162, go back to Tutorial 167 for avatars, or read the Gamer Services overview.