Getting a self-hosted Dune Awakening world booted is only the first hurdle. Run a "Windows-Only" Dedicated Server on Plain Linux covers that part, bypassing the official Windows and Hyper-V requirement by running the real Alpine and k3s payload directly under KVM. This guide picks up from there. Disabling the survival grind. Exposing a genuinely unusual port footprint to the public internet. And two real incidents that took the server offline after it was already working, one operational and one purely self-inflicted.
The whole point of self-hosting rather than playing on a public server is usually to turn off the parts
that force constant rebuilding. All three toggles live in UserGame.ini, deployed into the
running world with battlegroup.sh apply-default-usersettings followed by
battlegroup.sh restart to actually take effect:
[/DeteriorationSystem.ItemDeteriorationConstants]
UpdateRateInSeconds=0 ; item/base deterioration, 0 = off
[/Script/DuneSandbox.SandStormConfig]
m_bCoriolisAutoSpawnEnabled=False ; stops storm damage to bases
[/Script/DuneSandbox.BuildingSettings]
m_bBuildingRestrictionLimitsEnabled=False
GameSettings.ini file exist. Neither is real in this
build. A full search of the shipped config tree turned up nothing for either. The three keys above are the
complete, verified set. When a secondhand summary and the shipped files disagree, trust the files.
Most self-hosted game servers need one UDP port forwarded, and any tunnel provider handles that fine. playit.gg, ngrok, Tailscale Funnel. Dune Awakening does not fit that model. It needs a full range. 54 game-partition UDP ports, plus 54 corresponding inter-gateway UDP ports offset by +111 from each game port, plus one TCP port for its internal RabbitMQ. Single-port tunnel services do not support ranges like that, so this runs through a cheap VPS as a dedicated relay. frps, the frp server, on the VPS. frpc, the client, on the game VM as an OpenRC or systemd service. Both ends token-authed. Expose a Self-Hosted Game Server to the Internet has the general single-port against range-tunnel comparison this is based on.
localIP setting on every UDP proxy was 127.0.0.1, and
the game processes bind to the VM's real LAN IP, not localhost. netstat -ulnp confirmed it
directly:
udp 0 0 192.168.1.55:7777 0.0.0.0:* 30925/DuneSandboxSe
Every UDP proxy's local dial target pointed at an address nothing was listening on. That is a silent
misconfiguration "proxy registered successfully" logs never surface, because registration only proves the
control-plane handshake with the relay worked. Not that the tunnel's local end can reach anything.
The one TCP proxy, RabbitMQ, happened to be configured correctly for how that service binds, which is exactly
why testing it first gave false confidence the whole tunnel was healthy. Fixed by setting
localIP to the VM's real address on every UDP proxy block, config-validated before deploy, and
confirmed only by a successful player connection. Not by re-reading the registration log.
Separately, the tunnel client can keep running while it has silently stopped passing traffic. Process alive,
TCP socket to the relay still ESTABLISHED, nothing in systemctl status suggesting a
problem. The tell was its own log file no longer being written to. A plain service restart clears the symptom
immediately, which made it easy to declare fixed and move on. It recurred a day later, because the restart
cleared the symptom and not the cause.
Here is the actual cause. frp sizes each client's work-connection buffer as the smaller of the client's requested pool count and the server's configured maximum, plus a small constant. Neither side had set this explicitly, so both defaulted to a size far too small for 100+ proxies reconnecting at once. A mass reconnect burst, every proxy re-registering together after any restart, overflows that buffer immediately. frp discards the overflow silently rather than erroring. Both the relay's maximum and the client's requested count need raising. Setting only the server side generously does nothing, since the effective size is always the smaller of the two.
Months after initial setup, a routine check of the Proxmox host's own disk usage found it at 97% full. Not the VM's disk. The hypervisor's own root filesystem. This VM's 80GB virtual disk had been created on the host's local root-directory storage instead of a dedicated storage pool, and nobody had ever moved it. Fixed live, with the VM staying up the whole time:
qm disk move <vmid> scsi0 <dedicated-pool> --delete 1
Took under two minutes for the full disk. The host's root filesystem usage dropped by roughly 60 points immediately after.
qm config <vmid> across your whole fleet, checked against your host's dedicated pools,
costs nothing and catches exactly this before it becomes a full-disk incident.
An earlier attempt at daily auto-updates looked broken in a way that pointed at Steam's anonymous-login path being flaky in a non-interactive cron context. Plausible, since the same command worked fine run by hand. That diagnosis was wrong. The automation's own SSH key had never been added to the VM's authorized-keys file, so every run failed on a plain public-key auth rejection before the update logic was reached. A script that dies on an unhandled auth failure early looks identical, from outside, to one that ran and found nothing to update.
Once the key was authorized, the identical version-check command succeeded instantly and consistently, every
time, with no TTY required. Rebuilding this properly caught a second, independent bug. The game's own update
script exits non-zero even on a fully successful update, thanks to a couple of harmless pre-existing "file
already exists" messages it does not suppress. A naive update && start chain silently skips
starting the world after every real update. Fixed by decoupling the two steps and treating a real pod and
process health check as the success signal, not either command's exit code.