A declarative NixOS flake module for hosting multiple SteamCMD-based game servers with automatic updates, systemd hardening, and unified management.
- Multi-instance support: Run multiple game servers with isolated configurations
- Declarative configuration: Define your entire server infrastructure in Nix
- Automatic updates: Scheduled steamcmd updates with configurable timing
- Systemd hardening: Security-focused service configuration out of the box
- Resource limits: Memory, CPU, and nice value controls per server
- Firewall integration: Automatic port opening for enabled servers
- Presets: Ready-to-use configurations for popular games
- Management CLI:
steamcmd-ctlutility for server operations
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
steamcmd-servers.url = "github:ALH477/steamcmd-servers";
};
outputs = { self, nixpkgs, steamcmd-servers, ... }: {
nixosConfigurations.gameserver = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
steamcmd-servers.nixosModules.default
./configuration.nix
];
};
};
}{ config, lib, pkgs, ... }:
{
services.steamcmd-servers = {
enable = true;
openFirewall = true;
servers.tf2 = {
enable = true;
appId = "232250";
appIdName = "Team Fortress 2";
executable = "srcds_run";
executableArgs = [
"-game tf"
"+maxplayers 24"
"+map cp_badlands"
];
ports.game = 27015;
};
};
}# Rebuild system
sudo nixos-rebuild switch
# Check server status
steamcmd-ctl status tf2
# View logs
steamcmd-ctl logs tf2
# Manual update
steamcmd-ctl update tf2The module includes presets for popular games:
{ config, lib, pkgs, ... }:
let
presets = import ./modules/steamcmd-servers/presets.nix { inherit lib; };
in
{
services.steamcmd-servers.servers = {
# Use preset with custom port
tf2 = lib.recursiveUpdate presets.tf2 {
enable = true;
ports.game = 27015;
};
# Valheim with custom settings
valheim = lib.recursiveUpdate presets.valheim {
enable = true;
executableArgs = [
"-name \"My Server\""
"-port 2456"
"-world MyWorld"
"-password secret"
];
};
};
}| Preset | App ID | Description |
|---|---|---|
cs2 |
730 | Counter-Strike 2 |
tf2 |
232250 | Team Fortress 2 |
gmod |
4020 | Garry's Mod |
rust |
258550 | Rust |
valheim |
896660 | Valheim |
ark |
376030 | ARK: Survival Evolved |
projectZomboid |
380870 | Project Zomboid |
sevenDaysToDie |
294420 | 7 Days to Die |
l4d2 |
222860 | Left 4 Dead 2 |
satisfactory |
1690800 | Satisfactory |
palworld |
2394010 | Palworld |
enshrouded |
2278520 | Enshrouded |
vRising |
1829350 | V Rising |
terraria |
105600 | Terraria |
dstTogether |
343050 | Don't Starve Together |
services.steamcmd-servers = {
enable = true;
# Base directory for all server data
dataDir = "/var/lib/steamcmd-servers";
# User/group for server processes
user = "steamcmd";
group = "steamcmd";
# Automatically configure firewall
openFirewall = true;
# Update schedule
updates = {
automatic = true;
schedule = "04:00"; # Daily at 4 AM
# Or use full OnCalendar format:
# schedule = "Sun *-*-* 04:00:00"; # Sundays at 4 AM
randomDelay = "15min";
};
};servers.myserver = {
enable = true;
# Steam app configuration
appId = "232250";
appIdName = "My Game Server";
beta = null; # Beta branch name
betaPassword = null; # Beta branch password
validate = true; # Validate files on update
# Authentication (most servers work with anonymous)
anonymous = true;
steamUsername = null;
steamPasswordFile = null;
# Executable configuration
executable = "server_binary";
executableArgs = [ "-arg1" "+arg2" ];
# Lifecycle hooks
preStart = "";
postStart = "";
postStop = "";
# Environment variables
environment = {
MY_VAR = "value";
};
# Networking
ports = {
game = 27015;
query = null; # Defaults to game port
rcon = null;
extraPorts = [
{ port = 27016; protocol = "udp"; }
{ port = 8080; protocol = "tcp"; }
];
};
# Resource limits
resources = {
memoryLimit = "4G";
cpuQuota = "200%"; # 200% = 2 CPU cores
nice = 0; # -20 to 19
};
# Service behavior
autoStart = true;
restartOnFailure = true;
restartSec = 10;
# Update behavior
autoUpdate = true;
stopBeforeUpdate = true;
# Extra steamcmd commands
extraSteamcmdCommands = [
"workshop_download_item 440 123456789"
];
};The steamcmd-ctl utility provides convenient server management:
# List all servers
steamcmd-ctl list
# Server status
steamcmd-ctl status # All servers
steamcmd-ctl status tf2 # Specific server
# Start/stop/restart
steamcmd-ctl start tf2
steamcmd-ctl stop tf2
steamcmd-ctl restart tf2
# View logs (follows by default)
steamcmd-ctl logs tf2 # Last 50 lines
steamcmd-ctl logs tf2 100 # Last 100 lines
# Trigger updates
steamcmd-ctl update # All servers
steamcmd-ctl update tf2 # Specific serverEach server creates these systemd units:
steamcmd-server-<name>.service- The game serversteamcmd-update.service- Update job (oneshot)steamcmd-update.timer- Update schedule
# Direct systemd commands also work
sudo systemctl status steamcmd-server-tf2
sudo journalctl -u steamcmd-server-tf2 -fThe module applies these hardening measures by default:
- Dedicated system user/group
NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=true- Restricted write paths
- Kernel tunables/modules protection
- Namespace and realtime restrictions
For optimal game server performance, consider these system-level settings:
{
# Increase file descriptor limits
security.pam.loginLimits = [
{ domain = "steamcmd"; type = "soft"; item = "nofile"; value = "65536"; }
{ domain = "steamcmd"; type = "hard"; item = "nofile"; value = "65536"; }
];
# Network buffer tuning
boot.kernel.sysctl = {
"net.core.rmem_max" = 26214400;
"net.core.wmem_max" = 26214400;
"net.core.rmem_default" = 1048576;
"net.core.wmem_default" = 1048576;
"net.ipv4.udp_mem" = "65536 131072 262144";
};
}Some games require Steam account authentication:
servers.privateGame = {
enable = true;
appId = "123456";
anonymous = false;
steamUsername = "myaccount";
steamPasswordFile = "/run/secrets/steam-password";
};Note: For accounts with Steam Guard, you may need to pre-authenticate once manually.
Download workshop items during installation/updates:
servers.gmod = {
enable = true;
appId = "4020";
extraSteamcmdCommands = [
"workshop_download_item 4020 131759821" # TTT
"workshop_download_item 4020 180713847" # ULX
"workshop_download_item 4020 104482086" # Wiremod
];
};- Check logs:
steamcmd-ctl logs <server> - Verify installation: Check if files exist in data directory
- Manual steamcmd run:
sudo -u steamcmd steamcmd +runscript /etc/steamcmd-servers/<server>.txt
- Check network connectivity
- Verify disk space
- Try manual update with verbose output
Ensure the data directory is owned by the steamcmd user:
sudo chown -R steamcmd:steamcmd /var/lib/steamcmd-serversIssues and PRs welcome. Please test changes with the included NixOS test:
nix flake checkMIT