← Back to Wiki
Self-Hosting / Game Servers

Self-Hosting Dune Awakening: Exposure, Tuning, and Keeping It Running

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.

Share on X

Turning off decay, sandstorms, and building restrictions

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
Ignore secondhand "AI summary" config advice for this game. A commonly circulated summary claims a separate "Taxation" toggle and a 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.

Exposing it: this game needs a port range, not a port

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.

BE WARNED: the tunnel can report 100% success and pass zero traffic. Verify the data path, not registration. Every one of the 100+ proxies registered successfully with the relay from day one. Nobody could connect. frp's 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.

A silent tunnel hang, and the pool-size bug underneath it

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.

A disk quietly sitting on the wrong storage

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.

Check this on every VM, not just this one. Whatever storage a VM's disk landed on at creation is easy to never revisit once it runs fine. This one sat unnoticed for weeks. A quick 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.

Auto-update automation: the real fix, after an earlier misdiagnosis

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.

BE WARNED: read the failure at the layer it happened, not the layer above it. An SSH-level auth rejection and an application-level flaky-login theory produce the exact same visible symptom, a cron job that "runs" and does nothing. The only way to tell them apart is checking the real error at the connection layer before you theorize about what sits above it.