Containerized Counter-Strike 1.6 scoutzknivez server using the ReHLDS stack.
- ReHLDS 3.14.0.857 — reverse-engineered engine
- ReGameDLL_CS 5.28.0.756 — reverse-engineered game DLL
- Metamod-R 1.3.0.149 — plugin loader
- AMX Mod X 1.10 — scripting platform
- ReAPI 5.26.0.338 — extended API
curl -fsSL https://raw.githubusercontent.com/KevinTCoughlin/cs-server/main/install.sh | bashRequires Podman (rootless) or Docker. The script pulls the image from ghcr.io, creates config files, and starts the server on port 27015. With Podman it installs a systemd Quadlet unit for auto-start; with Docker it creates a container with restart policy.
To uninstall: curl -fsSL https://raw.githubusercontent.com/KevinTCoughlin/cs-server/main/install.sh | bash -s -- --uninstall
Pre-built images are available on GitHub Container Registry:
podman pull ghcr.io/kevintcoughlin/cs-server:latest
podman run -d -p 27015:27015/udp -p 27015:27015/tcp \
--name scoutzknivez ghcr.io/kevintcoughlin/cs-server:latestThe server starts on port 27015. Connect with your CS 1.6 client:
connect <your-lan-ip>:27015
Docker users: replace
podmanwithdockerin the commands below.
If you need custom maps or configurations:
Place .bsp files into the maps/ directory. They get baked into the image at build time.
Maps are not included in the repo due to file size. You can find them on sites like GameBanana or 17buddies.
podman compose up --buildThe server starts on port 27015. Connect with your CS 1.6 client:
connect <your-lan-ip>:27015
Docker Desktop for Windows cannot reliably forward UDP game traffic to containers (#18). Run the server natively inside a WSL2 Debian distro instead.
wsl --install Debianwsl -d Debian
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y ca-certificates curl lib32gcc-s1 lib32stdc++6 \
lib32z1 libc6-i386 patchelf unzip tarcurl -fsSL https://raw.githubusercontent.com/KevinTCoughlin/cs-server/main/install.sh | bashOr build from source (from the repo root on the Windows filesystem):
cd /mnt/c/path/to/cs-server
# Follow the Containerfile steps manually or use the install scriptFrom your Windows CS 1.6 client, open the console and connect using the WSL2 IP:
# Find your WSL2 IP
wsl -d Debian -- ip addr show eth0 | grep "inet "connect <wsl2-ip>:27015
Note: The WSL2 IP changes on reboot.
localhost:27015may also work depending on your WSL2 networking mode.
Edit files in config/ — they're bind-mounted into the container:
| File | Purpose |
|---|---|
config/server.cfg |
Game settings (gravity, round time, etc.) |
config/mapcycle.txt |
Map rotation |
config/autoexec.cfg |
Runs on server start |
Changes to server.cfg take effect next round or via rcon exec server.cfg.
| Setting | Value | Effect |
|---|---|---|
sv_gravity |
240 | Low gravity (default is 800) |
sv_airaccelerate |
100 | High air control |
mp_freezetime |
0 | No freeze on round start |
mp_buytime |
0 | No buy menu |
mp_roundtime |
3 | 3 minute rounds |
mp_startmoney |
16000 | Max money (unused) |
| Plugin | Description |
|---|---|
scoutzknivez.amxx |
Strips weapons on spawn, gives scout + knife with ammo |
autobhop.amxx |
Auto bunny hop — hold jump to bhop (essential for low-grav) |
AQS.amxx |
Advanced Quake Sounds v8.0 — kill streaks, multi-kills, headshots, knife kills with HUD + sound announcements. Toggle with !sounds in chat |
rtv.amxx |
Rock the Vote — type rtv to vote for map change, nominate <map> to add maps |
websitebot.amxx |
Adds a spectator bot showing the server website in the scoreboard |
afkkicker.amxx |
Moves idle players to spectator after 60s, kicks after 3min |
highpingkicker.amxx |
Warns then kicks players exceeding 150ms average ping |
advertisements.amxx |
Rotating chat messages with server info and commands |
antiflood.amxx |
Chat spam prevention (built-in AMX Mod X) |
The AQS plugin looks for WAV files in sound/quake/. These are not included (copyrighted). See sound/quake/README.md for sourcing instructions. The plugin works in HUD-only mode without them.
Custom content (maps, sounds) is served via GitHub Pages so clients download over HTTP instead of the game server's slow built-in transfer (~5 KB/s).
How it works: sv_downloadurl in server.cfg points clients to https://kevintcoughlin.com/cs-server/cstrike/. The engine tries .bz2 compressed files first, then falls back to uncompressed.
just fastdlThis copies files from sound/quake/ and maps/ into docs/cstrike/ with .bz2 compressed variants, then commit and push to deploy via GitHub Pages.
- Place maps in
maps/and sounds insound/quake/ - Run
just fastdlto sync and compress - Commit the
docs/cstrike/changes and push tomain - GitHub Pages auto-deploys from
docs/
| Variable | Default | Description |
|---|---|---|
MAP |
scoutzknivez |
Starting map |
MAXPLAYERS |
20 |
Max player slots |
PORT |
27015 |
Server port |
BOTS |
1 |
Enable bots (1=on, 0=off) |
NOMASTER |
0 |
Disable master server (1=on, 0=off). Enable for Docker Desktop on Windows |
Bots are enabled by default using ReGameDLL_CS's built-in ZBot support — no additional plugins or downloads required.
The bot configuration in config/server.cfg keeps the server populated when human players aren't present:
| Cvar | Value | Effect |
|---|---|---|
bot_quota |
10 | Target number of bots to maintain |
bot_quota_mode |
fill |
Bots fill empty slots; leave as humans join |
bot_difficulty |
1 | Normal difficulty |
bot_join_after_player |
0 | Bots join immediately (no human player required) |
bot_auto_vacate |
1 | Bots leave automatically to make room for humans |
bot_allow_rogues |
0 | Bots follow orders |
bot_knives_only |
0 | Bots use normal weapons (scoutzknivez plugin handles stripping) |
To disable bots, set BOTS=0 in compose.yml or your environment.
Set an RCON password by adding to config/server.cfg:
rcon_password "your_password_here"
Then in-game: rcon_password your_password_here followed by rcon <command>.
- Write
.smasource inplugins/amxmodx/scripting/ - Add the
.smafilename to the compile loop inContainerfile - Add the compiled
.amxxfilename toplugins/amxmodx/plugins.ini - Rebuild:
podman compose up --build
The server runs as a rootless Podman Quadlet unit managed by systemd.
# Build the image (tags as localhost/cs-server:latest)
podman compose up --build -d && podman compose down
# Reload systemd and start
systemctl --user daemon-reload
systemctl --user start scoutzknivezsystemctl --user start scoutzknivez # start
systemctl --user stop scoutzknivez # graceful stop (30s countdown)
systemctl --user restart scoutzknivez # restart
systemctl --user status scoutzknivez # check status
podman logs -f scoutzknivez # follow logspodman compose up --build -d && podman compose down
systemctl --user restart scoutzknivezWhen the server receives a stop signal, it announces the shutdown to connected players:
- T-30s: "Shutting down in 30 seconds..."
- T-10s: "Shutting down in 10 seconds..."
- T-5s: "Shutting down in 5 seconds..."
- T-2s: "Shutting down in 2 seconds..."
- T-1s: "Goodbye!"
- T-0s:
quitcommand sent to HLDS
The Quadlet gives the container 45 seconds for the shutdown sequence, with a 50-second systemd timeout as a buffer.
The Quadlet is configured with WantedBy=default.target, so the server starts automatically on boot (after loginctl enable-linger is set for the user).
Restart=on-failure restarts the container after 10 seconds if HLDS crashes, but stays stopped on a clean systemctl stop.
- No Linux capabilities — all capabilities dropped via
DropCapability=ALL - No privilege escalation —
NoNewPrivileges=trueprevents gaining new privileges via setuid/setgid binaries - System user with nologin shell — HLDS runs as a system user (
-r) with/usr/sbin/nologin, preventing interactive login - Setuid/setgid bits stripped — all setuid/setgid bits removed from the runtime image
- Resource limits —
MemoryMax=512M,CPUQuota=200%(2 cores max)
Rate limiting and anti-abuse cvars in server.cfg:
| Cvar | Value | Purpose |
|---|---|---|
sv_max_queries_sec |
3 | Rate-limit server info queries (anti-amplification) |
sv_max_queries_window |
30 | Query rate window in seconds |
sv_rcon_maxfailures |
5 | Lock RCON after 5 bad attempts |
sv_rcon_banpenalty |
60 | 60 minute RCON ban on failure |
Only the required ports should be open:
# CS 1.6 server
firewall-cmd --permanent --zone=FedoraWorkstation --add-port=27015/udp
firewall-cmd --permanent --zone=FedoraWorkstation --add-port=27015/tcp
firewall-cmd --reloadThe server is preconfigured with HLDS/ReHLDS network and tick-rate optimizations:
- Network buffer sysctls (
net.core.rmem_max,net.core.wmem_max) — applied automatically per-container via compose, Quadlet, and install.sh. No admin action required. - Host kernel timer and CPU governor — detected automatically by
install.shduring installation. The script checks the current state and prints actionable commands if changes are recommended.
HLDS benefits from a 1000 Hz kernel timer. The installer checks CONFIG_HZ and warns if below 1000. On Debian/Ubuntu:
sudo apt install linux-image-lowlatency
sudo rebootOr set CONFIG_HZ=1000 in a custom kernel config.
The installer checks the CPU frequency governor and warns if not set to performance. To apply:
sudo cpupower frequency-set -g performance