9.5 KiB
Headless Server Setup for Game Streaming
Full pipeline for running Sunshine + games on a headless Linux server (no monitor attached).
Required components
-
HDMI dummy plug ($5-10) — plugs into GPU HDMI port, emulates a 4K display. Without it, NVIDIA GPUs run in low-power mode with reduced clocks and may refuse to render at full speed. Essential for headless setups.
EDID reality check: Many "4K/120" dummy plugs (e.g., FUEARAN) advertise 4K@120Hz on the label but their EDID caps 4K at 60Hz. Actual high-refresh options are 1440p@120Hz and 1080p@120Hz. Check with:
DISPLAY=:0 xrandr --query | grep -A 20 "^HDMI-0" | grep -E "^[ ]+[0-9]"If 4K@120Hz is absent despite the label, the plug hardware doesn't support it — plan your game resolutions accordingly.
-
Xorg — needs to run headless pointing at the dummy display, and persist across reboots.
Verify the dummy display:
for conn in /sys/class/drm/card0-*/status; do echo "$conn: $(cat $conn)"; done # Look for "connected" — that's your dummy plug cat /sys/class/drm/card0-HDMI-A-1/modes | head -1 # should show 3840x2160 or similarStart Xorg (manual test):
sudo Xorg :0 -ac -nolisten tcp vt7 & sleep 2 DISPLAY=:0 xdpyinfo | grep dimensions # verifyXorg persistence (systemd system service): Xorg needs root for VT access on headless servers. Create a system service so it survives reboots:
# /etc/systemd/system/xorg-headless.service [Unit] Description=Xorg display server for headless game streaming After=multi-user.target [Service] Type=simple User=root ExecStart=/usr/bin/Xorg :0 -ac -nolisten tcp vt7 Restart=on-failure RestartSec=3 [Install] WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload && sudo systemctl enable --now xorg-headless -
Sunshine — game streaming server. See main SKILL.md for install.
-
Game launchers:
-
Steam (+ Proton): Requires 32-bit architecture on Ubuntu 26.04+.
sudo dpkg --add-architecture i386 sudo apt update sudo apt install -y steam-installerHeadless blocker — zenity license dialog: The
steam-installerpackage installs a/usr/games/steamwrapper script that, on first run, pops up a zenity license dialog (--ok-label=Install --cancel-label=Cancel). On a headless display, nobody can click "Install", so the Steam runtime never downloads. Do not attempt debconf preseeding — the zenity check (lines 113–139 of/usr/games/steam) is hardcoded and ignores debconf.Workaround — bootstrap Steam manually: Run the
scripts/setup-headless-steam.shscript included in this skill. It automatically detects the required version, downloads the bootstrap tarball, unpacks the embedded runtime, and links the directories so the GUI license dialog is bypassed.After these steps,
DISPLAY=:0 steamlaunches without any dialog. First run downloads the full Steam client (~200-500 MB) and displays a self-updating progress bar (zenity--progress --auto-close). The client goes through a multi-stage bootstrap: update download → restart → runtime unpack → restart → login screen. This takes 3-5 minutes on a fast connection. Steam must be running and logged in for Sunshine's "Steam Big Picture" app to work.Proton is built into Steam — enable in Settings → Steam Play. Handles ~85% of Windows games.
-
Lutris:
sudo apt install lutris. For non-Steam games: GOG, Epic Games Store, Battle.net, emulators.
-
Pipeline
Headless server (no monitor)
↳ HDMI dummy plug → GPU sees "4K display" → full clocks enabled
↳ Xorg virtual desktop on dummy display
↳ Game launches (Steam/Lutris)
↳ Sunshine captures X11 display via NVENC
↳ Moonlight on client device streams it
GPU requirements
- NVIDIA GPU with NVENC (Turing or newer: GTX 1650 Super+, RTX 20-series+, RTX 30-series+, RTX 40-series+)
- Consumer cards limited to 3 concurrent NVENC sessions
- NVENC is dedicated silicon — negligible FPS hit during encoding
Linux game compatibility
| Type | How | Compatibility |
|---|---|---|
| Native Linux games | Steam → Install → Play | 100% |
| Windows games (Proton) | Steam → Enable Steam Play → Install | ~85% works out of box, ~10% needs tweaks, ~5% broken |
| Anti-cheat games | Valorant, CoD, Fortnite, Destiny 2 | 0% — kernel-level anti-cheat incompatible with Proton |
| Non-Steam games | Lutris (GOG, Epic, Battle.net) | Varies, mostly good |
ProtonDB (protondb.com) is the definitive compatibility reference for any specific game.
Audio setup (headless)
Headless servers have no physical speakers and often no audio system at all. Sunshine needs an audio sink to capture game audio and stream it to Moonlight.
Install PipeWire
sudo apt install -y pipewire pipewire-pulse wireplumber pulseaudio-utils
This gives you a PulseAudio-compatible interface over PipeWire 1.6+. The auto_null (Dummy Output) sink is created automatically — it accepts audio from games and makes it available for Sunshine capture without needing physical hardware.
User must be in audio group
sudo usermod -a -G audio $USER
# Requires re-login or systemctl --user daemon-reexec to take effect
Without the audio group, /dev/snd/* devices are permission-denied and PipeWire can't enumerate ALSA hardware. The auto_null sink still works for capture, but NVIDIA HDMI audio (from the dummy plug's EDID) won't appear as a proper hardware sink until the group is active.
Sunshine config for headless dummy plug
Required config — tell Sunshine which display to capture:
Without output_name, Sunshine defaults to display index 0 (DP-0), which may be disconnected even with a dummy plug in HDMI-0. The log shows:
Configuring selected display (0) to stream
Couldn't get requested display info, defaulting to recording entire virtual desktop
Fix: add to ~/.config/sunshine/sunshine.conf:
output_name = 2
⚠️ IMPORTANT: output_name takes a numeric display index, NOT the connector name string. Passing HDMI-0 as the value causes Sunshine to fail with Could not stream display number, there are only [8] displays.
Determine the correct index from Sunshine's own log output:
grep "Detected display" ~/.config/sunshine/sunshine.log
# Detected display: DP-0 (id: 0)DP-0 connected: false
# Detected display: DP-1 (id: 1)DP-1 connected: false
# Detected display: HDMI-0 (id: 2)HDMI-0 connected: true ← use output_name = 2
# Detected display: DP-2 (id: 3)DP-2 connected: false
The (id: N) value is the zero-based display index. Only one display should be connected: true on a headless system with a single dummy plug. After setting, verify Sunshine captures the correct display:
Streaming display: HDMI-0 with res 3840x2160 offset by 0x0
This log line confirms Sunshine is streaming the right display. Required alongside capture = x11 for NVIDIA headless setups.
Do NOT set audio sink config:
audio_sink or virtual_sink in sunshine.conf causes an immediate fatal crash (pa_simple_new() failed: Invalid argument) on Sunshine v2026+. Let Sunshine auto-detect PipeWire instead.
CRITICAL FIX FOR CRASHING STREAMS: If Sunshine still crashes with Invalid argument upon stream start, PipeWire is rejecting Sunshine's attempt to dynamically create virtual surround sinks. You must pre-create them so Sunshine finds them instead of crashing:
pactl load-module module-null-sink sink_name=sink-sunshine-stereo
pactl load-module module-null-sink sink_name=sink-sunshine-surround51 channels=6 channel_map=front-left,front-right,front-center,lfe,rear-left,rear-right
pactl load-module module-null-sink sink_name=sink-sunshine-surround71 channels=8 channel_map=front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right
(Run these commands before Sunshine starts, e.g. in a wrapper script or systemd ExecStartPre).
Verify audio
pactl info | grep "Default Sink" # should show auto_null
pactl list short sinks # should show auto_null, SUSPENDED
journalctl --user -u sunshine | grep -i audio # should have no warnings/errors
The earlier "Warning: There will be no audio" in sunshine logs should be gone after this config.
Post-reboot: NVIDIA HDMI audio
After a reboot (once the audio group takes effect), WirePlumber discovers the NVIDIA HDMI audio device exposed by the dummy plug. An additional sink like alsa_output.pci-0000_01_00.1.hdmi-stereo appears. Either sink works — games output to the default sink and Sunshine captures whichever one is configured. The real HDMI sink has the advantage of proper EDID-negotiated sample rates (up to 96kHz on the FUERAN dummy plug).
VRAM cohabitation strategy
When the server also runs LLM inference:
| Scenario | Solution |
|---|---|
| Model always loaded, game occasionally | Socket-activated llama-server (see llama-cpp skill, deployment-patterns.md). Model unloads when idle, 3-4s warm restart. |
| Gaming always, model occasionally | Stop llama-server before gaming. Start manually when needed. |
| Both simultaneously | Only works if VRAM fits both — e.g., 24 GB card with a 7B model (~4.7 GB) leaves ~19 GB for games. |
Testing before buying hardware
- Verify
nvidia-smishows NVENC support (nvidia-smi -q | grep -A5 Encoder) - Run Sunshine and confirm web UI at
https://localhost:47990 - Install Moonlight on any device and verify auto-discovery works