Host a server
Run your own match.
A PeakRunner server is one small program: it runs the match, checks every move and shot, and talks to players over encrypted UDP. Download it, answer a few questions, and share the invite link. No domain name or certificate needed.
Overview
- Each server hosts one match of up to 28 players (14 a side; 8 by default) and rotates through the maps and modes you choose.
- The server is authoritative: players send inputs, and the server decides movement, hits, scores and teams.
- Connections use encrypted QUIC over UDP. On first run the server makes its own key; players' games check that exact key (from the invite link, or as vouched for by the public directory), so no domain or certificate is needed. A server with its own certificate works too.
- To appear on this site and in the game's server list, the server announces itself to the public directory. It's optional; players can always join with the invite link.
- Players must run the same game version as the server. When a new release comes out, update the server too.
Quick start
- Download PeakRunner Server for your computer from the downloads (macOS on Apple Silicon, Windows x64 or Linux x64) and unpack it.
- Run
peakrunner-serverfrom a terminal (on Windows, double-clickpeakrunner-server.exe). It asks for a name, the mode and maps, how many players, bots, an optional password, the UDP port (7777), whether to announce to the public list, and whether to open the port on your router automatically (UPnP). Press Enter to accept each default. - It saves your answers to
server.jsonin your user settings folder and starts. Later runs start straight away; run with--setupto change them. - Share the invite link it prints (
peakrunner://join/…#sha256=…): players paste it into Direct server address or invite link in the game. If you announced, your server also appears under Community servers here and in the game's server list.
Server name [PeakRunner Community]: Garage CTF
Mode [1]:
Maps (numbers like 1,3, or all) [all]:
Max players (2-28) [16]: 12
UDP port [7777]:
Announce to the public server list? (yes/no) [yes]:
Players outside your network need UDP to reach your computer: the server tries UPnP if you let it, and says plainly whether the directory could reach it; if not, forward the UDP port on your router. An unlisted server contacts nothing; run it with --publicip to also print an invite link with your public address. The password, if any, is kept in server.json, readable only by your user account; PEAKRUNNER_MATCH_PASSWORD overrides it.
Requirements
| You need | Details |
|---|---|
| A computer | macOS on Apple Silicon, Windows x64 or Linux x64 (Debian 12 / glibc 2.36 or newer). Docker is an option on Linux. One CPU core and 256 MB of memory per server is plenty, even for 28 players (a full local test used under a millisecond of simulation per tick); the official servers idle at about 35 MB. |
| A public address | A public IPv4 address, or a home router that forwards the port to your machine. |
| An open UDP port | 7777 by default; any port works. Run several servers on one machine with one port each (for example 7777, 7778, …). |
| Bandwidth | About 120 kbit/s upload per player in a busy match (each player gets 20 compact updates a second): roughly 1 Mbit/s for 8 players, 3.5 Mbit/s for 28. Download is small. |
Run it with Docker
For an always-on Linux host, the same program runs in Docker, configured only by environment variables (it never asks questions without a terminal). This is how the official servers run, with their own certificate; for a Docker server without one, mount a writable folder at PEAKRUNNER_SERVER_DIR to keep its generated key. A server that plays every map in Capture the Flag and Capture & Hold, then Team Deathmatch, and lists itself publicly:
services:
match:
image: peakrunner/server:<version> # published with the source release
command: [peakrunner-server]
user: "10001:10001"
environment:
PEAKRUNNER_MATCH_NAME: Example Ridge
PEAKRUNNER_LOCATION: Dallas, US
PEAKRUNNER_MAX_PLAYERS: "16"
PEAKRUNNER_MATCH_ROTATION: '[{"playlist":"ctf_cnh"},{"playlist":"team_deathmatch"}]'
PEAKRUNNER_DIRECTORY_URL: https://dir.peakrunner.net/announce
PEAKRUNNER_ADVERTISE_URL: quic://play.example.net:7777
ports:
- "7777:7777/udp"
volumes:
- ./certs:/run/peakrunner:ro # fullchain.pem and privkey.pem
restart: unless-stopped
read_only: true
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
mem_limit: 1g # Ashveil, the heaviest map, holds about 60 MB
cpus: 1.0
healthcheck:
test: [CMD, peakrunner-server, --healthcheck]
interval: 15s
Start it with docker compose up -d, then open the port in your firewall, for example sudo ufw allow 7777/udp.
Only publish the UDP game port. Inside the container the server also answers a small health endpoint on TCP 8080 for the health check. Run with a server.json (outside Docker), it keeps that page on this computer only (127.0.0.1, TCP, at the game port's number), so there's nothing extra to firewall.
Settings
The setup questions cover the common ones. Every setting can also be set with an environment variable, which overrides server.json.
| Variable | Meaning |
|---|---|
PEAKRUNNER_MATCH_NAME | Server name shown in lists, up to 64 characters. Names starting with "Springdale" (including the default) are kept for the official servers, so set your own. |
PEAKRUNNER_MATCH_ROTATION | The maps and modes to play, as JSON (see below). Without it the server plays one map in CTF. |
PEAKRUNNER_ROTATION_ORDER | in_order (default) plays the rotation as listed; random plays the same maps and modes in a new random order each time through. In server.json: "rotation_order": "random". |
PEAKRUNNER_MATCH_MAP | The map when no rotation is set (default old-holler, Old Holler). |
PEAKRUNNER_LOCATION | Where the server is, shown in server lists so players can pick a nearby one: for example Dallas, US. Up to 40 letters, digits, spaces, commas, periods or hyphens. |
PEAKRUNNER_MAX_PLAYERS | How many players the match holds, 2–28 (default 8). Teams are kept even. |
PEAKRUNNER_BOTS | Fill the match with bots up to this many players (even number, at most the capacity; default 0). Each person who joins takes a bot's place, and a bot returns when they leave. Bots count as players in the server lists, marked as bots. |
PEAKRUNNER_TIME_OF_DAY, PEAKRUNNER_WEATHER | Each round's sky: default (the map's own look; Team Deathmatch rolls its own), random (again each round), or one of map, dawn, day, dusk, night and clear, overcast, rain, storm, snow, fog. Only the look changes. In server.json: time_of_day and weather. |
PEAKRUNNER_MATCH_PASSWORD | Optional. Players must enter it to join. Keep it out of files you share; an .env file next to the Compose file works well. |
PEAKRUNNER_MAX_SPECTATORS | Spectator places, 0–8 (default 8). Spectators take no player slot; a full match seats newcomers as spectators and puts them in when a slot opens. |
PEAKRUNNER_SESSIONS_PER_IP | Players from one address, 1–28 (default 4). Raise it for a LAN party behind one router. |
PEAKRUNNER_SERVER_DIR | Where server.json and the generated key live (or --config-dir). |
PEAKRUNNER_UDP_BIND | Address and port to listen on inside the container (default 0.0.0.0:7777). |
PEAKRUNNER_TLS_CERT, PEAKRUNNER_TLS_KEY | Optional certificate chain and key (see Your own certificate). Without them the server uses its generated key. |
PEAKRUNNER_DIRECTORY_URL | Where to announce: https://dir.peakrunner.net/announce. Leave unset to stay unlisted. |
PEAKRUNNER_ADVERTISE_URL | The address players use: quic://your-hostname-or-public-ip:port, with the port as seen from the internet. The setup questions fill this in for you. |
Maps and rotation
The rotation is a JSON list. Each entry is either a playlist, which expands to every map that supports that mode, or one map in a mode. Entries play in order and then repeat; an empty server goes back to the start. Up to 32 entries, expanding to at most 64 matches.
[{"playlist":"ctf"},
{"map":"reefbreak","mode":"team_deathmatch"},
{"map":"highgoal","mode":"football"}]
| Playlist | Plays |
|---|---|
ctf_cnh | Every CTF map in CTF, then every Capture & Hold map in Capture & Hold |
ctf | Every CTF map |
capture_and_hold | Every map with Capture & Hold towers |
team_deathmatch | Every map except the stadiums |
rabbit, team_rabbit | Every map except the stadiums, in Rabbit or Team Rabbit |
football | The two stadiums |
duel | A duel server: a lobby, and one on one duels side by side on every map except the stadiums (below) |
| Map key | Map | Modes |
|---|---|---|
old-holler | Old Holler | CTF, C&H, TDM |
tower-complex | Tower Complex | CTF, C&H, TDM |
cairnhold | Cairnhold | CTF, C&H, TDM |
frostline | Frostline | CTF, C&H, TDM |
dustreach | Dustreach | CTF, C&H, TDM |
ozarktic-blast | Ozarktic Blast | CTF, C&H, TDM |
reefbreak | Reefbreak | CTF, C&H, TDM |
ashveil | Ashveil | CTF, C&H, TDM |
longfield | Longfield | Football |
highgoal | Highgoal | Football |
tenfold | Tenfold Tower | A duel server's lobby (not in rotations; below) |
Mode keys are ctf, capture_and_hold, team_deathmatch, rabbit, team_rabbit, football and duel, plus the modes of any mods you run. Some map keys are older working names; they stay so existing rotations keep working. The server refuses to start if the rotation names a map or mode it can't play, and says why.
A duel server
A rotation of only duel entries ([{"playlist":"duel"}], "Duels" in the setup, or Duel on the game's Host screen) makes a duel server. Everyone joins its lobby, Tenfold Tower, where the only weapon is a blade and nothing counts. From the duel board (the map-vote key, V) players challenge each other, or a bot, and watch duels being played. Each accepted challenge plays in an arena of its own, on a map drawn from the rotation, so many duels run at once: one life a round, first to 3 rounds, light armor's default kit. The capacity counts everyone: the lobby, the duelists and those away watching. The lobby is Tenfold Tower, and server bots spar with blades on its floors (two to a floor, from the ground up); each person who joins takes a bot's place.
Your own maps and mods
The server plays the maps that come with it. To run more, or to change how the game plays, put them in the content folder beside server.json (the setup creates it: on Linux ~/.config/PeakRunner/server/content, on a Mac ~/Library/Application Support/PeakRunner/server/content, on Windows %APPDATA%\PeakRunner\server\content):
content/maps/<key>/: a map, named in your rotation by its key.content/mods/<id>/: a mod, which can change any number, add modes, and bring its own rules. The download'sexamples/mods/folder has four to copy.
peakrunner-server --checkreads that folder exactly as the server will and names every problem, rules scripts included.peakrunner-server --setuplists the mods it found, asks which to run, and offers their modes and your maps with the rest.- Start it. The game's Find match shows your mods and how much joining downloads; players who lack something are asked, then get it straight from your server and keep it for next time.
Players only ever receive data. Rules scripts stay on your server. How to make mods →
Get listed
Answer yes to announcing (or set PEAKRUNNER_DIRECTORY_URL and PEAKRUNNER_ADVERTISE_URL), and the server announces itself every 20 seconds. Before listing it, the directory checks that:
- the advertised address is the address the announcement came from (or a hostname resolving to it), so nobody can list someone else's server;
- an encrypted status query to that address succeeds. For a server with its own key, the directory records the key it saw and publishes it with the listing, and players' games accept exactly that key; a key change is checked afresh before it's listed again;
- the name and map are sensible, and the name doesn't start with "Springdale".
Listings show the player count and capacity, and disappear about a minute after the announcements stop. Each address can list up to 4 servers. Your server then shows under Community servers on this site and in every player's Find match list, with its map, mode and player count. Servers with a certificate from a public authority are listed like the official ones.
Behind a home router, forward the UDP port to the machine running the server (or let UPnP do it). The server reports whether the directory could reach it.
Your own certificate (optional)
You don't need one: the generated key is checked just as strictly. A certificate from a public authority for a hostname is useful if you want players to join by name (quic://play.example.net:7777) or already run one. Players' games then check it against the usual public root authorities and the hostname, exactly as a browser does.
Any ACME client works. With certbot and a DNS provider plugin (no web server needed), for example Cloudflare:
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d play.example.net
The server reads the full chain and the private key in PEM form: by default /run/peakrunner/fullchain.pem and /run/peakrunner/privkey.pem. The key must be readable by the server's user and nobody else.
The certificate is loaded when the server starts, so restart it after each renewal (a certbot deploy hook can do this). A restart briefly disconnects players; they can rejoin straight away.
Operate it
- Health:
docker compose psshows the health check. Logs:docker compose logs -f match. Outside Docker,peakrunner-server --healthcheck(with the same--config-dir, if any) exits 0 while the match is running, for a service manager or a cron job. - Updates: when a new game version is released, change the image version and run
docker compose up -d. Players on the old version can't join the new server, and the reverse, so update promptly. - Several servers: copy the service with another name and host port, for example
"7778:7777/udp"andquic://play.example.net:7778. - Security: keep the container read-only, as a non-root user and with capabilities dropped, as above. The server checks every input, rate-limits connections, chat and renames, and never runs code sent by players.
- Floods: a server on a small line can be knocked offline by a denial-of-service attack. Hosting providers with DDoS filtering help; a kernel rate limit on the game port helps with smaller floods.
LAN and friends
For a LAN party or a private VPN such as Tailscale there is a lighter LAN server. It needs no certificate or hostname, but it only listens on private addresses, and its traffic is not encrypted, so it refuses to listen on a public address.
peakrunner-lan-server --bind 192.168.1.20 --port 7781 --name "Basement" --map reefbreak --players 12
Players join by typing the address and port (for example 192.168.1.20:7781) as the direct server address. Set PEAKRUNNER_MATCH_PASSWORD to require a password. The LAN server plays one map: CTF, or Football on a stadium.
For quick games you don't need a server at all: Start match in the game runs a local match with bots.