Some Steam games only ship a Windows dedicated server. There is no Linux binary in the depot, so the usual advice is "run it on a Windows box." You do not have to. With Wine you can run the Windows server on a normal Linux machine, and it will connect to Steam and accept real players. This is the exact recipe that worked for us, using Alien Swarm: Reactive Drop (a free Source co-op game) as the example. The same approach should carry to other Windows-only Source dedicated servers.
First, check there really is no Linux server
Do not assume. Ask Steam directly before you reach for Wine. Point steamcmd at the app and read its platform list:
steamcmd +login anonymous +app_info_print <appid> +quit \
| grep -iE "oslist|name"If the dedicated-server app is "oslist windows" only, there is no native Linux build to fall back to. You can also try to force a Linux install; when there is genuinely no Linux depot it fails fast:
steamcmd +@sSteamCmdForcePlatformType linux \
+login anonymous +app_update <appid> validate +quit
# ERROR! Failed to install app '<appid>' (Missing configuration)That error, and no ELF files landing, confirms it. Now Wine is the path. Install the server AND app 1007 (the Steamworks Common Redistributables) into the same folder, with the windows platform type. App 1007 ships the Steam DLLs the server needs, and you will see why in Step 2:
steamcmd +@sSteamCmdForcePlatformType windows +login anonymous \
+force_install_dir /path/to/server \
+app_update <appid> validate \
+app_update 1007 validate \
+quitThe server also needs the Microsoft Visual C++ 2019 runtime inside the Wine prefix. Install it once with winetricks (it needs a display, so wrap it in xvfb-run):
WINEPREFIX=/wine xvfb-run -a winetricks -q vcrun2019
wineserver -wWhy the naive Wine attempt fails
Running the server straight from Wine hits two walls in a row:
- No console: the server expects a real terminal. Piping from /dev/null gives CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents and it never starts.
- Unable to load Steam library: even once it boots, it prints "Unable to load Steam support library" and never registers with Steam, so no one can find or join it.
Both are fixable. Here is the full recipe.
Step 1: give it a real terminal (PTY)
A headless box has no terminal, and the Windows console server refuses to run without one. It needs a pseudo-terminal (PTY) whose input side stays open, which is what docker run -it gives you. The common tip is to wrap the launch in script. On a box with no terminal that is the bug: script's own input hits end of file, the server's console read sees it too, and its main loop stalls. The map may load, but the server never answers on its sockets. Instead, start it from a small Python wrapper that opens a PTY and never closes it, and use Xvfb for a fake display:
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99 WINEPREFIX=/wine
cat > launch_pty.py <<'EOF'
import os, sys
cmd = ["wine", "srcds_console.exe", "-console", "-condebug",
"-game", "<gamedir>", "+map", "<map>", "-port", "27015"]
pid, master = os.forkpty()
if pid == 0:
os.execvp(cmd[0], cmd)
# Parent: never close master, so the server's input stays open.
# Copy the server output to our own stdout.
while True:
try:
data = os.read(master, 4096)
except OSError:
break
if not data:
break
sys.stdout.buffer.write(data)
sys.stdout.flush()
_, status = os.waitpid(pid, 0)
sys.exit(os.waitstatus_to_exitcode(status))
EOF
python3 launch_pty.pyThat alone gets the server to load a map and bind its game port. But it still cannot talk to Steam.
Step 2: two Steam DLLs, two different sources
The Source engine needs BOTH steamclient.dll and steam.dll next to the server exe, and here is the trap that cost us a full extra debugging round: they come from DIFFERENT places, and app 1007 only gives you ONE of them.
- steamclient.dll: comes from app 1007 (Steamworks Common Redistributables), which you installed in Step 1. Canonical, version-matched, Valve-maintained.
- steam.dll: is NOT in app 1007. It is part of the Steam CLIENT, and the only clean way to get it is to run the Windows steamcmd.exe once under Wine, which self-updates and lays down steam.dll (plus steamclient.dll) in its own folder.
So bootstrap steam.dll with steamcmd.exe under Wine, then copy both DLLs next to the server exe:
# steam.dll: run Windows steamcmd once under Wine (needs Xvfb for a display)
cd "$WINEPREFIX/drive_c/steamcmd"
curl -fsSLO https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip
unzip -o steamcmd.zip
DISPLAY=:99 wine steamcmd.exe +quit # lays down steam.dll + steamclient.dll
# place both next to the server exe
cd /path/to/server
cp "$WINEPREFIX/drive_c/steamcmd/steam.dll" .
[ -f steamclient.dll ] || cp "$WINEPREFIX/drive_c/steamcmd/steamclient.dll" .Step 3: why steam.dll is the piece that matters
This is the part almost nobody writes down. The error says "Unable to load Steam support library," and the support library it means is not steamclient.dll, it is steam.dll. The Source engine looks for steam.dll next to its own executable to bootstrap the whole Steam chain. With only steamclient.dll present (which is all app 1007 gives you) it still fails; the moment steam.dll is beside the .exe, the log turns over from failure to success:
Connection to Steam servers successful.
VAC secure mode is activated.VAC will not activate unless the server is a properly logged-in gameserver, so that line is your proof it registered. Most Source servers log in anonymously here and need no Game Server Login Token (GSLT). Only a few games (like CS2) require one.
The trap: A2S goes silent
Here is the gotcha that cost us the most time. With the script wrapper, the server did NOT answer A2S_INFO, the UDP query that tools use to read a server's name, map, and player count. Every query timed out, even from the same machine, so the server looked completely dead. We first blamed Wine. It was the stalled main loop from Step 1: the server never read its sockets at all.
With the PTY held open, the server answers A2S normally and real players can join. The final test is still a client connecting, not a query answering. Once the PTY was right, a real player connected and played:
Client "player" connected (10.0.0.5:27005).
1/ 8 on map <mapname>The practical lesson: if a Wine-hosted server loads its map but A2S is silent, do not blame Wine first. Check how the console is attached. A bound UDP port is not proof the server is alive, because a stalled server keeps its port bound while packets pile up unread.
Does this work for every Windows game?
Be honest about the scope. We proved this on a Source-engine server, and the same recipe (PTY, a steamcmd-built runtime, steam.dll next to the exe) should work for other Windows-only Source dedicated servers. Games on other engines integrate Steam differently, so treat them as separate experiments. One thing to expect either way: boots are slower than native Linux (Wine plus the steamcmd step plus the Steam login).
Or skip all of this
This is the kind of yak-shave that eats an afternoon. If you just want the server, we run the Linux fleet and the Wine plumbing for you, by the hour, with a public IP from the first minute. Alien Swarm: Reactive Drop is not on our public deploy pages yet, but other Windows-only servers are, such as Wreckfest and rFactor 2. See everything we host on 2hours.gg.