Loosely Decoupled
GuidePowerShellAutomation

Configure a 7 Days to Die Dedicated Server and Open Its Ports

A 7 Days to Die server that starts on a clean Windows Server, with its saves where you choose and TCP 26900 and UDP 26900 to 26902 open in Windows Firewall.

A pigeon in a hard hat inspects three gates in a fence at night while a queue of shambling zombies waits on the far side. One lantern over an open gate is cyan.
On this page

TL;DR

Install the Visual C++ runtime, set the server’s name, password and port in a copy of serverconfig.xml that you start the server from, and start the server once. The copy matters: a later validate restores the shipped file. Read the ports it listens on, then open exactly those in Windows Firewall. With the default ServerPort of 26900, the server listens on TCP 26900, and on UDP 26900 and 26902, which is ServerPort and ServerPort + 2. The server’s own message about a bad port also names ServerPort + 1, so the rules open UDP 26900 to 26902. The port numbers and the config file are the game-specific parts.

In an elevated PowerShell on the server, once the server runs:

PowerShell
New-NetFirewallRule -DisplayName '7DaysToDie-26900-TCP' -Direction Inbound `
    -Action Allow -Protocol TCP -LocalPort 26900
New-NetFirewallRule -DisplayName '7DaysToDie-26900-UDP' -Direction Inbound `
    -Action Allow -Protocol UDP -LocalPort 26900-26902

Run those once. Step 5 has a version that replaces a rule of the same name, so a rule you customized under that name is overwritten. Windows Firewall blocks inbound traffic until a rule allows it, and a network security group or router in front of the server is a separate firewall that these rules don’t open. If the server runs but players can’t join, these rules are the first check, and the group or router’s port forwarding for the same ports is the second.

The thing most likely to stop you comes before any of that. On a clean Windows Server the server exits within seconds of starting, with [EOS] Native library or one of its dependencies not found in its log, because the Visual C++ runtime isn’t installed. Step 1 installs it.

The first part of this series found the server’s app ID, and the second installed it. The install leaves a server that can’t start on a clean machine, and one that reads a config file with placeholder values. This part fixes both, and ends with the ports a player needs.

1. Install the C++ runtime

The server needs Microsoft’s Visual C++ runtime, and a clean Windows Server doesn’t have it. SteamCMD puts the installer in the server’s folder, under _CommonRedist\vcredist\2022, but nothing ran it here. The install script beside it, installscript.vdf, names a command that runs it, and the registry value that script checks, under HKLM\Software\Valve\Steam\Apps\CommonRedist\vcredist\2022, didn’t exist. The folder comes from a second app that the install fetches, Steamworks Common Redistributables, app 228980.

Run this in an elevated PowerShell on the server. It compares the registered x64 Visual C++ v14 runtime with the installer in the server’s folder. If the registered one is missing or older, it checks that Microsoft Corporation signed the installer and runs it without prompts or a restart. Then it reads the registry again to confirm the runtime is current:

PowerShell
$key = 'HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64'
$installer = 'C:\7DaysToDieServer\_CommonRedist\vcredist\2022\VC_redist.x64.exe'
$have = Get-ItemProperty $key -ErrorAction SilentlyContinue
$registered = if ($have.Installed -eq 1) { [version]$have.Version.TrimStart('v') }
$bundled = [version](Get-Item $installer).VersionInfo.ProductVersion
if ($registered -ge $bundled) {
    'The runtime is already installed.'
}
else {
    $signature = Get-AuthenticodeSignature $installer
    $signer = $signature.SignerCertificate.GetNameInfo('SimpleName', $false)
    if ($signature.Status -ne 'Valid' -or $signer -ne 'Microsoft Corporation') {
        throw "Unexpected signature: $($signature.Status), signed by '$signer'"
    }
    $run = Start-Process $installer -ArgumentList '/install', '/quiet', '/norestart' `
        -Wait -PassThru
    "Installer exit code: $($run.ExitCode)"
    $now = Get-ItemProperty $key -ErrorAction SilentlyContinue
    $current = if ($now.Installed -eq 1) { [version]$now.Version.TrimStart('v') }
    if (-not ($current -ge $bundled)) {
        throw 'The runtime is still missing or older after the installer ran.'
    }
    if ($run.ExitCode -eq 3010) { 'The installer asks for a restart.' }
}

/install /quiet /norestart are options listed on Microsoft’s page on the redistributable packages (opens in a new tab). That page also says a package isn’t installed when a more recent version is on the machine, and returns an error, so the script compares versions first. A Status of Valid only says that some trusted publisher signed the file. That is why the script also checks the name on the certificate. The exit code was 0 here, with no restart pending. With /quiet the installer shows no error message, so the exit code and the second registry read are the only signs that it failed. An exit code of 3010 means a restart is needed.

The installer in the server’s folder is byte for byte the file Microsoft’s permalink serves. That permalink, https://aka.ms/vc14/vc_redist.x64.exe, is the one Microsoft’s list of the latest supported Visual C++ Redistributable downloads (opens in a new tab) gives for the x64 package. Download from it if your server folder has no _CommonRedist.

2. Set the server’s name, password, port and slots

The server reads serverconfig.xml in its install folder. It is a flat list of <property name="..." value="..."/> lines, each with a comment after it, and it ships with placeholder values. Don’t edit that file in place. The previous part showed app_update with validate restoring it to the shipped version, and the server takes any file name through -configfile=. Make a copy under your own name and edit the copy. This creates it beside the original:

PowerShell
Copy-Item C:\7DaysToDieServer\serverconfig.xml C:\7DaysToDieServer\myserver.xml

The settings to change first:

PropertyShipped valueWhat it sets
ServerNameMy Game HostThe name in the server list
ServerPasswordemptyThe password to join. Empty means none
ServerPort26900The game port, from 1024 to 65530. The other ports follow it
ServerMaxPlayerCount8Players at once

This function changes one value and leaves the rest of the file as it was:

Set-ServerProperty.ps1
function Set-ServerProperty {
    param(
        [Parameter(Mandatory)][string]$Path,
        [Parameter(Mandatory)][string]$Name,
        [Parameter(Mandatory)][AllowEmptyString()][string]$Value
    )

    # XML turns a line break inside an attribute into a space.
    if ($Value -match '[\r\n]') { throw "The value can't contain a line break." }

    # A property that is commented out, like UserDataFolder in the shipped
    # file, isn't a node, so a text edit would change the comment and
    # nothing else.
    $original = [IO.File]::ReadAllText($Path)
    if (-not ([xml]$original).SelectSingleNode("//property[@name='$Name']")) {
        throw "No active property named $Name in $Path."
    }

    # Replace the value in the text, so the rest of the file stays as it was.
    $pattern = '(<property\s+name="' + [regex]::Escape($Name) + '"\s+value=")[^"]*(")'
    if ([regex]::Matches($original, $pattern).Count -ne 1) {
        throw "Expected one double-quoted $Name line in $Path. Edit it by hand."
    }
    $escaped = [Security.SecurityElement]::Escape($Value)
    $text = [regex]::Replace($original, $pattern, {
        param($m) $m.Groups[1].Value + $escaped + $m.Groups[2].Value
    })
    $hasBom = [IO.File]::ReadAllBytes($Path)[0] -eq 0xEF
    [IO.File]::WriteAllText($Path, $text, [Text.UTF8Encoding]::new($hasBom))
}

Run it on a stopped server, with your own values:

PowerShell
$config = 'C:\7DaysToDieServer\myserver.xml'
Set-ServerProperty -Path $config -Name ServerName -Value 'Example Server'
Set-ServerProperty -Path $config -Name ServerPassword -Value '<password>'
Set-ServerProperty -Path $config -Name ServerMaxPlayerCount -Value 4

It changes one line per call and keeps the file’s line endings and its comments. -Value '' clears a value, for example to remove the password again. It stops with an error and leaves the file alone when it can’t find exactly one double-quoted line for the name, or when the value holds a line break. Saving the file through PowerShell’s [xml] type is shorter, but it rewrites the layout. The comments that trail each property move to lines of their own, and a line-by-line comparison shows about 260 changed lines for one new value.

3. Set UserDataFolder to choose where the saves go

By default the server writes its world under the profile of the account that runs it: %APPDATA%\7DaysToDie\Saves for the account that ran it here. Started as SYSTEM by a scheduled task, it wrote to C:\Windows\System32\config\systemprofile\AppData\Roaming\7DaysToDie\Saves instead. UserDataFolder sets one place whichever account starts it. The shipped file has it commented out, and so does your copy, which is why Set-ServerProperty can’t change it. Replace the comment with an active property, pointing at the install folder so that Saves sits beside your config file:

PowerShell
$config = 'C:\7DaysToDieServer\myserver.xml'
$find = "//property[@name='UserDataFolder']"
$text = [IO.File]::ReadAllText($config)
if (([xml]$text).SelectSingleNode($find)) {
    throw 'UserDataFolder is already set. Use Set-ServerProperty.'
}
$pattern = '<!--\s*<property name="UserDataFolder"[^>]*>\s*-->'
$active = '<property name="UserDataFolder" value="C:\7DaysToDieServer"/>'
$text = $text -replace $pattern, $active
if (-not ([xml]$text).SelectSingleNode($find)) {
    throw 'The commented UserDataFolder line from the shipped file was not found.'
}
[IO.File]::WriteAllText($config, $text)

This works on a copy of the shipped file. On a file that was edited, it stops with an error and writes nothing, either because UserDataFolder is already active or because the comment no longer matches. The server creates the folders it needs. After a first run it holds Saves, with serveradmin.xml beside the world, plus RfsCache and titlestorage. A new Navezgane world was 17.2 MB. The backup in the published S3 article looks for Saves beside the install’s config, which is where this puts it. That script archives serverconfig.xml, so change its $Config line to myserver.xml, or the archive holds the shipped settings and not yours.

4. Start the server and read its ports

This starts the server detached, with the flags a dedicated server needs, and waits for the game’s own ready message. It writes a log in the install folder and doesn’t change the config:

PowerShell
$dir = 'C:\7DaysToDieServer'
$log = Join-Path $dir 'output_log.txt'
if (Test-Path -LiteralPath $log) { Remove-Item -LiteralPath $log -ErrorAction Stop }
$server = Start-Process (Join-Path $dir '7DaysToDieServer.exe') -WorkingDirectory $dir `
    -PassThru -ArgumentList '-logfile', $log, '-quit', '-batchmode', '-nographics',
    '-configfile=myserver.xml', '-dedicated'
$null = $server.Handle
$ready = {
    (Test-Path $log) -and (Select-String -Path $log -Pattern 'StartGame done' -Quiet)
}
$deadline = (Get-Date).AddMinutes(5)
while (-not $server.HasExited -and -not (& $ready) -and (Get-Date) -lt $deadline) {
    Start-Sleep -Seconds 2
}
if ($server.HasExited) {
    throw "The server exited with code $($server.ExitCode). Read $log."
}
if (-not (& $ready)) { throw "The server wasn't ready after five minutes. Read $log." }

It deletes the old log first, so that a StartGame done from an earlier run can’t pass for this one. It stops if it can’t delete it, for example because an earlier server still has it open. It stops with an error if the server exits or isn’t ready after five minutes. It took about 40 seconds here. Then list every port the process listens on:

PowerShell
$tcp = Get-NetTCPConnection -State Listen -OwningProcess $server.Id |
    ForEach-Object { 'tcp {0}:{1}' -f $_.LocalAddress, $_.LocalPort }
$udp = Get-NetUDPEndpoint -OwningProcess $server.Id |
    ForEach-Object { 'udp {0}:{1}' -f $_.LocalAddress, $_.LocalPort }
$tcp + $udp | Sort-Object

On the default config the output was:

Text
tcp 0.0.0.0:26900
tcp 127.0.0.1:49532
tcp 127.0.0.1:8081
udp :::26902
udp 0.0.0.0:11000
udp 0.0.0.0:26900
udp 0.0.0.0:26902
udp 0.0.0.0:57573

The game’s ports are the ones on ServerPort: TCP 26900, UDP 26900, and UDP 26902. The rest aren’t game ports:

  • Telnet is on 127.0.0.1:8081, reachable only from the server itself.
  • The server’s LAN announcer is on udp 0.0.0.0:11000. Its log line reads [LANMasterServerAnnouncer] listening on 11000 and multicast group 239.192.0.1.
  • The high ports change on every start, and Windows chooses them.

I didn’t connect a player, so this says what the server listens on, not which of those ports a client uses. Nothing listened on UDP 26901 or 26903. The server’s own error message for a port it can’t use lists TCP and UDP on ServerPort, and UDP on ServerPort + 1 and ServerPort + 2, as the ports that must be free. The rules in step 5 open UDP 26900 to 26902 for that reason, though nothing listened on 26901.

Stop the server before you carry on. Its telnet port accepts shutdown from the server itself when no telnet password is set. The server saved its world, logged SaveAndCleanupWorld and exited with code 0 in four seconds:

PowerShell
$telnet = New-Object Net.Sockets.TcpClient('127.0.0.1', 8081)
$stream = $telnet.GetStream()
$bytes = [Text.Encoding]::ASCII.GetBytes("shutdown`r`n")
$stream.Write($bytes, 0, $bytes.Length)
if (-not $server.WaitForExit(90000)) {
    throw 'The server was still running after 90 seconds.'
}

The snippet throws if the server is still running after 90 seconds. It sends no password, so it works only while no telnet password is set. The next part covers stopping it from a script that falls back to other methods.

5. Open the ports in the Windows firewall

Windows Firewall blocks inbound traffic on every profile unless a rule allows it. On this server Get-NetFirewallProfile read NotConfigured for the default, which doesn’t say what applies. The active store does:

Command
PS> Get-NetFirewallProfile -PolicyStore ActiveStore |
>     Select-Object Name, DefaultInboundAction

Output

Name    DefaultInboundAction
----    --------------------
Domain                 Block
Private                Block
Public                 Block

This adds one TCP rule and one UDP rule, and replaces any rule that already has the same name, so a stale rule can’t block players. It changes the firewall of this machine and nothing else:

PowerShell
$rules = @(
    @{ Name = '7DaysToDie-26900-TCP'; Protocol = 'TCP'; Port = '26900' }
    @{ Name = '7DaysToDie-26900-UDP'; Protocol = 'UDP'; Port = '26900-26902' }
)
foreach ($rule in $rules) {
    Get-NetFirewallRule -DisplayName $rule.Name -ErrorAction SilentlyContinue |
        Remove-NetFirewallRule
    New-NetFirewallRule -DisplayName $rule.Name -Direction Inbound -Action Allow `
        -Protocol $rule.Protocol -LocalPort $rule.Port | Out-Null
}

Read them back:

Command
PS> Get-NetFirewallRule -DisplayName '7DaysToDie-*' | ForEach-Object {
>     $port = $_ | Get-NetFirewallPortFilter
>     '{0,-22} {1} {2}  enabled={3} action={4}' -f $_.DisplayName, $port.Protocol,
>         ($port.LocalPort -join ','), $_.Enabled, $_.Action
> }

Output

7DaysToDie-26900-TCP   TCP 26900  enabled=True action=Allow
7DaysToDie-26900-UDP   UDP 26900-26902  enabled=True action=Allow

Each rule applies on every network profile and to every source address, because nothing in the loop narrows either. If you change ServerPort, the ports move with it. With ServerPort set to 26910 the server listened on TCP 26910 and on UDP 26910 and 26912, so the rules need the new numbers.

Ports to leave closed

Don’t open 8081 or 8080 to the internet. Telnet is a way to run server commands. Without a telnet password the server listens for it on 127.0.0.1 only. With TelnetPassword set, it listened on every interface, and enabling the web dashboard with WebDashboardEnabled opened TCP 8080 on every interface too. Both are for the server’s own use, or for a rule limited with -RemoteAddress to your own address.

A firewall rule on the server doesn’t open a cloud network security group or a router. If players can’t connect with the rules above in place, those are the next places to look. I didn’t test inbound connections from outside, so this article stops at the server’s own firewall.

Common failures

Server exits seconds after starting with an EOS error

The server’s log, in the file you passed as -logfile, holds the cause. These are three lines from it, in the order they appear, without their timestamps:

Error

Plugins: Failed to load 'C:/7DaysToDieServer/7DaysToDieServer_Data/Plugins\x86_64/BacktraceCrashpadWindows.dll' because one or more of its dependencies could not be loaded.
ERR [EOS] Native library or one of its dependencies not found (e.g. no Microsoft Visual C Redistributables 2022)
InvalidOperationException: GamePrefs is being accessed before it is ready.

The process exited with code 0 and listened on no port. I reproduced it by uninstalling the runtime, and the server started again once step 1 reinstalled it. vcruntime140.dll and msvcp140.dll were missing from System32. SteamCMD had put the installer in _CommonRedist but not run it. The log also holds ERROR: Shader ... not supported on this GPU lines. A server with no GPU prints them on a healthy start too, so they aren’t the cause. Search the log for EOS.

Server exits seconds after starting with an invalid port

Error

ERR NET: Starting server protocols failed: Invalid ServerPort 65534, must be within 1024 and 65530
ERR Make sure all required ports are unused: 65534/TCP, 65534/UDP, 65535/UDP, 65536/UDP

ServerPort must be from 1024 to 65530. With 65534, and with 1000, the server exited after about ten seconds with code 0, and 65530 started normally. The second line is the server’s own list of the ports it needs free: ServerPort for TCP and UDP, then UDP on the next two.

Set-ServerProperty says No active property named

Error

No active property named UserDataFolder in C:\7DaysToDieServer\myserver.xml.

Either the name is misspelled, or the property is commented out, as UserDataFolder is in the shipped file. Use the replacement in step 3 for a commented-out property. If the line uses single quotes, or the name appears twice, it stops with Expected one double-quoted <name> line and doesn’t write.

Two rules with the same name

New-NetFirewallRule doesn’t refuse a name that exists. Running it twice made two rules named the same, which is why step 5 removes a rule of that name before it adds one. To remove the extra rules and start again:

PowerShell
Get-NetFirewallRule -DisplayName '7DaysToDie-*' | Remove-NetFirewallRule

Tested with

OS
Windows Server 2025 Datacenter (10.0.26100), virtual machine
PowerShell
Windows PowerShell 5.1, elevated
Game
7 Days to Die dedicated server (294420), V 3.2.0 (b10)
Runtime
Microsoft Visual C++ v14 Redistributable (x64)
Date
October 2026

New articles, by email.

A short note when something new is published.