How to Auto-Start HLDS with a systemd Service

April 22, 2026 Daemon666 8 min read 18 görüntülenme

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.

1. Decide who restarts on crash

There is one gotcha unique to HLDS. The usual launcher, hlds_run, is a shell wrapper that already restarts hlds_linux after a crash. If systemd also restarts on failure, the two can fight — systemd sees the wrapper still alive and never triggers, or you get confusing double restarts. Pick one:

  • Let systemd restart (cleaner): pass -norestart to hlds_run so it exits on crash and systemd brings it back. This is the approach below.
  • Let hlds_run restart: use Restart=no and rely on the wrapper. You then lose systemd's crash accounting.

2. Write the unit

Create /etc/systemd/system/hlds.service:

[Unit]
Description=CS 1.6 Dedicated Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=steam
Group=steam
WorkingDirectory=/home/steam/hlds
ExecStart=/home/steam/hlds/hlds_run -game cstrike -norestart \
  -strictportbind -ip 0.0.0.0 -port 27015 \
  +map de_dust2 +maxplayers 20 +sv_lan 0
Restart=on-failure
RestartSec=5
KillSignal=SIGINT

[Install]
WantedBy=multi-user.target

Points that matter:

  • User=steam — never run the engine as root. If the unit is under /etc/systemd/system it runs as root unless you set the user.
  • WorkingDirectory — HLDS resolves cstrike/ relative to the working directory. Get this wrong and it dies with a missing-liblist.gam style error.
  • -norestart — hands crash-restart to systemd (see step 1).
  • KillSignal=SIGINT — lets HLDS shut down cleanly on systemctl stop instead of being hard-killed.
  • network-online.target — the server needs the network up before it heartbeats the master.

3. Enable and start

sudo systemctl daemon-reload
sudo systemctl enable --now hlds
sudo systemctl status hlds

enable makes it start on boot; --now starts it immediately. status should show active (running) and the most recent console lines.

4. Read the console

Because the unit is Type=simple, everything HLDS prints goes to the journal:

journalctl -u hlds -f

That is your live console. To see the last boot's output:

journalctl -u hlds -n 100 --no-pager

5. Sending commands to a running server

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 tmux/screen session that systemd launches. If you want console access, wrap the start in tmux:

ExecStart=/usr/bin/tmux new-session -d -s cs16 \
  '/home/steam/hlds/hlds_run -game cstrike -norestart -port 27015 +map de_dust2 +maxplayers 20 +sv_lan 0'
ExecStop=/usr/bin/tmux kill-session -t cs16
Type=forking
RemainAfterExit=yes

Then attach with tmux attach -t cs16. For most people, RCON is enough and the plain unit above is better because systemd tracks the process directly.

6. A template for many servers

Running several instances? Use a template unit [email protected] parameterised by directory, as covered in running multiple servers on one VPS, and enable hlds@server1, hlds@server2, and so on.

Common errors

  • Unit runs as root — you forgot User=steam. Add it; a network binary running as root is a real risk.
  • status=203/EXEC or "No such file or directory" — the ExecStart path is wrong, or hlds_run is not executable. Use absolute paths and chmod +x.
  • Server can't find cstrike / liblist.gamWorkingDirectory is wrong. It must be the server root that contains hlds_run and cstrike/.
  • Double restarts on crash — both systemd and hlds_run are restarting. Add -norestart to the command.
  • Starts before the network, never lists — add After=network-online.target and Wants=network-online.target.
  • systemctl stop takes 90s then kills it — add KillSignal=SIGINT so HLDS shuts down cleanly instead of timing out.

Hardening the unit

Because systemd manages the process directly, you can sandbox it further at no cost. A few directives worth adding to the [Service] block on a public server:

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=read-only

ProtectHome=read-only is too strict if the server writes logs and bans under /home/steam — in that case drop it or switch to ReadWritePaths=/home/steam/hlds. 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 journalctl, not as an obvious permission message.

Verification

sudo systemctl restart hlds
sudo systemctl status hlds
ss -lunp | grep 27015

Status should read active (running) and the port should be bound by hlds_linux. Now reboot the box and confirm the server comes back on its own:

sudo reboot
# after it comes back:
systemctl is-active hlds

An active result after a cold boot means autostart works. Combine this with an annotated server.cfg and the correct firewall ports and the server is production-ready.

Katkıda bulunanlar: Daemon666 ✦
Paylaş: