---
title: "How to Install HLDS as a Non-Root User (Best Practice)"
excerpt: "Run HLDS under a dedicated unprivileged user, set the right ownership, and use a systemd unit that drops privileges — the correct, secure way to host CS 1.6."
category: "installation"
tags: "linux, permissions"
image: ""
read_time: "8 min read"
---
<p>Running a game server as <code>root</code> is a habit worth breaking permanently. HLDS parses untrusted network traffic from anyone on the internet; if a flaw is ever exploited, the attacker inherits whatever privileges the process holds. Run it as <code>root</code> and that is the whole machine. Run it as a locked-down user and the blast radius is one game server's files. There is no performance cost and no functional downside, so this is simply how it should be done.</p>
<h2>1. Why root is unnecessary here</h2>
<p>The one reason a service traditionally needs root is binding a port below 1024. HLDS uses <strong>UDP 27015</strong>, which is well above that range, so an unprivileged user can bind it without any special capability. There is nothing HLDS does that requires elevated privileges — the only reason people run it as root is that they installed as root and never switched.</p>
<h2>2. Create a dedicated user</h2>
<p>Make a user that exists only to run the server, with no login password and no shell privileges beyond what it needs:</p>
<pre>sudo adduser --disabled-password --gecos "" csserver</pre>
<p>Use a service-specific account, not your own login account — you want the server isolated from your personal files and SSH keys. Everything the server touches will be owned by <code>csserver</code>.</p>
<h2>3. Install everything as that user</h2>
<p>Switch to the new user and install SteamCMD and HLDS from there, so every file is created with the right ownership from the start:</p>
<pre>sudo su - csserver
mkdir -p ~/steamcmd ~/hlds
cd ~/steamcmd
curl -sqL "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar zxf -
./steamcmd.sh +force_install_dir ~/hlds +login anonymous +app_update 90 validate +quit</pre>
<p>Installing as <code>csserver</code> avoids the most common mistake: installing as root and later getting <code>Permission denied</code> when the game process cannot write its own logs or download files. The full install is in <a href="/tutorials/install-cs16-dedicated-server-linux-steamcmd">the SteamCMD guide</a>; the 32-bit libraries it needs are in <a href="/tutorials/install-32bit-libraries-cs16-linux">32-bit libraries for CS 1.6</a>.</p>
<h2>4. If you already installed as root, fix ownership</h2>
<p>Inherited a root-owned install? Do not reinstall — just hand it to the new user:</p>
<pre>sudo chown -R csserver:csserver /home/csserver/hlds</pre>
<p>Then always start the server as <code>csserver</code>, never with <code>sudo</code>. A single root-owned file in the tree (often a log or <code>.dem</code> written during a root test) can cause a <code>Permission denied</code> later, so re-run the <code>chown</code> if anything misbehaves.</p>
<h2>5. Run it under systemd with dropped privileges</h2>
<p>The clean way to keep it non-root permanently is a systemd unit that sets <code>User=</code>, so the service can never run as root even if you start it as root. Create <code>/etc/systemd/system/hlds.service</code>:</p>
<pre>[Unit]
Description=CS 1.6 HLDS Server
After=network.target
[Service]
Type=simple
User=csserver
Group=csserver
WorkingDirectory=/home/csserver/hlds
ExecStart=/home/csserver/hlds/hlds_run -game cstrike +map de_dust2 +maxplayers 24 -port 27015
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target</pre>
<p>Enable and start it:</p>
<pre>sudo systemctl daemon-reload
sudo systemctl enable --now hlds</pre>
<p>The <code>User=csserver</code> line is the guarantee: systemd drops to that account before executing HLDS. A fuller unit with auto-restart tuning is in <a href="/tutorials/hlds-systemd-service">the HLDS systemd service guide</a>.</p>
<h2>6. Optional hardening</h2>
<p>Because the service is already unprivileged, you can add cheap systemd sandboxing on top — <code>NoNewPrivileges=true</code>, <code>ProtectSystem=strict</code> with a <code>ReadWritePaths=</code> for the game dir, and <code>PrivateTmp=true</code>. These cost nothing and further shrink what a compromised process could reach. Start with just <code>User=</code> though; that alone is the difference that matters.</p>
<h2>Troubleshooting</h2>
<ul>
<li><strong><code>Permission denied</code> writing logs or downloads</strong> — part of the tree is root-owned. Run the <code>chown -R csserver:csserver</code> from step 4.</li>
<li><strong>Service fails with <code>hlds_run: No such file or directory</code></strong> — the 32-bit loader is missing, not a permission issue. See <a href="/tutorials/fix-hlds-no-such-file-32bit">the fix</a>.</li>
<li><strong>Works when you run it manually as root but not under systemd</strong> — that is the point; fix ownership so it runs as <code>csserver</code> rather than reverting to root.</li>
<li><strong>Port bind fails</strong> — not a privilege problem on 27015; another process holds the port. Check with <code>ss -ulpn | grep 27015</code>.</li>
</ul>
<h2>Verification</h2>
<p>With the service running, confirm the process is not root:</p>
<pre>ps -o user= -C hlds_linux
systemctl status hlds</pre>
<p>The owner should read <code>csserver</code>, not <code>root</code>. Connect to the server to confirm it plays normally as an unprivileged process — it will, because nothing HLDS does needs root. If <code>ps</code> shows <code>csserver</code> and players can join, you are running the correct, secure way. Pair this with a <a href="/tutorials/choose-vps-for-cs16">well-chosen VPS</a> and a locked-down <a href="/tutorials/cs16-server-firewall-ports">firewall</a>.</p>