Because HLDS is single-threaded, a multi-core box is really a set of independent lanes, and a well-organised admin can run five, eight, even a dozen CS 1.6 servers on one machine without any of them lagging. The trick is treating each instance as an isolated tenant: its own core, its own config, its own port, and an eye on the shared resources they all draw from. Here is the layout that scales.
1. One instance per core, pinned
The foundation is that each server gets a dedicated physical core and stays there. Without pinning, the scheduler migrates instances between cores under load, thrashing CPU caches and causing fps jitter across all of them. Pin at launch with taskset:
taskset -c 0 ./hlds_run -game cstrike +port 27015 +exec server1.cfg & taskset -c 1 ./hlds_run -game cstrike +port 27016 +exec server2.cfg & taskset -c 2 ./hlds_run -game cstrike +port 27017 +exec server3.cfg &
Do not oversubscribe: if you have 6 cores, run at most 5–6 busy instances and leave a core for the OS, the master-server traffic and your web/FastDL stack. The full pinning method is in CPU affinity with taskset and the multi-server layout in multiple servers on one VPS.
2. Cap fps sanely per instance
Every instance chasing 1000 fps multiplies CPU draw. If five servers each burn a core holding 1000 fps, you have committed five full cores to frame generation alone. Decide honestly what each server needs: a competitive 5v5 benefits from 1000 fps for tight hitreg, a casual 10-slot fun server is perfectly smooth at 500. Set it per instance so you are not paying for fps a server does not use — see sys_ticrate and stable 1000 fps.
3. Fully separate configs, ports and content
Each instance needs its own server.cfg, its own +port (27015, 27016, ...), its own RCON password, and ideally its own log directory so you can tell them apart:
+port 27016 +maxplayers 24 +map de_dust2 +servercfgfile server2.cfg
Sharing one config across instances is how you end up with two servers fighting over the same log file or announcing the same hostname. Keep them independent; they can share the read-only game files but not their runtime config.
4. Force the performance governor once, for the whole box
A single machine-wide setting benefits every instance: stop the cores downclocking between frames.
cpupower frequency-set -g performance
On a box packed with servers this is one of the highest-value changes — a core that drops to 800 MHz to "save power" between ticks introduces jitter across whatever instance lives on it. Detail in the performance governor guide.
5. Watch the shared resources
Cores are isolated by pinning, but three things are shared and will bite you as you scale:
- Aggregate CPU — read per-core, not total. Each pinned instance should sit below 100% on its own core at peak.
- RAM and precache — each instance loads its own copy of map models and sounds. Ten servers running heavy custom-content maps can exhaust RAM even when CPU is fine; watch
free -mand keep precache lean per map. - Bandwidth — every player on every server draws from one NIC. Do the bandwidth math: total players × per-player rate must fit your uplink or every server chokes at once.
Common errors
- All servers stutter together at peak — shared bandwidth saturated, or the governor is powersave. Check the NIC and the governor first.
- One server lags, others fine — that instance's core is at 100% (a heavy plugin or fps target). Pin it and lighten its load.
- Instances drift between cores — you launched without
taskset; the scheduler is migrating them. - Out-of-memory kills a server — too many heavy-content maps loaded at once; trim precache and watch
free -m. - Two servers show the same name / share a log — they are reading the same config. Give each its own
server.cfgand port.
Verification
Load every instance to a realistic population simultaneously and watch per-core usage and memory:
top -H free -m
Each pinned core should stay under 100% and total RAM should have headroom. Join each server and confirm net_graph 3 shows zero choke on all of them at once — if choke appears on all servers together the moment they fill, it is a shared bottleneck (bandwidth or governor), not any single instance. Scale the number of servers to the number of cores minus one, and no further.









