left4me/docs/l4d2-server-cvar-reference.md
mwiegand 3514d04518
docs(cvar): add copy-paste best-practice server.cfg + more cvars
Adds a starter server.cfg (and matching sourcemod.cfg) that pairs with
the existing tickrate.sh overlay and a stock SourceMod install. Also
documents additional cvars (nb_update_framelimit, net_maxcleartime,
sv_tags, sv_region) alongside the per-cvar rationale already in the file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:59:16 +02:00

507 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# L4D2 server cvar reference
Working notes from the 2026-05 research session on best-practice
L4D2 dedicated server settings. Sources cited inline; some findings
verified empirically on the `left4.me` Trixie test server (kernel
6.12.86). This is reference material, not a settled design.
## Quick lookup
| Topic | Recommended |
|---|---|
| Tickrate (stock) | 30 |
| Tickrate (competitive) | 100, requires Tickrate Enabler plugin |
| `sv_pure` | `2` (strict), or `0`/`1` for modded servers |
| `sv_cheats` | `0` (set to 1 only on private practice servers; disables VAC) |
| `sv_consistency` | `0` (allow custom campaigns) or `1` (strict for competitive) |
| `sv_alltalk` | `0` (no cross-team voice), `1` for casual / fun servers |
| `sv_lan` | `0` (internet server) |
| `sv_voiceenable` | `1` |
| `nb_update_frequency` | `0.033` (safe, no plugin), `0.014` with the SM fix plugin. **Cheat-protected — must be set via `sm_cvar`.** |
| `nb_update_framelimit` | `30` (was 15). Raises the per-frame bot-AI cap so commons don't lag at high counts. **Cheat-protected.** |
| `fps_max` | `64` for 30-tick, `0` (uncapped) for higher ticks |
| `net_maxcleartime` | `0.0001` — drop choked packets fast instead of stalling. **Cheat-protected.** |
| `sv_tags` | `"coop,custom"` (etc.) — Steam server browser hint |
| `sv_region` | `3` (EU) / `1` (US East) / `255` (any) |
## Copy-paste best practice config
A complete starting config that pairs with the project's existing
`examples/script-overlays/tickrate.sh` overlay (which installs the
Tickrate Enabler plugin) and a SourceMod install. Two files: the
plain `server.cfg` and a SourceMod-only `cfg/sourcemod/sourcemod.cfg`.
For background and per-cvar rationale, see the topic sections below.
### `server.cfg` (vanilla, non-cheat cvars only)
```
// --- Identity & discoverability ---
hostname "your server name here"
sv_tags "coop,custom"
sv_region 3 // 3=EU, 1=US East, 255=any
sv_lan 0
sv_steamgroup "0" // your Steam group ID for reserved slots
sv_search_key "0" // groups your servers in the lobby browser
// --- Security ---
sv_cheats 0
sv_pure 0 // 0/1 for modded servers; 2 = strict
sv_consistency 0 // 0 if hosting custom campaigns; 1 = strict
sv_password ""
sv_allow_lobby_connect_only 0 // let players connect via IP, not just lobby
// --- Voice / chat ---
sv_voiceenable 1
sv_alltalk 0
// --- Player limits (coop) ---
sv_maxplayers 4
sv_visiblemaxplayers 4
// (For versus: 8/8)
// --- Network rates (100-tick; requires Tickrate Enabler) ---
sv_minrate 100000
sv_maxrate 100000
sv_mincmdrate 100
sv_maxcmdrate 100
sv_minupdaterate 100
sv_maxupdaterate 100
sv_client_min_interp_ratio -1
sv_client_max_interp_ratio 2
net_splitpacket_maxrate 50000
net_splitrate 2
fps_max 0
sv_forcepreload 1
// --- Logging (used by left4me's log-streaming feature) ---
sv_logfile 1
sv_logflush 0
sv_logecho 1
sv_logbans 1
```
### `cfg/sourcemod/sourcemod.cfg` (cheat-flagged cvars, set via `sm_cvar`)
```
// --- Network tweaks (cheat-flagged or SM-managed) ---
sm_cvar net_maxcleartime 0.0001
// --- Simulation cadence (more frequent AI ticks; no behaviour change) ---
sm_cvar nb_update_frequency 0.033 // 0.014 if you have the AM fix plugin
sm_cvar nb_update_framelimit 30 // default 15 — raise per-frame bot AI cap
// --- Diagnostics ---
sm_cvar nb_stuck_dump_threshold 5 // log stuck bots ≥5s
```
> **If you're not running SourceMod**, the entire `sm_cvar` block
> above is dead — those cvars are cheat-protected and silently
> ignored from plain `server.cfg`. The vanilla block still applies
> and delivers the bulk of the network-feel improvements. See
> [Cheat-protected cvars and `sm_cvar`](#cheat-protected-cvars-and-sm_cvar).
For tickrates other than 100, see the
[Network rates](#network-rates) section below.
## Network rates
L4D2 default tickrate is **30**. Rates above the corresponding
ceiling are ignored without the
[Tickrate Enabler plugin](https://github.com/SirPlease/Server4Dead-Project/tree/master/Tickrate%20Enabler).
Rule of thumb: `sv_maxrate = tickrate × 1000`.
### 30-tick (stock)
```
sv_minrate 30000
sv_maxrate 30000
sv_mincmdrate 30
sv_maxcmdrate 30
sv_minupdaterate 30
sv_maxupdaterate 30
net_splitpacket_maxrate 30000
fps_max 64
```
### 60-tick (requires Tickrate Enabler)
```
sv_minrate 60000
sv_maxrate 60000
sv_mincmdrate 60
sv_maxcmdrate 60
sv_minupdaterate 60
sv_maxupdaterate 60
net_splitpacket_maxrate 60000
fps_max 128
```
### 100-tick (competitive, requires Tickrate Enabler)
```
sv_minrate 100000
sv_maxrate 100000
sv_mincmdrate 100
sv_maxcmdrate 100
sv_minupdaterate 100
sv_maxupdaterate 100
net_splitpacket_maxrate 100000
fps_max 0
```
### sv_min*rate vs. sv_max*rate
- Locking `min == max` (competitive servers do this) ensures every
client sends at the tickrate exactly. Strict — kicks clients
that dip below.
- Leaving a range (e.g. `min=10, max=30` on a 30-tick public
server) tolerates clients on weak connections or loaded CPUs.
- Setting `sv_mincmdrate=0` means *no enforced minimum* — clients
could send as few as 1-2 cmds/sec. Bad. Pick a floor that's
playable (~10 minimum).
## Cheat-protected cvars and `sm_cvar`
Several gameplay-affecting cvars are flagged as "cheat" in L4D2 and
**cannot be set via `server.cfg` unless `sv_cheats 1`** — which
disables VAC and gates achievements. Trying to set them from cfg
silently fails (the value stays at default).
To set them on a real (VAC-protected) server: install SourceMod and
use `sm_cvar <name> <value>` instead of `<name> <value>`. SourceMod
bypasses the cheat protection for *server-side cvar writes only*
(does not grant cheat commands to players).
Cheat-protected cvars worth knowing:
- `nb_update_frequency` — common-infected pathing/state update
rate (see below).
- `director_*` — most director cvars (AI difficulty, panic events,
pacing).
- `z_*` — most zombie-behavior cvars.
`sm_cvar` writes go in `cfg/sourcemod/sourcemod.cfg` (auto-execed
by SM on map change) or in any cfg under `cfg/sourcemod/`. SM
re-applies these on every map change — important because
cheat-protected cvars *reset to defaults on map change* even
within the same server session.
## `nb_update_frequency`
Like raising server tickrate, this controls *how often* common
infected and witches get an AI tick — it doesn't change what they
decide, only how quickly the engine asks them. Pure cadence cvar.
Default `0.1` (10 Hz), independent of server tickrate.
| Value | Effect |
|---|---|
| `0.1` (default) | Common-infected look choppy regardless of tickrate |
| `0.033` | ~30 Hz updates; smooth, safe without plugin |
| `0.024` | Lowest "safe" without plugin per community testing |
| `0.014` | ~71 Hz; clients with `cl_interp 0` see jittery commons unless the [nb_update_frequency fix plugin](https://forums.alliedmods.net/showthread.php?t=344019) is installed |
Set via `sm_cvar nb_update_frequency 0.033` in
`cfg/sourcemod/sourcemod.cfg` (or any sm-auto-execed cfg). Without
SourceMod, you cannot reliably set this on a VAC-protected server.
## NextBot scheduler & diagnostics
`nb_update_frequency` (covered above) is *how often* the scheduler
asks bots to think. Two related cvars are also pure
cadence/throughput — no behaviour change — and one is a passive
diagnostic.
### `nb_update_framelimit`
Default `15`. **Maximum number of NextBots that get an AI tick per
server frame.** Above this cap the engine round-robins bots across
frames, so at 30 commons on a 30-tick server each common gets a
fresh think roughly every other frame — visible as "zombies
hesitate before chasing." Raising this to `30``60` lets every bot
think every frame at the cost of linear extra CPU. Does not alter
how bots decide what to do; only how often they get to decide.
This is the most under-documented L4D2 cvar and the one most often
blamed on tickrate or `nb_update_frequency` when it's neither.
Cheat-protected — use `sm_cvar`.
### `nb_stuck_dump_threshold`
Default `-1` (disabled). Set to `5` to log any bot stuck for ≥5
seconds to the server console. Costs nothing at runtime and is the
single best diagnostic for "why do zombies keep clipping into
geometry on this custom campaign?" tickets. Pure logging — does
not affect bot behaviour. Cheat-protected.
## Lag compensation
Most lag-compensation cvars are present but not in the truncated
`cvar_list` dump. Verify on your own server with `sm_cvar <name>`
(no value) before relying on them.
| Cvar | Default | Notes |
|---|---|---|
| `sv_unlag` | `1` | Enable lag compensation. Keep on. |
| `sv_maxunlag` | `0.5``1.0` | Max ms of lag-comp rewind. Confogl uses `1`. Higher = better for higher-ping shots. |
| `sv_unlag_fixstuck` | `1` | Used by upstream Competitive-Rework. |
| `sv_forcepreload` | `0` | Set to `1` to preload server-side assets at boot. Smoother first map. Confirmed in `cvar_list`. |
## Packet compression & high-entity-count tuning
Relevant when running custom servers with raised `z_common_limit`,
big mob spawns, or many addon entities. At high entity counts,
snapshots routinely exceed the UDP MTU and get split into multiple
packets. Clients perceive this as "lag" — but it's really
*snapshot drops*, visible in `net_graph` as updates/sec dipping
well below `sv_maxupdaterate`. The fix is on the wire, not in the
simulation.
Source: [Lux's L4D2 high-zombie-count discussion (Steam)](https://steamcommunity.com/app/550/discussions/0/2568690416482192538/).
| Cvar | Default | Recommended | Notes |
|---|---|---|---|
| `net_compresspackets` | varies | `1` | Enable LZ-style packet compression. Cheap CPU win for high-entity servers. Verify with `sm_cvar`. |
| `net_compresspackets_minsize` | varies | `2324` | Compress packets ≥ this size — roughly the wire MTU. |
| `net_splitrate` | `1` | `2` | Allow 2 split-packet pieces per net frame; drains queue faster. Confirmed in `cvar_list`. |
| `net_splitpacket_maxrate` | `15000` | `50000`+ | Throughput cap when sending split packets. |
| `net_maxcleartime` | `4.0` | `0.0001` | Don't stall on choke — drop choked packets fast. Confirmed real (RCON-verified 2026-05-20: `sm_cvar net_maxcleartime` returns the set value). |
| `sv_extra_client_connect_time` | varies | `0.0001` | Tiny handshake speedup from the Lux thread. Verify with `sm_cvar`. |
Several of these are missing from the local `cvar_list` dump but
that file is **not exhaustive** — see
[Verifying a cvar actually exists](#verifying-a-cvar-actually-exists)
below. Several of these lines exist verbatim in upstream
Competitive-Rework's `cfg/server.cfg`, which has been running on
public servers for years.
## Server discoverability
Cosmetic but real UX wins for public servers.
| Cvar | Recommended | What it does |
|---|---|---|
| `sv_tags` | `"coop,custom,modded"` (your choice) | Comma-separated tags shown in the Steam server browser. Players filter on these. |
| `sv_region` | `3` (EU), `1` (US East), `255` (any) | Region reported to the master server. Set this and your server appears in the right regional browser. |
| `sv_search_key` | `"left4me"` (or your own string) | When players search from the in-game lobby, only servers with a matching key appear. Useful for grouping a fleet. |
| `sv_steamgroup` | your group's ID | Steam group members get reserved-slot priority (with the appropriate plugin). |
| `sv_lan` | `0` | Set `1` only for local-only play; skips Steam auth (players can't friend-join). |
## Logging hygiene
Relevant because the project's log-streaming feature (the work in
`l4d2web/static/js/files-overlay/editor.js` and adjacent) tails
the server log file. These cvars control what actually gets
written.
| Cvar | Recommended | Notes |
|---|---|---|
| `sv_logfile` | `1` | Server log to disk. Required for log-streaming. |
| `sv_logflush` | `0` | Don't flush after every line — slow. Keep at `0` unless you're debugging crashes. |
| `sv_logecho` | `1` | Mirror log to stdout — needed for any process that tails srcds's console. |
| `sv_logbans` | `1` | Log every `kickid` / `banid` to the same log file. Cheap audit trail. |
| `sv_log_onefile` | `0` | Default — one log per day. `1` rolls everything into a single file (gets large quickly). |
| `sv_logsdir` | `"logs"` | Default. Path is relative to the game directory. |
## Verifying a cvar actually exists
The local `/Users/mwiegand/Projekte/left4me/cvar_list` dump (~2199
entries) is **incomplete** — it's missing several real L4D2 cvars
that upstream Competitive-Rework uses and that have been verified
in-engine via RCON. Likely it was generated via the in-engine
`cvarlist` command, which truncates and filters.
Authoritative existence check via SourceMod console (RCON):
```
sm_cvar <name> # no value → "Value of cvar X: Y" if real,
# "unknown" otherwise
```
The screenshot evidence for `net_maxcleartime` (2026-05-20):
```
> sm_cvar net_maxcleartime
[SM] Value of cvar "net_maxcleartime": "0.0001"
```
Rule of thumb when copying configs from elsewhere:
1. If the cvar is in `cvar_list` → it's definitely real.
2. If it's *not* in `cvar_list` but is in upstream Competitive-
Rework's `server.cfg` → probably real, but verify via `sm_cvar`
before relying on it.
3. If it's in neither and only mentioned in a random forum post →
high probability it's a CSGO/CS:S or HL2 cvar that someone
assumed exists in L4D2.
## Cvars that DO NOT exist in L4D2 (despite some guides claiming otherwise)
These come up in older guides or are inherited from other Source
games but don't actually exist in L4D2's command set. Verified by
RCON `sm_cvar <name>` returning "unknown":
- `z_resolve_zombie_collision_multiplier` — confirmed unknown in
current L4D2 builds (verified via RCON 2026-05-14). Some
community guides list it; it's not in the binary.
- `z_update_rate` — referenced in older tuning guides but not a
real L4D2 cvar. The actual zombie-AI cadence knob is
`nb_update_frequency`.
If a guide tells you to set one of these in L4D2, the guide is
wrong or out of date.
**Earlier revisions of this doc also listed `net_maxcleartime`
here. That was wrong** — it's a real L4D2 cvar (RCON-verified
2026-05-20 returning `0.0001` on `left4.me`). It just happens to
be missing from the `cvar_list` dump. The lesson: the cvar_list
file is useful as a positive check but unreliable as a negative
check (see
[Verifying a cvar actually exists](#verifying-a-cvar-actually-exists)).
## Security and integrity
```
sv_cheats 0
sv_pure 2 # force Steam-only files (strictest)
sv_consistency 1 # enforce file hashes for critical files
# (set 0 if hosting custom campaigns)
sv_lan 0 # internet server
```
Launch the server with `-secure` to enable VAC. `sv_cheats 1`
requires `-insecure` (no VAC) — only acceptable on private
practice servers.
`sv_pure 2` breaks many workshop maps/mods. Use `sv_pure 0` or `1`
for modded servers.
## Player limits
```
# Co-op / Scavenge
sv_maxplayers 4
sv_visiblemaxplayers 4
# Versus
sv_maxplayers 8
sv_visiblemaxplayers 8
```
## Voice
```
sv_voiceenable 1
sv_alltalk 0 # 1 = cross-team voice (casual / fun servers)
```
## Recommended plugins (SourceMod ecosystem)
| Plugin | Purpose |
|---|---|
| MetaMod:Source + SourceMod | Required foundation for most of the below |
| [Tickrate Enabler](https://github.com/SirPlease/Server4Dead-Project/tree/master/Tickrate%20Enabler) | Unlock >30 tick servers |
| [Little Anti-Cheat](https://github.com/J-Tanzanite/Little-Anti-Cheat) | Aimbot / angle-cheat detection |
| SMAC | Secondary AC layer (older but still works) |
| [ZoneMod](https://github.com/SirPlease/L4D2-Competitive-Rework) | Competitive Versus ruleset (full bundle: ZoneMod + MatchMode + Confogl-style plugins) |
| `l4d2_TKStopper` | Teamkill / griefing control |
| `l4d_sb_fix` | Survivor bot behavior fixes |
| [nb_update_frequency fix](https://forums.alliedmods.net/showthread.php?t=344019) | Eliminates client-side jitter at very low `nb_update_frequency` values |
## MetaMod:Source / SourceMod versioning
- Stable branches are pinned in URL paths: `1.10`, `1.11`, `1.12`,
etc. There is no "latest stable" alias URL — you pick the
branch.
- Within a branch, the `mmsource-latest-linux` and
`sourcemod-latest-linux` text files contain the current build's
filename, e.g. `mmsource-1.12.0-git1219-linux.tar.gz`. Curl the
pointer file, then curl the actual tarball.
- AM bumps stable every ~2-3 years. When 1.13 (or later) is
declared stable, update the `MM_BRANCH=1.12` / `SM_BRANCH=1.12`
pins in the seeded Sourcemod overlay script.
- L4D2 has no special branch — it uses whatever the current
stable supports. L4D2's engine is so stable that SM 1.11 and
1.12 both work.
Watch for stable announcements at
[Metamod:Source news](https://www.sourcemm.net/) and
[SourceMod releases](https://github.com/alliedmodders/sourcemod/releases).
## Empirically-verified kernel quirk (relevant if you tweak the helpers)
Idmapped bind mounts on kernel 6.12 (Trixie) **do** propagate
through plain `mount --bind` re-binds. Verified end-to-end on
`left4.me` during the 2026-05-15 build-time-idmap refactor: a
sandbox process inside a re-bound idmapped mount can write files,
and those writes land on disk with the idmap-translated uid.
This contradicts some published claims (including a generic
research-agent summary) that idmaps don't propagate through plain
re-bind on this kernel. Our use case is `mount --bind --map-users
src staging` → systemd-run with `BindPaths=staging:/overlay` (a
plain re-bind into the unit's namespace). It works.
The `--map-users <a>:<b>:<count>` direction is **on-disk uid
first**, then in-mount uid. The util-linux man page calls these
`<inner>:<outer>` which is confusing — `<inner>` means "the
filesystem's native uid" (on disk) and `<outer>` means "the uid
exposed outward through the mount." Empirically verified; do not
trust the man page's word choice.
## Project integration (left4me overlays)
The project already ships overlays in `examples/script-overlays/`
that map cleanly onto the recommendations above:
| Overlay | Use it for |
|---|---|
| [`tickrate.sh`](../examples/script-overlays/tickrate.sh) | Drop-in 100-tick foundation: installs the Tickrate Enabler plugin (`tickrate_enabler.dll/.so/.vdf`) and writes the core rate cvars (`sv_minrate/maxrate 100000`, `nb_update_frequency 0.014`, `net_splitpacket_maxrate 50000`, `net_maxcleartime 0.0001`, `fps_max 0`). Required base layer for any of the higher-tick recommendations in this doc. |
| [`competitive_rework.sh`](../examples/script-overlays/competitive_rework.sh) | Pulls the entire SirPlease/L4D2-Competitive-Rework master branch into the overlay. Full confogl bundle — plugins, configs, cfgogl per-mode tuning. Opinionated for tournament versus. Use this *or* `tickrate.sh`, not both. |
| [`cedapug_maps.sh`](../examples/script-overlays/cedapug_maps.sh), [`l4d2center_maps.sh`](../examples/script-overlays/l4d2center_maps.sh) | Competitive map pools (orthogonal to cvars). |
The cvars in the "Copy-paste best practice config" section above
are intended to be applied **on top of `tickrate.sh`** — either by
adding them to an instance's `spec.config` YAML list, or by
creating a new overlay (e.g. `examples/script-overlays/ux_polish.sh`)
that writes them to `$OVERLAY/left4dead2/cfg/server.cfg`.
How `spec.config` becomes `server.cfg` (for reference):
`l4d2host/l4d2host/instances.py:52-54` joins the YAML list with
newlines into `{LEFT4ME_ROOT}/instances/{name}/server.cfg`, then
that file is staged into the runtime upper layer at instance start.
## Launch parameters (reference)
Typical srcds invocation:
```
./srcds_run -console -game left4dead2 -secure -autoupdate \
+maxplayers 8 -port 27015 +exec server.cfg +log on
```
- `-secure` enables VAC. Don't run public servers without it.
- `-autoupdate` keeps the server patched automatically.
- `+exec server.cfg` runs your config on startup.
- `-tickrate <N>` sets the engine tickrate (requires Tickrate
Enabler for `N > 30`).
## Sources
Primary references used for the recommendations above:
- [L4D2-Competitive-Rework server.cfg](https://github.com/SirPlease/L4D2-Competitive-Rework/blob/master/cfg/server.cfg) — the canonical confogl/competitive cvar block. Many cheat-flagged cvars in this doc are sourced from here.
- [L4D2-Competitive-Rework cvar_tracking.cfg](https://github.com/SirPlease/L4D2-Competitive-Rework/blob/master/cfg/cvar_tracking.cfg) — client-cvar enforcement list (anti-cheat tracking; not directly used here but useful context).
- [Lux's L4D2 high-zombie-count packet compression analysis (Steam Discussions, app/550)](https://steamcommunity.com/app/550/discussions/0/2568690416482192538/) — origin of the `net_compresspackets` / `net_splitrate` / `net_maxcleartime` recommendations.
- [L4D2 Dedicated Server Guide (Steam Community)](https://steamcommunity.com/sharedfiles/filedetails/?id=276173458)
- [L4D2 Dedicated Server Network Tweaks (Steam Discussions)](https://steamcommunity.com/app/550/discussions/1/1839063537784156851/)
- [SirPlease/Server4Dead-Project — Tickrate Enabler](https://github.com/SirPlease/Server4Dead-Project/tree/master/Tickrate%20Enabler)
- [Valve Developer Community — L4D2 console commands](https://developer.valvesoftware.com/wiki/List_of_Left_4_Dead_2_console_commands_and_variables)
- [AlliedModders — nb_update_frequency fix (Experimental)](https://forums.alliedmods.net/showthread.php?t=344019)
- [Source Multiplayer Networking — Valve Developer Community](https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking)
- [Required Versions (SourceMod wiki)](https://wiki.alliedmods.net/Required_Versions_(SourceMod))
- [MetaMod:Source news](https://www.sourcemm.net/)
- Local: `/Users/mwiegand/Projekte/left4me/cvar_list` — 2199-line dump of L4D2 cvars (positive existence reference; *not* exhaustive — see [Verifying a cvar actually exists](#verifying-a-cvar-actually-exists)).
- Local: `examples/script-overlays/tickrate.sh`, `examples/script-overlays/competitive_rework.sh` — overlay scripts that apply these settings to a left4me instance.