Add 3 tutorials (3 files)
Browse Source
3 changed files
with 217 additions and 0 deletions
A
fix-hlds-segfault-startup/en.md
+68 -0
A
fix-players-timeout-map-change/en.md
+51 -0
A
hlds-systemd-service/en.md
+98 -0
fix-hlds-segfault-startup/en.md
A
| @@ -0,0 +1,68 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: HLDS Segmentation Fault on Startup (Linux)" | |
| 3 | +excerpt: "A CS 1.6 server that segfaults the moment it starts on Linux: the usual causes — mismatched 64/32-bit binaries, a bad engine or game DLL, Metamod ordering — and how to isolate it." | |
| 4 | +category: "errors" | |
| 5 | +tags: "segfault, crash" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>The server dies before it ever reaches a map:</p> | |
| 10 | +<pre>./hlds_run: line 456: 1234 Segmentation fault $HL_CMD | |
| 11 | +Add "-debug" to the ./hlds_run command line to generate a debug.log</pre> | |
| 12 | +<p>A startup segfault is almost never "HLDS is broken" — it is two pieces of your stack that do not agree, and the trick is to strip the server back to the minimum that still crashes so you know which piece. Work from the outside in.</p> | |
| 13 | + | |
| 14 | +<h2>1. Turn on the crash log</h2> | |
| 15 | +<p>Do exactly what the message says — add <code>-debug</code>:</p> | |
| 16 | +<pre>cd /home/steam/hlds | |
| 17 | +./hlds_run -debug -game cstrike +map de_dust2 +maxplayers 20 +sv_lan 0</pre> | |
| 18 | +<p>On a crash this writes <code>debug.log</code> in the server directory with a backtrace. The top frames name the library that faulted — <code>engine_i486.so</code>, <code>cs.so</code>, <code>metamod</code>, or an AMXX module. That single line usually tells you the culprit before you change anything.</p> | |
| 19 | + | |
| 20 | +<h2>2. Bisect the stack</h2> | |
| 21 | +<p>Strip to bare HLDS and add layers back. First, disable Metamod/AMXX by pointing <code>liblist.gam</code> back at the game DLL directly (or temporarily rename the metamod entry), and start:</p> | |
| 22 | +<pre>./hlds_run -game cstrike +map de_dust2 +maxplayers 12 +sv_lan 1</pre> | |
| 23 | +<ul> | |
| 24 | +<li><strong>Crashes even with a bare server</strong> → the problem is the engine or game DLL (sections 3–4).</li> | |
| 25 | +<li><strong>Boots fine bare, crashes with Metamod</strong> → the problem is Metamod, a plugin or a module (section 5).</li> | |
| 26 | +</ul> | |
| 27 | +<p>This one test cuts the search space in half and is worth the two minutes.</p> | |
| 28 | + | |
| 29 | +<h2>3. Check the architecture of every binary</h2> | |
| 30 | +<p>The single most common startup segfault is a 64-bit object where a 32-bit one belongs. Everything is 32-bit:</p> | |
| 31 | +<pre>file engine_i486.so cstrike/dlls/cs.so cstrike/addons/metamod/metamod_i386.so</pre> | |
| 32 | +<p>Each must say <code>ELF 32-bit LSB shared object, Intel 80386</code>. Any <code>x86-64</code> is your bug — you built or downloaded the wrong architecture. A related symptom is <code>wrong ELF class: ELFCLASS64</code> at load; a hard segfault happens when a mismatched-but-loadable object is called.</p> | |
| 33 | + | |
| 34 | +<h2>4. Mismatched engine and game DLL</h2> | |
| 35 | +<p>A current <a href="/tutorials/install-regamedll-cs16">ReGameDLL</a> against a stale <a href="/tutorials/install-rehlds-on-linux">ReHLDS</a> (or a Valve engine) crashes at init because the interfaces do not match. Update both to releases from the same era. Confirm the engine actually is what you think:</p> | |
| 36 | +<pre>./hlds_run -game cstrike +map de_dust2 +sv_lan 1 | |
| 37 | +] rehlds_version</pre> | |
| 38 | +<p>If it segfaults before you can type that, revert to the Valve <code>engine_i486.so.valve.bak</code> you backed up and confirm the bare Valve stack boots — if it does, the ReHLDS/ReGameDLL pairing is the fault.</p> | |
| 39 | + | |
| 40 | +<h2>5. Metamod, plugins and modules</h2> | |
| 41 | +<p>If the bare server boots, re-enable Metamod and check <code>metamod/plugins.ini</code> and AMXX's <code>plugins.ini</code>. A module built for the wrong AMXX or architecture can segfault at load rather than reporting <code>bad load</code>. Comment out third-party modules and plugins, boot clean, then re-add them a few at a time until it crashes — the last batch you added owns the crash. Related loader failures are covered in <a href="/tutorials/fix-metamod-bad-load">the bad-load fix</a>.</p> | |
| 42 | + | |
| 43 | +<h2>6. Missing 32-bit libraries</h2> | |
| 44 | +<p>A missing i386 runtime library can crash the loader. Verify dependencies resolve:</p> | |
| 45 | +<pre>ldd engine_i486.so | |
| 46 | +ldd cstrike/dlls/cs.so</pre> | |
| 47 | +<p>Any <code>not found</code> is a package to install (<code>lib32gcc-s1</code>, <code>lib32stdc++6</code>, <code>libc6:i386</code>). See <a href="/tutorials/install-cs16-dedicated-server-linux-steamcmd">the base install</a> for the full multiarch setup.</p> | |
| 48 | + | |
| 49 | +<h2>Common errors</h2> | |
| 50 | +<ul> | |
| 51 | +<li><strong><code>Segmentation fault</code> with a <code>debug.log</code> frame in <code>cs.so</code></strong> — bad or mismatched ReGameDLL, or missing <code>delta.lst</code>. Reinstall the matching build.</li> | |
| 52 | +<li><strong>Frame in an AMXX module <code>.so</code></strong> — that module is wrong-architecture or built for another AMXX. Replace it.</li> | |
| 53 | +<li><strong><code>./hlds_linux: No such file or directory</code></strong> (not a segfault) — the 32-bit loader is missing, not the file. Install i386 libs.</li> | |
| 54 | +<li><strong>Crashes only with a full stack, boots bare</strong> — bisect Metamod/plugins/modules as in section 5.</li> | |
| 55 | +</ul> | |
| 56 | + | |
| 57 | +<h2>Verification</h2> | |
| 58 | +<p>Once it boots, prove the stack is coherent rather than merely surviving:</p> | |
| 59 | +<pre>rehlds_version | |
| 60 | +regamedll_version | |
| 61 | +meta list | |
| 62 | +amxx plugins</pre> | |
| 63 | +<p>All four must answer cleanly with no plugin in <code>bad load</code> or <code>error</code>. Then load a map, let a client connect and play a full round including a map change — a segfault that only appears on connect or map change is a different bug, covered in <a href="/tutorials/fix-segfault-map-change">crash on map change</a>. A clean boot plus a clean full round means the startup fault is genuinely resolved.</p> | |
| 64 | + | |
| 65 | +<h2>Reading debug.log without a debugger</h2> | |
| 66 | +<p>You do not need gdb to get value from <code>debug.log</code>. The file HLDS writes on a crash contains a call stack, and the frames are listed innermost-first — the top few name the shared object that was executing when it died. You are looking for the first line that names one of your stack's libraries:</p> | |
| 67 | +<pre>grep -E "engine_i486|cs\.so|metamod|amxmodx|_amxx" debug.log</pre> | |
| 68 | +<p>If the top frame is in <code>cs.so</code>, suspect ReGameDLL and its <code>delta.lst</code>. If it is <code>metamod</code> or an <code>*_amxx.so</code>/<code>*_i386.so</code> module, suspect that module's architecture or version. If it is <code>engine_i486.so</code>, suspect the engine binary or an engine/game-DLL version mismatch. This does not give you a line number, but it reliably names the guilty component — which is exactly the input the bisection in section 2 needs. Ninety percent of startup segfaults are resolved by pairing that one frame with the architecture check in section 3: the faulting library plus "is it actually 32-bit and are its deps present" answers most cases outright, and the rest fall to bisecting the plugin and module set.</p> |
fix-players-timeout-map-change/en.md
A
| @@ -0,0 +1,51 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: Players Time Out or Disconnect on Map Change" | |
| 3 | +excerpt: "Players dropping on every map change usually means slow in-game resource downloads, a precache overflow, or timeout settings too tight. Set up fastdl and cut the resource load." | |
| 4 | +category: "errors" | |
| 5 | +tags: "timeout, mapchange" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>If players survive a whole map but get dropped the moment it changes — "Connection timed out", stuck on the loading screen, or a mass disconnect at <code>changelevel</code> — the problem is almost always the resource transfer that happens between maps. The new map's custom models, sounds and sprites are being pushed to clients over the slow in-game UDP channel, the transfer runs past the timeout, and the client gives up. Less often it is a precache overflow that takes the whole server down. Both are fixable.</p> | |
| 10 | + | |
| 11 | +<h2>1. Set up a fast download (fastdl) server</h2> | |
| 12 | +<p>By default, custom content is sent to clients over the game socket — a trickle that easily exceeds the connection timeout on a content-heavy map. A fastdl (HTTP) mirror moves that transfer to a real web server. In <code>server.cfg</code>:</p> | |
| 13 | +<pre>sv_downloadurl "http://your-cdn.example.com/cstrike/" | |
| 14 | +sv_allowdownload 1 | |
| 15 | +sv_allowupload 1</pre> | |
| 16 | +<p>Mirror your <code>cstrike/</code> content tree (maps, models, sounds, sprites, gfx) under that URL, preserving the folder structure exactly. Clients then fetch resources over HTTP in seconds instead of timing out on UDP. This is the single most effective fix for map-change timeouts on custom-content servers.</p> | |
| 17 | + | |
| 18 | +<h2>2. Give the transfer more room</h2> | |
| 19 | +<p>Even with fastdl, raise the timeout and the per-client upload allowance so a client on a slower link is not cut off mid-transfer:</p> | |
| 20 | +<pre>sv_timeout 120 | |
| 21 | +sv_max_upload 5 | |
| 22 | +sv_allowdownload 1</pre> | |
| 23 | +<p><code>sv_timeout</code> is how long the server waits before declaring a silent client dead — and a client busy downloading a large map looks silent to the game socket. Give it headroom. Do not set it absurdly high on a public server, though, or genuinely dropped clients linger in slots and hold them against your slot count for two minutes each. The client's own <code>cl_dlmax</code> and rate settings matter too, but those live on the player's machine and you cannot force them from the server; a working fastdl mirror is what removes the dependency on them.</p> | |
| 24 | +<p>Keep your network rates sane in <code>server.cfg</code> as well, because a client that renegotiates rate at map load on a starved connection is more likely to stall out during the resource push:</p> | |
| 25 | +<pre>sv_maxrate 25000 | |
| 26 | +sv_minrate 15000 | |
| 27 | +sv_maxupdaterate 101 | |
| 28 | +sv_minupdaterate 30</pre> | |
| 29 | + | |
| 30 | +<h2>3. Rule out a precache overflow</h2> | |
| 31 | +<p>The other cause is the server exceeding an engine table when it loads the new map. GoldSrc caps precached models and sounds, and a plugin (or a heavy map) that precaches too many resources crashes the server at map load with lines like:</p> | |
| 32 | +<pre>PF_precache_model: Model overflow | |
| 33 | +SZ_GetSpace: overflow | |
| 34 | +nummodels == MAX_MODELS</pre> | |
| 35 | +<p>If your log shows one of these at the exact moment of the change, the fix is to <em>reduce</em> precaches, not to tune networking. Audit resource-heavy plugins (custom models, skins, sound packs) and trim what each precaches. A plugin that precaches a model per weapon skin across dozens of skins is the usual offender. Bisect with <a href="/tutorials/find-crashing-plugin">the crashing-plugin method</a>.</p> | |
| 36 | + | |
| 37 | +<h2>4. Consistency kicks look like timeouts</h2> | |
| 38 | +<p>If the server enforces file consistency and a client's copy of a model differs, the client is dropped at map load with a consistency error that reads to players as "it kicks me on map change". Check for <code>Server is enforcing consistency for this file</code> in the client console. Either ship the exact files via fastdl (step 1) so clients match, or relax the consistency enforcement for the offending files.</p> | |
| 39 | + | |
| 40 | +<h2>Common errors</h2> | |
| 41 | +<ul> | |
| 42 | +<li><strong>Only content-heavy maps drop players</strong> — no fastdl; the UDP download times out. Step 1.</li> | |
| 43 | +<li><strong>Every map change crashes the whole server, not just one client</strong> — precache overflow, step 3. Check the log for <code>Model overflow</code> / <code>SZ_GetSpace</code>.</li> | |
| 44 | +<li><strong>fastdl set but clients still download in-game</strong> — the URL structure does not match your <code>cstrike/</code> tree, so the HTTP fetch 404s and the client falls back to UDP. Fix the mirror layout and check for 404s in the web server log.</li> | |
| 45 | +<li><strong>Players on fast connections are fine, slow ones drop</strong> — classic timeout-too-tight; raise <code>sv_timeout</code> and make sure fastdl is actually serving.</li> | |
| 46 | +</ul> | |
| 47 | + | |
| 48 | +<h2>Verification</h2> | |
| 49 | +<p>Connect a client on an ordinary connection and force several map changes across your heaviest custom maps:</p> | |
| 50 | +<pre>changelevel <heavy_custom_map></pre> | |
| 51 | +<p>The client should reach the new map without timing out, and with fastdl working you will see it pull files over HTTP (fast) rather than the slow in-game bar. On the server, watch <code>Log/errors-*.log</code> for precache overflow lines during the change — there should be none. A client that rides through five consecutive changes on your worst maps, with no overflow in the log, means both the transfer and the precache load are under control.</p> |
hlds-systemd-service/en.md
A
| @@ -0,0 +1,98 @@ | ||
| 1 | +--- | |
| 2 | +title: "How to Auto-Start HLDS with a systemd Service" | |
| 3 | +excerpt: "Write a proper systemd unit for HLDS so it starts on boot, restarts on crash, runs as an unprivileged user, and streams its console to journalctl." | |
| 4 | +category: "installation" | |
| 5 | +tags: "systemd, autostart" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>Starting a server by hand in an SSH session works right up until the session closes or the box reboots. A systemd unit fixes that properly: the server starts on boot, restarts if it crashes, runs as an unprivileged user, and its console goes to the journal where you can actually read it. This is the setup that turns a test server into something you can leave running.</p> | |
| 10 | + | |
| 11 | +<h2>1. Decide who restarts on crash</h2> | |
| 12 | +<p>There is one gotcha unique to HLDS. The usual launcher, <code>hlds_run</code>, is a shell wrapper that already restarts <code>hlds_linux</code> after a crash. If systemd <em>also</em> restarts on failure, the two can fight — systemd sees the wrapper still alive and never triggers, or you get confusing double restarts. Pick one:</p> | |
| 13 | +<ul> | |
| 14 | +<li><strong>Let systemd restart</strong> (cleaner): pass <code>-norestart</code> to <code>hlds_run</code> so it exits on crash and systemd brings it back. This is the approach below.</li> | |
| 15 | +<li><strong>Let hlds_run restart</strong>: use <code>Restart=no</code> and rely on the wrapper. You then lose systemd's crash accounting.</li> | |
| 16 | +</ul> | |
| 17 | + | |
| 18 | +<h2>2. Write the unit</h2> | |
| 19 | +<p>Create <code>/etc/systemd/system/hlds.service</code>:</p> | |
| 20 | +<pre>[Unit] | |
| 21 | +Description=CS 1.6 Dedicated Server | |
| 22 | +After=network-online.target | |
| 23 | +Wants=network-online.target | |
| 24 | + | |
| 25 | +[Service] | |
| 26 | +Type=simple | |
| 27 | +User=steam | |
| 28 | +Group=steam | |
| 29 | +WorkingDirectory=/home/steam/hlds | |
| 30 | +ExecStart=/home/steam/hlds/hlds_run -game cstrike -norestart \ | |
| 31 | + -strictportbind -ip 0.0.0.0 -port 27015 \ | |
| 32 | + +map de_dust2 +maxplayers 20 +sv_lan 0 | |
| 33 | +Restart=on-failure | |
| 34 | +RestartSec=5 | |
| 35 | +KillSignal=SIGINT | |
| 36 | + | |
| 37 | +[Install] | |
| 38 | +WantedBy=multi-user.target</pre> | |
| 39 | +<p>Points that matter:</p> | |
| 40 | +<ul> | |
| 41 | +<li><strong><code>User=steam</code></strong> — never run the engine as root. If the unit is under <code>/etc/systemd/system</code> it runs as root unless you set the user.</li> | |
| 42 | +<li><strong><code>WorkingDirectory</code></strong> — HLDS resolves <code>cstrike/</code> relative to the working directory. Get this wrong and it dies with a missing-<code>liblist.gam</code> style error.</li> | |
| 43 | +<li><strong><code>-norestart</code></strong> — hands crash-restart to systemd (see step 1).</li> | |
| 44 | +<li><strong><code>KillSignal=SIGINT</code></strong> — lets HLDS shut down cleanly on <code>systemctl stop</code> instead of being hard-killed.</li> | |
| 45 | +<li><strong><code>network-online.target</code></strong> — the server needs the network up before it heartbeats the master.</li> | |
| 46 | +</ul> | |
| 47 | + | |
| 48 | +<h2>3. Enable and start</h2> | |
| 49 | +<pre>sudo systemctl daemon-reload | |
| 50 | +sudo systemctl enable --now hlds | |
| 51 | +sudo systemctl status hlds</pre> | |
| 52 | +<p><code>enable</code> makes it start on boot; <code>--now</code> starts it immediately. <code>status</code> should show <code>active (running)</code> and the most recent console lines.</p> | |
| 53 | + | |
| 54 | +<h2>4. Read the console</h2> | |
| 55 | +<p>Because the unit is <code>Type=simple</code>, everything HLDS prints goes to the journal:</p> | |
| 56 | +<pre>journalctl -u hlds -f</pre> | |
| 57 | +<p>That is your live console. To see the last boot's output:</p> | |
| 58 | +<pre>journalctl -u hlds -n 100 --no-pager</pre> | |
| 59 | + | |
| 60 | +<h2>5. Sending commands to a running server</h2> | |
| 61 | +<p>systemd captures stdout but does not give you an interactive stdin. To type commands into a running server you have two clean options: use RCON from a client or web tool, or run the server inside a <code>tmux</code>/<code>screen</code> session that systemd launches. If you want console access, wrap the start in tmux:</p> | |
| 62 | +<pre>ExecStart=/usr/bin/tmux new-session -d -s cs16 \ | |
| 63 | + '/home/steam/hlds/hlds_run -game cstrike -norestart -port 27015 +map de_dust2 +maxplayers 20 +sv_lan 0' | |
| 64 | +ExecStop=/usr/bin/tmux kill-session -t cs16 | |
| 65 | +Type=forking | |
| 66 | +RemainAfterExit=yes</pre> | |
| 67 | +<p>Then attach with <code>tmux attach -t cs16</code>. For most people, RCON is enough and the plain unit above is better because systemd tracks the process directly.</p> | |
| 68 | + | |
| 69 | +<h2>6. A template for many servers</h2> | |
| 70 | +<p>Running several instances? Use a template unit <code>[email protected]</code> parameterised by directory, as covered in <a href="/tutorials/multiple-cs16-servers-one-vps">running multiple servers on one VPS</a>, and enable <code>hlds@server1</code>, <code>hlds@server2</code>, and so on.</p> | |
| 71 | + | |
| 72 | +<h2>Common errors</h2> | |
| 73 | +<ul> | |
| 74 | +<li><strong>Unit runs as root</strong> — you forgot <code>User=steam</code>. Add it; a network binary running as root is a real risk.</li> | |
| 75 | +<li><strong><code>status=203/EXEC</code> or "No such file or directory"</strong> — the <code>ExecStart</code> path is wrong, or <code>hlds_run</code> is not executable. Use absolute paths and <code>chmod +x</code>.</li> | |
| 76 | +<li><strong>Server can't find cstrike / <code>liblist.gam</code></strong> — <code>WorkingDirectory</code> is wrong. It must be the server root that contains <code>hlds_run</code> and <code>cstrike/</code>.</li> | |
| 77 | +<li><strong>Double restarts on crash</strong> — both systemd and <code>hlds_run</code> are restarting. Add <code>-norestart</code> to the command.</li> | |
| 78 | +<li><strong>Starts before the network, never lists</strong> — add <code>After=network-online.target</code> and <code>Wants=network-online.target</code>.</li> | |
| 79 | +<li><strong><code>systemctl stop</code> takes 90s then kills it</strong> — add <code>KillSignal=SIGINT</code> so HLDS shuts down cleanly instead of timing out.</li> | |
| 80 | +</ul> | |
| 81 | + | |
| 82 | +<h2>Hardening the unit</h2> | |
| 83 | +<p>Because systemd manages the process directly, you can sandbox it further at no cost. A few directives worth adding to the <code>[Service]</code> block on a public server:</p> | |
| 84 | +<pre>NoNewPrivileges=true | |
| 85 | +PrivateTmp=true | |
| 86 | +ProtectSystem=full | |
| 87 | +ProtectHome=read-only</pre> | |
| 88 | +<p><code>ProtectHome=read-only</code> is too strict if the server writes logs and bans under <code>/home/steam</code> — in that case drop it or switch to <code>ReadWritePaths=/home/steam/hlds</code>. Test each directive one at a time: an over-tight sandbox that blocks a path the engine needs shows up as a startup failure in <code>journalctl</code>, not as an obvious permission message.</p> | |
| 89 | + | |
| 90 | +<h2>Verification</h2> | |
| 91 | +<pre>sudo systemctl restart hlds | |
| 92 | +sudo systemctl status hlds | |
| 93 | +ss -lunp | grep 27015</pre> | |
| 94 | +<p>Status should read <code>active (running)</code> and the port should be bound by <code>hlds_linux</code>. Now reboot the box and confirm the server comes back on its own:</p> | |
| 95 | +<pre>sudo reboot | |
| 96 | +# after it comes back: | |
| 97 | +systemctl is-active hlds</pre> | |
| 98 | +<p>An <code>active</code> result after a cold boot means autostart works. Combine this with an <a href="/tutorials/setup-server-cfg-cs16">annotated server.cfg</a> and the correct <a href="/tutorials/cs16-server-firewall-ports">firewall ports</a> and the server is production-ready.</p> | |
| 0 | 99 |









