Add 3 tutorials (3 files)
Browse Source
3 changed files
with 208 additions and 0 deletions
A
fix-nonsteam-players-cant-join/en.md
+53 -0
A
install-custom-maps-cs16-server/en.md
+68 -0
A
multiple-cs16-servers-one-vps/en.md
+87 -0
fix-nonsteam-players-cant-join/en.md
A
| @@ -0,0 +1,53 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: Non-Steam Players Can't Join My Server" | |
| 3 | +excerpt: "Non-Steam clients get rejected at connect because your server has no non-Steam auth provider. Install and configure Reunion on ReHLDS or dproto on HLDS the right way." | |
| 4 | +category: "errors" | |
| 5 | +tags: "non-steam, reunion" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>A stock CS 1.6 server only accepts clients that pass Steam authentication. Non-Steam clients — which are still a large share of the CS 1.6 population, especially outside the US and Western Europe — get bounced at connect with <code>STEAM validation rejected</code> or <code>No Steam logon</code>, or they simply hang on "Verifying…" and drop. The server is not broken; it just has no component that says "let non-Steam clients in". That component is <strong>Reunion</strong> on ReHLDS, or <strong>dproto</strong> on original HLDS.</p> | |
| 10 | + | |
| 11 | +<h2>1. Pick the right tool for your engine</h2> | |
| 12 | +<ul> | |
| 13 | +<li>Running <a href="/tutorials/install-rehlds-on-linux">ReHLDS</a> (you should be) → use <strong>Reunion</strong>. See <a href="/tutorials/install-reunion">the full Reunion install</a>.</li> | |
| 14 | +<li>Running original Valve HLDS → use <strong>dproto</strong>. See <a href="/tutorials/install-dproto">the dproto install</a>.</li> | |
| 15 | +</ul> | |
| 16 | +<p>Do not run both. They both intercept authentication and will fight each other. This guide covers the Reunion path, which is what a modern server should be on, and points at dproto where it differs.</p> | |
| 17 | + | |
| 18 | +<h2>2. Load Reunion through Metamod</h2> | |
| 19 | +<p>Reunion is a Metamod plugin. Drop its file into <code>cstrike/addons/reunion/</code> and add it to <code>cstrike/addons/metamod/plugins.ini</code>:</p> | |
| 20 | +<pre>linux addons/reunion/reunion_mm_i386.so | |
| 21 | +win32 addons/reunion/reunion_mm.dll</pre> | |
| 22 | +<p>Load Reunion <strong>before</strong> AMX Mod X in <code>plugins.ini</code> — auth has to be in place before the plugin layer starts asking clients for their id. Restart, then confirm it loaded:</p> | |
| 23 | +<pre>meta list</pre> | |
| 24 | +<p>Reunion must show as <code>RUN</code>. If it is <code>bad load</code>, it is the wrong architecture or the path is wrong — check with <code>file</code> and fix the path before going further. The load order is not cosmetic: Metamod loads plugins top to bottom, and if AMXX comes up before the auth layer, plugins that read a player's authid on connect can see the wrong value for the first clients that join after a map change.</p> | |
| 25 | + | |
| 26 | +<h2>3. Set the hash salt — Reunion refuses to load without it</h2> | |
| 27 | +<p>This is the single most common Reunion mistake. Its config (<code>reunion.cfg</code>) requires a <code>SteamIdHashSalt</code> of <strong>at least 16 characters</strong>. If it is shorter or missing, Reunion aborts loading entirely and you are back to non-Steam players being rejected.</p> | |
| 28 | +<pre>SteamIdHashSalt = 8f3Kd92lasQ7zXcV19mT | |
| 29 | +cid_00 = 0 | |
| 30 | +cid_01 = 1 | |
| 31 | +cid_02 = 2</pre> | |
| 32 | +<p>Generate a random string of 16+ characters and keep it secret and <strong>permanent</strong> — the salt is what makes each non-Steam player's generated SteamID stable across sessions. Change it later and every non-Steam player's id changes with it, which wipes out admin access and bans keyed to those ids. The <code>cid_*</code> lines control how each client type is authenticated; read <a href="/tutorials/install-reunion">the Reunion guide</a> for what each mode does before changing them.</p> | |
| 33 | + | |
| 34 | +<h2>4. sv_lan must be 0</h2> | |
| 35 | +<p>With <code>sv_lan 1</code> the engine treats the server as LAN-only, hands every client <code>VALVE_ID_LAN</code>, and rejects internet connections. Non-Steam auth is meaningless in that state. In <code>server.cfg</code>:</p> | |
| 36 | +<pre>sv_lan 0</pre> | |
| 37 | +<p>If your non-Steam players all end up with the same id, that is a related but distinct problem — see <a href="/tutorials/fix-duplicate-steamids-nonsteam">all non-Steam players get the same SteamID</a>.</p> | |
| 38 | + | |
| 39 | +<h2>5. Don't forget voice for non-Steam</h2> | |
| 40 | +<p>Getting non-Steam players <em>in</em> is step one; a common follow-up complaint is that they connect but cannot use voice chat, because stock CS voice depends on Steam. On a ReHLDS server the fix is <a href="/tutorials/install-revoice">Revoice</a>, which restores voice for non-Steam clients. It is a separate component from Reunion, so install it once auth is working — chasing "no voice" before players can even join wastes time.</p> | |
| 41 | + | |
| 42 | +<h2>Common errors</h2> | |
| 43 | +<ul> | |
| 44 | +<li><strong>Reunion shows <code>bad load</code> in <code>meta list</code></strong> — wrong architecture (must be 32-bit i386) or wrong path in <code>plugins.ini</code>.</li> | |
| 45 | +<li><strong>Reunion loads but non-Steam still rejected</strong> — the salt is under 16 characters, so Reunion bailed out after loading. Fix <code>SteamIdHashSalt</code> and restart. This also breaks after the <a href="/tutorials/fix-25th-anniversary-server-broken">25th Anniversary update</a> if you are on an old Reunion build; update it.</li> | |
| 46 | +<li><strong>Steam players now rejected instead</strong> — a mis-set <code>cid_*</code> mode is refusing legitimate Steam clients. Restore the default modes.</li> | |
| 47 | +<li><strong>Running both Reunion and dproto</strong> — remove one. They cannot coexist.</li> | |
| 48 | +</ul> | |
| 49 | + | |
| 50 | +<h2>Verification</h2> | |
| 51 | +<p>From a non-Steam client, connect directly:</p> | |
| 52 | +<pre>connect YOUR_IP:27015</pre> | |
| 53 | +<p>You should spawn normally. Then, in the server console, run <code>status</code> and look at the <code>uniqueid</code> column — a working non-Steam auth shows real <code>STEAM_x:y:z</code> ids, not <code>VALVE_ID_LAN</code> and not the same id repeated for every player. Confirm a Steam client still connects too, so you know you did not trade one group of players for the other. Once both connect and carry distinct ids, non-Steam support is working.</p> |
install-custom-maps-cs16-server/en.md
A
| @@ -0,0 +1,68 @@ | ||
| 1 | +--- | |
| 2 | +title: "How to Add Custom Maps to a CS 1.6 Server" | |
| 3 | +excerpt: "Add custom maps to a CS 1.6 server the right way: where the .bsp and its assets go, the mapcycle, fast download so players actually get the files, and the missing-resource crashes." | |
| 4 | +category: "installation" | |
| 5 | +tags: "maps, custom-content" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>Adding a map is not just dropping a <code>.bsp</code> in a folder. A map references textures, models, sounds and sprites that must all be present on the server, and — for a public server — must be downloadable by every client that joins. Get the file placement right and the crashes disappear; get fast download right and players stop timing out on the loading screen. This guide covers both.</p> | |
| 10 | + | |
| 11 | +<h2>1. Understand what a map is made of</h2> | |
| 12 | +<p>A single custom map is typically several files across several directories inside <code>cstrike/</code>:</p> | |
| 13 | +<pre>maps/de_mymap.bsp <-- the level itself | |
| 14 | +maps/de_mymap.txt <-- optional MOTD/overview text | |
| 15 | +maps/de_mymap.res <-- optional resource list (used by fast download) | |
| 16 | +overviews/de_mymap.bmp <-- radar overview | |
| 17 | +overviews/de_mymap.txt | |
| 18 | +sound/mymap/... <-- custom sounds the map plays | |
| 19 | +gfx/env/... <-- custom skybox, if any | |
| 20 | +models/... sprites/... <-- any custom models/sprites</pre> | |
| 21 | +<p>Many maps embed their textures inside the <code>.bsp</code>; those need nothing extra. Maps that reference external WADs need those WADs present too, or players see missing textures.</p> | |
| 22 | + | |
| 23 | +<h2>2. Copy the files, preserving structure</h2> | |
| 24 | +<pre>cp de_mymap.bsp /home/steam/hlds/cstrike/maps/ | |
| 25 | +cp de_mymap.txt /home/steam/hlds/cstrike/maps/ | |
| 26 | +cp overview.bmp /home/steam/hlds/cstrike/overviews/de_mymap.bmp</pre> | |
| 27 | +<p>Filenames are case-sensitive on Linux. <code>De_MyMap.bsp</code> and a mapcycle entry of <code>de_mymap</code> will not match, and the server will report the map as not found.</p> | |
| 28 | + | |
| 29 | +<h2>3. Add the map to the mapcycle and vote list</h2> | |
| 30 | +<p>The rotation is driven by <code>mapcycle.txt</code> in <code>cstrike/</code>:</p> | |
| 31 | +<pre>de_dust2 | |
| 32 | +de_mymap | |
| 33 | +de_inferno</pre> | |
| 34 | +<p>If you run a map-vote or nomination plugin, add the map to its map list too (commonly <code>addons/amxmodx/configs/maps.ini</code>). A map in <code>mapcycle.txt</code> but not the vote list can be rotated to but not chosen by players, and vice versa.</p> | |
| 35 | + | |
| 36 | +<h2>4. Set up fast download (this is the part people skip)</h2> | |
| 37 | +<p>Without fast download, clients pull custom files over the game channel at a crawl and frequently time out. Point clients at an HTTP mirror of your <code>cstrike/</code> tree:</p> | |
| 38 | +<pre>sv_downloadurl "http://dl.example.com/cstrike" | |
| 39 | +sv_allowdownload 1 | |
| 40 | +sv_allowupload 1</pre> | |
| 41 | +<p>On the web host, the directory served at that URL must mirror the layout under <code>cstrike/</code> exactly — the map's <code>.bsp</code> at <code>maps/de_mymap.bsp</code>, sounds at <code>sound/...</code>, and so on. The client requests the same relative path it needs locally. A <code>.res</code> file next to the <code>.bsp</code> lists every asset the client should pre-download, which prevents the "connected but missing sounds" experience.</p> | |
| 42 | + | |
| 43 | +<h2>5. Compress for fast download (optional but standard)</h2> | |
| 44 | +<p>The engine's HTTP downloader also accepts bzip2-compressed copies. Ship <code>de_mymap.bsp.bz2</code> alongside <code>de_mymap.bsp</code> on the download host and clients grab the smaller file:</p> | |
| 45 | +<pre>bzip2 -k maps/de_mymap.bsp # keep the original, produce de_mymap.bsp.bz2</pre> | |
| 46 | +<p>Do this for the large assets. The uncompressed file must still exist on the game server itself — the <code>.bz2</code> is only for the download mirror.</p> | |
| 47 | + | |
| 48 | +<h2>Common errors</h2> | |
| 49 | +<ul> | |
| 50 | +<li><strong><code>Host_Error: Map change failed: 'de_mymap' not found on server</code></strong> — the <code>.bsp</code> is missing, misspelled, or wrong-case in <code>maps/</code>.</li> | |
| 51 | +<li><strong><code>Host_Error: PF_precache_sound_I: 'mymap/x.wav' Precache can only be done in spawn functions</code></strong> or <code>failed to load</code> — a sound/model the map calls is not on the server. Copy the map's <code>sound/</code> and <code>models/</code> assets.</li> | |
| 52 | +<li><strong>Purple/black checkerboard textures</strong> — a referenced WAD is missing, or the map expected embedded textures that were stripped. Get the complete map package.</li> | |
| 53 | +<li><strong>Players stuck at "Verifying and downloading resources"</strong> — no <code>sv_downloadurl</code>, or the mirror layout does not match. Test the URL in a browser: the <code>.bsp</code> must download from the exact path.</li> | |
| 54 | +<li><strong>Map runs but no radar</strong> — the <code>overviews/</code> <code>.bmp</code> and <code>.txt</code> are missing or misnamed.</li> | |
| 55 | +</ul> | |
| 56 | + | |
| 57 | +<h2>Verification</h2> | |
| 58 | +<p>From the server console force the map:</p> | |
| 59 | +<pre>changelevel de_mymap</pre> | |
| 60 | +<p>The server must load it without a <code>Host_Error</code>. Then join as a client from a machine that has never seen the map: you should download the assets over HTTP quickly and spawn into a fully-textured level with a working radar. If the download bar crawls, fast download is not wired up; if you spawn but hear no custom sounds, the <code>sound/</code> assets are missing from either the server or the mirror. Once maps rotate cleanly, tie the rotation to player count with your map-vote plugin and confirm entries match between <code>mapcycle.txt</code> and the vote list.</p> | |
| 61 | + | |
| 62 | +<h2>Batch-adding a map pack</h2> | |
| 63 | +<p>Adding one map is manual; adding fifty is a script. When you drop a pack in, the failure mode changes from "one wrong path" to "one of these fifty maps is missing an asset and crashes the rotation at 3am". Two habits make a pack safe. First, verify every <code>.bsp</code> is actually present and named as the mapcycle expects:</p> | |
| 64 | +<pre>cd /home/steam/hlds/cstrike | |
| 65 | +while read m; do [ -f "maps/$m.bsp" ] || echo "MISSING: $m"; done < mapcycle.txt</pre> | |
| 66 | +<p>Anything printed is a mapcycle entry with no map file — it will break rotation when reached. Second, mirror the entire pack to fast download in one pass and generate the <code>.bz2</code> copies so clients are not pulling megabytes over the game channel:</p> | |
| 67 | +<pre>find maps -name "*.bsp" -exec bzip2 -k {} \;</pre> | |
| 68 | +<p>Do the same for large <code>sound/</code> and <code>models/</code> assets the maps add. The uncompressed originals stay on the game server; only the mirror serves the <code>.bz2</code>. A pack installed this way either boots clean or tells you exactly which map is incomplete before a player ever finds out the hard way.</p> |
multiple-cs16-servers-one-vps/en.md
A
| @@ -0,0 +1,87 @@ | ||
| 1 | +--- | |
| 2 | +title: "How to Run Multiple CS 1.6 Servers on One VPS" | |
| 3 | +excerpt: "Run several CS 1.6 instances on one box using separate ports and directories, share the base install to save disk, and manage them all with a systemd template unit." | |
| 4 | +category: "installation" | |
| 5 | +tags: "vps, multi-server" | |
| 6 | +image: "" | |
| 7 | +read_time: "9 min read" | |
| 8 | +--- | |
| 9 | +<p>One VPS can comfortably host several CS 1.6 servers — the engine is light and a modern core has cycles to spare. The rules are simple: each instance needs its own UDP port, its own working directory for logs and configs, and its own process supervisor. This guide sets up three, but the pattern scales to as many as your CPU and bandwidth allow.</p> | |
| 10 | + | |
| 11 | +<h2>1. One base install, many instances</h2> | |
| 12 | +<p>A full App 90 install is a few hundred megabytes, and copying it per server wastes disk and makes updates painful. Install it once, then create per-instance directories that share the read-only engine and mod files via hardlinks, keeping only configs and logs separate. Start from a working base at <code>/home/steam/hlds</code> (see <a href="/tutorials/install-cs16-dedicated-server-linux-steamcmd">the SteamCMD install</a>), then:</p> | |
| 13 | +<pre>cd /home/steam | |
| 14 | +cp -al hlds server1 | |
| 15 | +cp -al hlds server2 | |
| 16 | +cp -al hlds server3</pre> | |
| 17 | +<p><code>cp -al</code> makes an archive copy with hardlinks: the binaries and maps occupy disk once, but each directory can hold its own <code>cstrike/server.cfg</code>, <code>logs/</code>, and <code>banned.cfg</code>. When you re-run <code>app_update 90</code> on the base, hardlinked files update in place; regenerate the copies after a major engine change to be safe.</p> | |
| 18 | + | |
| 19 | +<h2>2. Give each instance its own port and identity</h2> | |
| 20 | +<p>The engine's UDP port is set with <code>-port</code>. Use a clean numbering scheme so the firewall and your memory both stay sane:</p> | |
| 21 | +<table> | |
| 22 | +<thead><tr><th>Instance</th><th>Directory</th><th>Port</th></tr></thead> | |
| 23 | +<tbody> | |
| 24 | +<tr><td>server1</td><td>/home/steam/server1</td><td>27015</td></tr> | |
| 25 | +<tr><td>server2</td><td>/home/steam/server2</td><td>27016</td></tr> | |
| 26 | +<tr><td>server3</td><td>/home/steam/server3</td><td>27017</td></tr> | |
| 27 | +</tbody> | |
| 28 | +</table> | |
| 29 | +<p>Each instance's <code>cstrike/server.cfg</code> needs a distinct <code>hostname</code> and its own <code>rcon_password</code>. If the VPS has multiple public IPs you can instead bind each server to a different IP with <code>-ip</code> and reuse port 27015; most people just use different ports on one IP.</p> | |
| 30 | + | |
| 31 | +<h2>3. Start them by hand first</h2> | |
| 32 | +<p>Prove each one boots before automating anything:</p> | |
| 33 | +<pre>cd /home/steam/server1 | |
| 34 | +./hlds_run -game cstrike -strictportbind -ip 0.0.0.0 -port 27015 \ | |
| 35 | + +map de_dust2 +maxplayers 20 +sv_lan 0 | |
| 36 | + | |
| 37 | +cd /home/steam/server2 | |
| 38 | +./hlds_run -game cstrike -strictportbind -ip 0.0.0.0 -port 27016 \ | |
| 39 | + +map de_dust2 +maxplayers 20 +sv_lan 0</pre> | |
| 40 | +<p><code>-strictportbind</code> is important here: without it, a second server started on an already-used port silently grabs the next free one, and you end up with two servers on 27016 and none on 27015, wondering why a client cannot connect.</p> | |
| 41 | + | |
| 42 | +<h2>4. Manage them with a systemd template</h2> | |
| 43 | +<p>Instead of one unit per server, write a single template unit parameterised by directory and port. Create <code>/etc/systemd/system/[email protected]</code>:</p> | |
| 44 | +<pre>[Unit] | |
| 45 | +Description=CS 1.6 server (%i) | |
| 46 | +After=network.target | |
| 47 | + | |
| 48 | +[Service] | |
| 49 | +Type=simple | |
| 50 | +User=steam | |
| 51 | +WorkingDirectory=/home/steam/%i | |
| 52 | +ExecStart=/home/steam/%i/hlds_run -game cstrike -strictportbind \ | |
| 53 | + -ip 0.0.0.0 -port ${PORT} +map de_dust2 +maxplayers 20 +sv_lan 0 | |
| 54 | +Restart=on-failure | |
| 55 | +RestartSec=5 | |
| 56 | + | |
| 57 | +[Install] | |
| 58 | +WantedBy=multi-user.target</pre> | |
| 59 | +<p>Because each instance needs a different port, drop an override per instance:</p> | |
| 60 | +<pre>sudo systemctl edit hlds@server2</pre> | |
| 61 | +<pre>[Service] | |
| 62 | +Environment=PORT=27016</pre> | |
| 63 | +<p>Then enable and start each — the full template approach is covered in <a href="/tutorials/hlds-systemd-service">the systemd service guide</a>:</p> | |
| 64 | +<pre>sudo systemctl enable --now hlds@server1 | |
| 65 | +sudo systemctl enable --now hlds@server2 | |
| 66 | +sudo systemctl enable --now hlds@server3</pre> | |
| 67 | + | |
| 68 | +<h2>5. Open the ports</h2> | |
| 69 | +<pre>sudo ufw allow 27015:27017/udp | |
| 70 | +sudo ufw allow 27015:27017/tcp</pre> | |
| 71 | +<p>The TCP range is for RCON. If you also run HLTV per instance, allocate a second block (27020+) and open it too — see <a href="/tutorials/cs16-server-firewall-ports">which ports to open</a>.</p> | |
| 72 | + | |
| 73 | +<h2>Common errors</h2> | |
| 74 | +<ul> | |
| 75 | +<li><strong>Two servers, one shows, one does not</strong> — a port collision the engine resolved silently. Add <code>-strictportbind</code> and give each a unique <code>-port</code>.</li> | |
| 76 | +<li><strong>Both servers write to the same log</strong> — they share a <code>cstrike/</code> directory. Each instance needs its own directory (the <code>cp -al</code> copies above).</li> | |
| 77 | +<li><strong>RCON controls the wrong server</strong> — same <code>rcon_password</code> and you connected to the wrong port. Use distinct passwords and connect by port.</li> | |
| 78 | +<li><strong>High load with several full servers</strong> — you set an aggressive <code>sys_ticrate</code>/<code>-pingboost</code> on every instance. FPS scales per process; do not run four servers each pinned at 1000 FPS on a two-core box.</li> | |
| 79 | +</ul> | |
| 80 | + | |
| 81 | +<h2>Plan the resources honestly</h2> | |
| 82 | +<p>Instances are cheap in disk but not free in CPU and bandwidth. A busy 32-slot server pushes real traffic, and running several full ones on a low-core VPS with an aggressive <code>-pingboost</code> or high <code>sys_ticrate</code> on each will starve them all. A useful rule of thumb: budget one physical core per busy public server if you want stable server FPS, and check your provider's bandwidth cap — several full servers can move a surprising amount of egress. Maps are shared through the hardlinked copies, so adding a custom map to the base and re-running <code>cp -al</code> propagates it without duplicating disk. Keep each instance's <code>rcon_password</code> distinct and stored only in that instance's <code>cstrike/server.cfg</code>, readable only by the <code>steam</code> user, so one leaked password does not hand over every server.</p> | |
| 83 | + | |
| 84 | +<h2>Verification</h2> | |
| 85 | +<pre>systemctl status 'hlds@*' | |
| 86 | +ss -lunp | grep hlds</pre> | |
| 87 | +<p>You should see one <code>hlds_linux</code> bound per port (27015, 27016, 27017). Connect a client to each port in turn and confirm the hostname differs. When all instances answer independently and survive a reboot, the multi-server setup is done.</p> | |
| 0 | 88 |









