← 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 at all is only the first hurdle — see Run a "Windows-Only" Dedicated Server on Plain Linux for that part (bypassing the official Windows+Hyper-V requirement by running the real Alpine/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 — one operational, one purely self-inflicted — that took the server offline after it was already working.

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 actual 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 (playit.gg, ngrok, Tailscale Funnel) handles that fine. Dune Awakening doesn't fit that model — it needs a full range: 54 game-partition UDP ports plus 54 corresponding inter-gateway UDP ports (offset +111 from each game port), plus one TCP port for its internal RabbitMQ. Single-port tunnel services don't support ranges like that, so this runs through a cheap VPS as a dedicated relay instead — frps (the frp server) on the VPS, frpc (the client) on the game VM as an OpenRC/systemd service, both ends token-authed. See Expose a Self-Hosted Game Server to the Internet for the general single-port-vs-range-tunnel comparison this is based on.

The tunnel can report 100% success and still pass zero traffic — verify the data path, not just registration. Every one of the 100+ proxies registered successfully with the relay from day one. Nobody could actually connect. The cause: frp's localIP setting on every UDP proxy was set to 127.0.0.1, but 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 — a silent misconfiguration that "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 actually 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 deploying, and confirmed only by an actual 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 — process alive, TCP socket to the relay still ESTABLISHED — while having silently stopped passing any traffic at all, with nothing in systemctl status to suggest a problem. The tell was that its own log file had simply stopped being written to. A plain service restart clears the symptom immediately, which made it easy to declare fixed and move on — until it recurred a day later, because the restart only cleared the symptom, not the cause.

The actual cause: frp sizes each client's work-connection buffer as the smaller of the client's own requested pool count and the server's configured maximum, plus a small constant — neither side had ever set this explicitly, so both defaulted to a size far too small for 100+ proxies reconnecting at once. A mass reconnect burst (all proxies re-registering together after any restart) overflows that buffer immediately, and frp discards the overflow silently rather than erroring. Both the relay's maximum and the client's requested count needed 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. The culprit: 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.

Worth checking on every VM, not just this one: whatever storage a VM's disk landed on at creation time is easy to never revisit once it's running fine — this one sat unnoticed for weeks. A quick qm config <vmid> across your whole fleet, checked against your host's actual 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 exact same command worked fine run by hand. That diagnosis turned out to be wrong. The actual cause: the automation's own SSH key had never actually 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 ever reached — and a script that dies on an unhandled auth failure early can look identical, from the outside, to one that ran and found nothing to update.

Once the key was actually authorized, the identical version-check command succeeded instantly and consistently, every time, no TTY required. A second, independent bug was caught while rebuilding this properly: the game's own update script exits with a non-zero status even on a fully successful update, due to a couple of harmless pre-existing "file already exists" messages it doesn't suppress — a naive update && start chain would silently skip starting the world after every real update. Fixed by decoupling the two steps and treating an actual pod/process health check as the real success signal, not either command's own exit code.

Read the actual failure at the layer it happened, not the layer above it. An SSH-level auth rejection and an application-level flaky-login theory can 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 theorizing about what's above it.