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

22 KiB
Raw Blame History

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.

For tickrates other than 100, see the Network rates section below.

Network rates

L4D2 default tickrate is 30. Rates above the corresponding ceiling are ignored without the Tickrate Enabler plugin.

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_minrate vs. sv_maxrate

  • 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 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 3060 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.51.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).

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 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).

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)
Plugin Purpose
MetaMod:Source + SourceMod Required foundation for most of the below
Tickrate Enabler Unlock >30 tick servers
Little Anti-Cheat Aimbot / angle-cheat detection
SMAC Secondary AC layer (older but still works)
ZoneMod 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 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 and 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 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 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, 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: