The Valheim world and mod manager that keeps everyone in sync.
Modding Valheim is easy. Keeping mods perfectly in sync across your server and every player? Nearly impossible. Mod managers like Thunderstore work great individually, but they don't coordinate across a group — eventually someone's client drifts out of sync and the session breaks.
PhValheim is a two-part system (server + client) that locks server and client mod configurations together. Deploy worlds with any combination of Thunderstore and Hexium mods, and every player automatically gets the exact same files when they connect. No more "which version do you have?" conversations.
Not every world needs mods, though. PhValheim also hosts vanilla worlds — stock Valheim, zero mods, joined with the ordinary Valheim client — so you can run plain servers alongside your modded ones without managing a second stack.
| One-Click Worlds | Deploy unique Valheim worlds with any combination of Thunderstore and Hexium mods at the click of a button. |
| Vanilla Servers | Run stock, zero-mod worlds alongside your modded ones. Password protected and optionally listed in the public Valheim server browser. Players join with the ordinary Valheim client — no PhValheim client needed. |
| Crossplay | Let Xbox / Microsoft Store players join. Available on every world, modded or unmodded. |
| Automatic Mod Sync | Server and client mods stay in lock-step. Players always have the right files. |
| Setup Wizard | Guided first-run configuration — just start the container and follow the steps. No environment variables required. |
| Steam Authentication | Players log in with their Steam account. Per-world access control lists manage who can see and join each world. |
| Citizens, Admins & Banned | Per-world allow list, admin list granting in-game admin commands, and ban list — all rendered from the database at every world start. |
| Custom Launch Parameters | Append your own arguments to any world's Valheim server command line. |
| Multi-Catalogue Mods | Thunderstore and Hexium, usable together in one world. Search across both, see which catalogue each mod came from, pin any previous version, and get dependency resolution across sources. |
| Backup System | Activity-aware scheduled backups with compression (gzip/zstd), tiered retention, one-click restore, and per-world overrides. Supports separate backup volumes. |
| Live Monitoring | Real-time CPU, memory, and load metrics for every running world, visible in both the admin and public UIs. |
| Hugin, the AI Helper | Bring your own model — any OpenAI-compatible endpoint, Anthropic, Gemini, or a self-hosted box. Hugin reads your logs and settings to diagnose mod errors, missing dependencies and server health, and can carry out changes — start or back up a world, edit settings, access lists, backup schedules or mods — showing you exactly what will change before anything happens. |
| Health Scan | Runs the moment you open the AI panel and needs no AI provider at all. Flags mod load failures, missing dependencies, permission errors, port conflicts, restart loops, overdue backups, low disk and more — each with the log lines that triggered it. |
| Custom Configs | Push custom configuration files to clients, or keep server-only configs that persist across updates. |
| Single Container | Everything runs in one Docker container — NGINX, PHP, MariaDB, Supervisor, and the PhValheim engine. |
services:
phvalheim:
image: theoriginalbrian/phvalheim-server:latest
container_name: phvalheim
ports:
- "8080:8080/tcp" # Public UI
- "8081:8081/tcp" # Admin UI (do NOT expose publicly)
- "25000-26000:25000-26000/udp" # Game ports
volumes:
- /path/to/data:/opt/stateful:Z
- /path/to/backups:/opt/stateful/backups:Z # ideally a separate disk
restart: unless-stoppedThat's it. Start the container and open http://your-host:8081 — the Setup Wizard will walk you through configuration.
docker create \
--name phvalheim \
-p 8080:8080/tcp \
-p 8081:8081/tcp \
-p 25000-26000:25000-26000/udp \
-v /path/to/data:/opt/stateful:Z \
-v /path/to/backups:/opt/stateful/backups:Z \
theoriginalbrian/phvalheim-server:latest
docker start phvalheim Search for PhValheim in the Community Apps store.
A Helm chart is included in the repo at helm/phvalheim/.
# Minimal install
helm install phvalheim ./helm/phvalheim/
# With Ingress for the public UI
helm install phvalheim ./helm/phvalheim/ \
--set ingress.public.enabled=true \
--set ingress.public.hosts[0].host=phvalheim.example.com \
--set ingress.public.hosts[0].paths[0].path=/ \
--set ingress.public.hosts[0].paths[0].pathType=Prefix
# With an existing PVC
helm install phvalheim ./helm/phvalheim/ \
--set persistence.data.existingClaim=my-phvalheim-pvcHow it works:
-
The pod runs with
hostNetwork: trueby default so Valheim's UDP game ports (25000-26000) bind directly to the node — no NodePort or LoadBalancer gymnastics required. -
Two ClusterIP Services are created: one for the public UI (8080) and one for the admin UI (8081). Each has an optional Ingress resource (disabled by default).
-
A 20Gi PersistentVolumeClaim is created for
/opt/stateful. An optional separate PVC for backups can be enabled withpersistence.backups.enabled=true. -
Without Ingress enabled, access the admin UI via port-forward to run the Setup Wizard:
kubectl port-forward svc/phvalheim-admin 8081:8081 # Then open http://localhost:8081
See helm/phvalheim/values.yaml for the full set of configurable values.
All settings are configured through the Admin UI after first launch. No environment variables are needed for new installations.
Upgrading from an older version? Your existing environment variables will be automatically migrated to the database on first boot. A one-time migration notice will confirm the imported values.
| Setting | Description |
|---|---|
| Steam API Key | Required. Used for player authentication. Get one here. |
| PhValheim Host | Public FQDN for the web UI. |
| Game DNS | DNS name players use to connect to game servers. Can be the same as PhValheim Host. |
| Base Port | First UDP port for worlds (must match the container's port range). |
| Backups to Keep | Number of backup snapshots to retain per world. |
| Client Download URL | URL for the PhValheim Client installer. |
PhValheim includes a full backup system with activity-aware scheduling, compression, tiered retention, and one-click restore.
Mount a separate volume for backups to keep them isolated from game data:
volumes:
- /path/to/data:/opt/stateful:Z
- /path/to/backups:/opt/stateful/backups:Z # separate disk recommendedIf no dedicated backup volume is detected, the admin UI will display a warning and automatic backups are disabled. Manual backups can still be created.
| Setting | Default | Description |
|---|---|---|
| Backup Interval | 30 min |
How often scheduled backups run. The cron runs every 10 minutes but self-gates based on this interval. |
| Require Player Activity | Yes |
Only create backups when players have connected since the last backup. Prevents redundant backups of idle worlds. |
| Setting | Default | Description |
|---|---|---|
| Compression | None |
Algorithm: none (uncompressed tar), gzip, or zstd. Zstd is faster with better compression ratios. |
| Compression Schedule | 3:00 AM |
Hour to run deferred compression. Set to Immediate to compress at backup time. Deferred mode reduces CPU impact during active hours. |
| Compression Level | 0 (default) |
Higher levels = smaller files but more CPU. 0 uses each algorithm's default level. |
Disk space note: Compression requires temporary space for both the uncompressed tar and the compressed output (~2x world size). If insufficient space is available, the backup is saved uncompressed with a warning.
Backups are pruned automatically using a tiered retention policy. Manual backups are never auto-pruned.
| Tier | Default | Description |
|---|---|---|
| Keep All | 24 hours |
Every backup within this window is kept. |
| Daily | 7 days |
After the keep-all window, one backup per day is retained. |
| Weekly | 30 days |
After the daily tier, one backup per week is retained. |
| Monthly | 6 months |
After the weekly tier, one backup per month is retained. |
These settings control how aggressively backup operations use system resources. Lower priority = less impact on active players.
| Setting | Default | Description |
|---|---|---|
| CPU Priority | Low (10) |
nice value for tar and compression. Normal (0) = full speed, Low (10) = reduced, Lowest (19) = minimal. |
| I/O Priority | Low |
ionice class. Idle = backups only use disk when the game server isn't reading/writing. Normal = no throttling. |
Each world can override the global backup settings. In the world settings modal, switch to the Backups tab and uncheck Use Global Defaults to configure per-world intervals, retention, compression, and performance settings.
Restoring a backup replaces the current world directory with the backup contents:
- A pre-restore safety backup is created automatically (unless disk space is insufficient).
- The current world directory is cleared.
- The backup is extracted. Legacy backups (pre-2.38) are detected and extracted to the correct path.
- File ownership is fixed and the world is set to rebuild on next engine cycle.
- The world process is restarted.
Restores are blocked while a world is in a transitional state (starting, stopping, updating, etc.).
On startup, PhValheim reconciles backup records with files on disk:
- Orphaned records: DB entries pointing to missing files are flagged as orphaned.
- Untracked files: Backup files on disk with no DB record are discovered and imported.
- Recovery: If an orphaned file reappears (e.g., backup volume remounted), the orphan flag is cleared.
Orphaned records are shown in the dashboard Storage card and in the per-world backup table. Use the Clean up button to purge orphaned records.
PhValheim reads mods from Thunderstore and Hexium. Both are enabled by default and a world can use either or both; search results show a coloured pill for each mod's origin (Thunderstore blue, Hexium purple).
| Setting | Default | Description |
|---|---|---|
| Thunderstore / Hexium | Enabled | Whether that catalogue is synced and offered in the mod picker. |
| Thunderstore API Key / Hexium API Key | (empty) | Not required. Both catalogues are public and unauthenticated. Supply a key only if a source starts demanding one; it is sent as a bearer token. |
| Catalogue Sync Interval | 6 hours |
How often the catalogues are checked for changes. Does not apply to the start-up sync, which always runs. |
Disabling a catalogue hides its mods from the picker but does not remove mods a world has already selected.
Version pinning. Every published version of every mod is stored, not just the newest.
Select a mod and pick a version from the dropdown in its row to freeze it there; Latest (auto) keeps following new releases. A pinned version is kept in the database even if the
source later delists it, so a pinned world does not silently move.
Both catalogues are checked every time the server starts, and on the configured interval after that. So a container you have just restarted comes back with a current mod list rather than whatever the last scheduled check left behind — the start-up sync ignores the interval deliberately, and costs about a second when nothing has changed.
It runs in the background, so a slow or unreachable catalogue never delays your worlds starting.
There is nothing to tune. The sync is designed so that the common case does almost no work:
- Thunderstore sends a
Last-Modifiedheader, so PhValheim makes a conditional request and normally gets a bodiless304 Not Modifiedback in about a tenth of a second. - Hexium sends no validator, so its (much smaller) response body is hashed and compared against the previous run.
- Every stored row carries a content hash, so even when a catalogue has changed only the rows that genuinely differ are rewritten.
Measured on a normal host, against the live catalogues:
| Time | |
|---|---|
| Routine check, nothing changed (both catalogues) | ~1–2 seconds |
| Full cold build from empty — 11,600+ mods, 91,000+ versions, all history | ~30 seconds |
The Sync & Maintenance card shows a live panel per catalogue: current phase, packages and
versions seen, how many mods and versions were added, changed or removed, how long it took,
and how that compares with the previous run. Progress is in
/opt/stateful/logs/modSync.log.
Each catalogue's panel has its own live sync log — expand it to watch a sync happen: the endpoint used, how change detection decided to fetch or skip, what was added, updated or delisted by name, which dependencies could not be resolved, and a per-phase timing breakdown. A per-mod detail toggle hides the individual mod lines when you only want the summary.
To force a sync immediately, use the per-catalogue sync link in the Sync & Maintenance
panel. A forced sync ignores change detection and rebuilds the dependency graph, so it takes
considerably longer than a routine check.
Upgrading from 2.42 or earlier: existing mod selections are migrated automatically on first start and the old Thunderstore tables are left untouched, so nothing is discarded. The
Thunderstore Local SyncandThunderstore Chunk Sizesettings have been removed — they configured the old parallel-worker sync, which no longer exists. The sidebar's Thunderstore Sync button is gone too; syncing is automatic, and the Sync & Maintenance panel has a per-cataloguesynclink if you want to force one.
Add one or more providers under Server Settings → AI Setup. A five-step wizard tests the endpoint, the credential and the model separately, so a failure tells you which of the three is wrong instead of surfacing later as a chat error.
| Provider type | What you need |
|---|---|
| OpenAI-compatible | Endpoint URL + API key. One-click presets for OpenAI, Ollama, vLLM, LM Studio, llama.cpp, OpenRouter, Groq, Together, DeepSeek, Mistral and xAI — or type any endpoint of your own. |
| Anthropic | API key |
| Google Gemini | API key |
PhValheim has no built-in list of models. Every provider publishes its own catalogue over HTTP, so the model dropdown is fetched live from your endpoint and cached briefly. When a vendor retires a model it simply stops appearing, and a model we have never heard of is sent exactly as you entered it — never silently substituted. Configure as many providers as you like, including several of the same type, and switch between them from the panel header.
Hugin reads logs, world settings, the resolved mod list, catalogue sync state, backups and host health on its own, and shows which of them it consulted in every reply. It can also act: start, stop, restart, update or delete a world, take or restore a backup, and change world options, access lists, backup policy, mods or server settings.
Nothing that matters happens without your say-so. Anything that stops a service, changes configuration or destroys data is shown first as a card listing every change, old value to new, with Apply and Dismiss. That card is built on the server from the validated change — not from Hugin's description of it — works once, expires after fifteen minutes, and is re-checked at the moment you click. Deleting a world and restoring a backup additionally require you to type the world's name.
Hugin refuses changes that would quietly break something: an access list that would be enforced but empty (which opens a world to everyone), listing a vanilla world with no password (Valheim will not start), and crossplay or a password on a modded world (where they do nothing). The "What can Hugin do for me?" button lists every capability and every refusal, generated from the live capability list, and works with no provider configured.
If your model cannot call tools — some smaller self-hosted models cannot — Hugin answers anyway and tells you it could not inspect anything and cannot make changes.
| Container Path | Purpose |
|---|---|
/opt/stateful |
All persistent data — worlds, mods, database, configuration. |
/opt/stateful/backups |
World backups. Point this to a separate disk for safety. |
| Port | Purpose |
|---|---|
8080/tcp |
Public web UI — expose this to your players. |
8081/tcp |
Admin web UI — keep this private. |
25000-26000/udp |
Game server port range for Valheim worlds. |
A world can be created as vanilla: stock Valheim with zero mods and no BepInEx. Players join with the ordinary Valheim client, so no PhValheim client install is required.
Tick Vanilla world (no mods) when creating a world, or flip it later in the world's Settings modal (the world needs an update/restart to apply).
| Option | Notes |
|---|---|
| Server Password | Minimum 5 characters, and it cannot appear inside the world name — Valheim refuses to start otherwise. |
| Crossplay | Lets Xbox / Microsoft Store players join. Not vanilla-only — this setting is available on every world from its Settings modal. Note that players on those platforms cannot install mods, so a heavily modded world may not be joinable for them. |
| List in server browser | Publishes the world to the public Valheim community server list. Valheim requires a password for this. |
| Show password on public UI | On by default. Turn it off and the password row is removed from the world card entirely, for worlds whose password you share another way. |
Players see a dedicated card on the public UI with the server address, the password (show to reveal, copy to put it on the clipboard), and a Join button. They can also connect from Valheim's own Join IP screen using the address shown.
Note: Valheim has no way to pre-fill a server password from a launch argument, so players type it at the prompt — which is why the card shows it.
Because these worlds run no mods, a few PhValheim features that depend on the companion mod do not apply to them: boss progression ("hung heads"), player join/leave events, and tick-health metrics — so the HEALTH bar is hidden for them in the admin UI.
Seeds. Valheim's dedicated server has no seed argument — the seed is fixed when the world is first generated. Choosing one needs the CustomSeed mod, which a vanilla world does not run, so the seed control is hidden when creating one. PhValheim reads the seed Valheim actually generated out of the world's .fwl after first start and displays that; until then the card shows generated on first start.
If you want a specific seed on a vanilla world, generate the world in the Valheim client (where you can type a seed) and use Import World — no mod required.
A vanilla world cannot have mods: Edit Mods is disabled for it, and turning the vanilla switch on clears any existing mod selection. Turn the switch back off to make it a modded world again, then use Edit Mods and run an Update.
Modded worlds are unaffected by any of this — they continue to be gated by the Citizens list.
Each world has three per-world player lists, all in the world's Settings modal:
- Citizens — who may join (
permittedlist.txt). Setting a world Public here removes the restriction entirely and lets anyone join. - Admins — who gets in-game admin commands (
adminlist.txt). - Banned — who is blocked from the world (
bannedlist.txt). A ban applies even when the world is public.
Have them join any public world and press F2. The panel shows their Platform User ID — note it down and paste it in. This is the only method that works for every player: Xbox, PlayStation, Nintendo and GameCenter players have no SteamID64 at all.
A plain SteamID64 (17 digits) also works for Steam players. PhValheim stores what you type and converts it on write.
For Steam players you can also use Look Up SteamID in the Citizens editor — enter a Steam username and it returns the ready-to-paste V_… form. (Requires a Steam API key in Server Settings.)
Why the conversion: since Valheim 1.0, a bare SteamID64 in these files does not match.
ZNet.ListContainsId()finishes by looking up the display-prefix form of the ID (Steam→V) and assigns that result over the earlier checks rather than OR-ing it, so onlyV_<steamid64>can match. PhValheim writes that form for you. This is a bug in Valheim — it is also why Valheim's ownbanconsole command writes an entry its own matcher cannot match, and why older "put your SteamID64 in permittedlist.txt" guides no longer work.
Changes take effect without a restart — Valheim re-reads all three files while running. The one exception is admin status, which a connected client caches until it reconnects.
The database is the source of truth. All three files are regenerated from it every time a world starts, so a world that was restored from a backup or rebuilt converges back to what the admin UI shows instead of quietly keeping an older list.
Upgrading from 2.39 or earlier: your stored IDs are converted to the V_ form automatically on first start, and the Access tab explains it once. Entries that were already prefixed, and console IDs, are left alone; anything unrecognised is kept exactly as you left it rather than dropped.
Note: Use Access List controls the Citizens gate only. It does not publish the world to the Valheim server browser — that is the separate List in server browser option on vanilla worlds.
Use Access List sits at the top of the Access tab, because it decides whether the Citizens list is consulted at all. On, only the players on the list may join. Off, anyone may join, so the editor is hidden — the list is kept, not cleared, and comes back when you switch it on again.
Each world's Settings modal has a Custom Launch Parameters field, appended to the Valheim server command line after everything PhValheim generates, so it can override the defaults.
-saveinterval 900 -instanceid myserver
Shell metacharacters are rejected. Invalid Valheim arguments will stop the world from booting, with the reason only visible in the world log — change these one at a time.
The server is only half the equation. PhValheim Client is a cross-platform companion app (Windows, Linux, macOS) that registers a custom phvalheim:// URL protocol. When a player clicks a launch link:
- The client compares the remote world's checksum against the local copy.
- If outdated, it downloads the new payload (mods, configs, dependencies).
- It launches Valheim, connecting to the correct server and world automatically.
Vanilla worlds skip steps 1 and 2 — there is no payload — and are joined directly, so players do not need the client for them at all.
| Platform | Installer | Config location |
|---|---|---|
| Windows | .msi |
%appdata%\PhValheim |
| Linux | .deb, .rpm, or .tar.gz |
~/.config/PhValheim |
| macOS | macinstall.sh (universal — Intel + Apple Silicon) |
~/Library/Application Support/PhValheim |
Client repo: brianmiller/phvalheim-client
Access to each world is controlled by the PhValheim database. Steam IDs are associated with each world, gating both the web UI (world visibility) and the game server allow-list.
Example NGINX config for TLS termination:
server {
listen 80;
server_name phvalheim.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name phvalheim.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1200s;
client_max_body_size 0;
}
}| Folder | Behavior |
|---|---|
custom_configs/ |
Pushed to clients on world update. Use for shared game configs. |
custom_configs_secure/ |
Server-only. Persists across updates but never sent to clients. |
PhValheim makes mod management effortless, but not all mods play well together. If you're experiencing crashes or unexpected behavior, deploy a vanilla world (no mods) first to rule out mod conflicts. Most mod issues occur after major Valheim updates.



