


Lock your Valheim dedicated server to a specific list of mods. Vanilla and wrong-modpack players are automatically kicked.
It works because every player runs a small companion plugin that tells the server exactly which mods they have loaded — signed with a shared password so it can't be faked. The server compares the list to your allowlist and decides whether to let them in.
You'll be installing two files:
Valheim-ServerGuard.dll → goes on your server (you, the host)Valheim-ServerGuard-Client.dll → goes on every player's Valheim install (you and your friends)Players who don't install the client file get kicked when they try to join. That's the whole point.
Valheim-ServerGuard.dll into your server's BepInEx/plugins/ folder.BepInEx/config/ServerGuard/conf/ and auto-generates a password (called sharedSecret) for you.BepInEx/config/ServerGuard/conf/settings.yaml and copy the sharedSecret: line. It looks like:sharedSecret: 'ftnNxBse+Lx2H41ixsTJ637CFffq58C5rrvwwXrabYU='
You'll need to give this exact value to every player.Valheim-ServerGuard-Client.dll into the player's BepInEx/plugins/ folder. With r2modman, this means dragging it into the active profile.BepInEx/config/ServerGuard/client.yaml and paste the password the server gave you:sharedSecret: "ftnNxBse+Lx2H41ixsTJ637CFffq58C5rrvwwXrabYU="
BepInEx/config/ServerGuard/mods_for_allowed_mods.yaml
required_mods:, allowed_mods:, and banned_mods: sections in the server's BepInEx/config/ServerGuard/conf/allowed_mods.yaml.Players join the server normally. Every log line and Discord message identifies the player by character name + Steam ID (the name comes from registrations.yaml; brand-new players show as NewPlayer until they pick a character). The server log (in BepInEx/LogOutput.log) shows lines like:
[Info: Valheim ServerGuard] [ServerGuard] Incoming connection: Modtest (76561198xxxxxxxxx)
[Info: Valheim ServerGuard] [ServerGuard] Modtest (76561198xxxxxxxxx) attested OK (29 mods).
If someone gets kicked, the log says exactly why:
[Warning: Valheim ServerGuard] [ServerGuard] Modtest (76561198xxxxxxxxx) REJECTED: DisallowedMod - Unapproved mod: <name>
That's the whole setup. Everything else has sensible defaults. The rest of this document is only relevant if you want to customize behavior.
Stop reading here unless something doesn't work or you want to change defaults.
1. Player connects.
2. Server sends a one-time random "challenge" to the player's companion plugin.
3. Companion replies with: a list of every mod loaded, the challenge, a timestamp,
and a fingerprint (HMAC) computed using the shared password.
4. Server verifies:
- the fingerprint matches (so the list wasn't tampered with),
- the challenge matches (so it's not a replayed message),
- the timestamp is recent,
- every mod is in the allowlist,
- no banned mods are present,
- all required mods are present.
5. If anything fails, the player is kicked with a clear reason.
6. If the companion plugin doesn't reply within 10 seconds, the player is kicked
for not having the companion plugin installed (vanilla clients fall here).
All on the server, under BepInEx/config/ServerGuard/conf/. Edits are picked up live (no restart needed) for settings.yaml, admins.yaml, and allowed_mods.yaml.
| File | What it does |
|---|---|
settings.yaml |
Main switches and the shared password. |
admins.yaml |
Steam IDs that bypass the mod check entirely. |
allowed_mods.yaml |
The required / allowed / banned mod lists. |
registrations.yaml |
Auto-managed: which character names each Steam ID has used. |
violations.yaml |
Auto-managed: how many times each Steam ID has been rejected. |
metrics.yaml |
Auto-managed: connection / detection counters. |
On every player's PC, only one file matters: BepInEx/config/ServerGuard/client.yaml, which holds the shared password.
| Option | Default | What it does |
|---|---|---|
enforce |
true |
If false, kicks are logged but never executed. Use for dry-run testing. |
violationThreshold |
3 |
After this many rejected connections of the same kind, the player is auto-banned. |
kickMessage |
(built-in) | Message shown when a player is kicked. The specific reason is appended automatically. |
banReason |
(built-in) | Reason recorded when an auto-ban triggers. |
characterLimit |
1 |
Maximum number of distinct character names a single Steam ID can use on this server. |
requireCompanion |
true |
If true, players without the client plugin are kicked on timeout. Set to false to allow vanilla connections. |
companionTimeoutSeconds |
10 |
How long the server waits for the manifest to arrive before declaring no-companion. |
requireHmac |
true |
If true, every manifest must be signed with the shared password. Strongly recommended. |
sharedSecret |
(auto-generated) | The shared password. Auto-created on first launch. Must match client.yaml on every player. |
allowUnlisted |
false |
If false, every mod a player runs must be in allowed_mods or required_mods. If true, unknown mods are tolerated. |
maxClockSkewSeconds |
120 |
Reject manifests whose clock is more than this many seconds off from the server's. |
logPeerManifest |
false |
If true, the server logs the full mod list of every connecting player. Useful for collecting plugin GUIDs to add to the allowlist. Turn off afterward — it's noisy. |
enableMetrics |
true |
Track connection / detection counters in metrics.yaml. |
discordWebhookUrl |
"" |
If set, kicks/bans/violations are also posted to a Discord channel via webhook. |
discordChannelLink |
"" |
Free-form note for your reference. Not used by the plugin. |
Three sections. Each entry is either GUID or GUID|hash.
required_mods:
- com.taeguk.valheim.serverguard.client # the companion itself - leave this in
# - com.azu.anticheat # require AzuAntiCheat too, etc.
allowed_mods:
- com.jotunn.jotunn # match by GUID, any version
- com.jotunn.jotunn|c56246207080b854363396f2b001b389e7d3... # match by GUID, specific DLL hash
- Jotunn # match by display name (less precise)
banned_mods:
- com.example.flycheat # always kicked if present
GUIDs are the strings inside [BepInPlugin("...")] in each mod's source code. They never change between versions, so they're the safest choice. The companion plugin's first-run export gives you GUIDs automatically — just paste them in.
Hash pinning (the |hash part) locks the mod to a specific DLL. Useful if you want to forbid newer or older versions. Dropping the hash means any version with that GUID is accepted.
admins:
- "76561198012345678" # this Steam ID skips the entire mod check
Use sparingly. Admins are not asked for a manifest, so they can run any mods.
To find your Steam ID, paste your profile URL into https://steamid.io and copy the steamID64 number.
sharedSecret: "<paste the value from the server's settings.yaml>"
That's it. The companion reads this once at startup. If you change the password on the server, every player must update their client.yaml.
When you (or a player) installs a new mod and you want to add it to the server's allowlist:
BepInEx/config/ServerGuard/mods_for_allowed_mods.yaml.allowed_mods.yaml.What you'll see in BepInEx/LogOutput.log. Every line that mentions a player shows their character name (from registrations.yaml) followed by their Steam ID. First-time Steam IDs appear as NewPlayer (<steamid>) until they create a character — subsequent connections then use the real name. Multiple characters on the same Steam ID are listed comma-separated.
[ServerGuard] Incoming connection: Modtest (<steamid>)
[ServerGuard] Modtest (<steamid>) attested OK (29 mods).
[ServerGuard] Modtest (<steamid>) REJECTED: DisallowedMod - Unapproved mod: <mod-name>
[ServerGuard] Disconnected Modtest (<steamid>). Reason: ... (Unapproved mod: <mod-name>)
Fix: add the mod to allowed_mods.yaml, or remove it from the player's modpack.
[ServerGuard] NewPlayer (<steamid>) did not deliver a manifest within 10s.
[ServerGuard] Disconnected NewPlayer (<steamid>). Reason: ... (Missing required companion plugin: ServerGuard.Client)
Fix: have the player install Valheim-ServerGuard-Client.dll and set their client.yaml.
[ServerGuard] HMAC mismatch for NewPlayer (<steamid>). Either bad sharedSecret on client, or tampered manifest.
[ServerGuard] Disconnected NewPlayer (<steamid>). Reason: ... (Invalid signature)
Fix: copy the server's sharedSecret into the player's client.yaml exactly. Watch for stray quotes or trailing spaces.
[ServerGuard] Loaded (v1.3.0). Enforcement: ON. RequireCompanion: ON. RequireHmac: ON. AllowUnlisted: OFF. Required: 1, Allowed: 28, Banned: 0. Metrics: ON
[ServerGuard] sharedSecret in use (copy to every client.yaml): <password>
Confirms the plugin loaded and the allowlist parsed. If Allowed: 0 despite a populated file, the YAML keys are wrong — they must be required_mods:, allowed_mods:, banned_mods: exactly.
In settings.yaml:
discordWebhookUrl: 'https://discord.com/api/webhooks/12345/abcdef...'
Get the URL from your Discord server: Channel Settings → Integrations → Webhooks → New Webhook → Copy URL.
You'll get real-time alerts for kicks, bans, and rejected manifests, plus a 2-second-buffered stream of all ServerGuard log events to the same channel.
If a player leaves and you want to lock them out:
settings.yaml and replace the sharedSecret: value with a new random string (or just delete the line and restart — it'll auto-generate a fresh one).client.yaml.The departing player can no longer connect (their old password fails the HMAC check).
After 3 rejected connections of the same kind (configurable via violationThreshold), the player is auto-banned via Valheim's normal ban list. To undo:
unban <steamid>BepInEx/config/ServerGuard/conf/violations.yaml and remove their entry, so the count restarts at zero.If you previously used heuristic detection (ignore_mods.yaml, mod_patterns.yaml):
Valheim-ServerGuard.dll with the new one.ignore_mods.yaml.legacy / mod_patterns.yaml.legacy. A fresh allowed_mods.yaml is created. The new sharedSecret is auto-generated.Valheim-ServerGuard-Client.dll, copy the password into their client.yaml, launch the game once, and use their mods_for_allowed_mods.yaml to populate the server's allowed_mods.yaml.The old YAML files (now .legacy) are kept for reference. You can delete them when you're ready.
See BUILD.md for instructions. Both the server DLL and client DLL build from dotnet build -c Release after pointing VALHEIM_PATH at your Valheim install.
| File | Role |
|---|---|
| Plugin.cs | Server plugin. Patches ZNet.OnNewConnection (challenge issue + manifest receiver registration) and ZNet.RPC_PeerInfo (character-limit enforcement). |
| ServerGuard.Client/ClientPlugin.cs | Client companion. Patches ZNet.OnNewConnection (registers the manifest reply handler). Builds the manifest from Chainloader.PluginInfos and writes mods_for_allowed_mods.yaml on first run. |
| Shared/Manifest.cs | Shared DTO (used by both): the ModManifest shape, the canonical-string format used as input to HMAC, and the HMAC-SHA256 helpers. |
Version: 1.3.0 Repository: https://github.com/yesu0725/Valheim-ServerGuard