← Back to Wiki
Game Servers / Palworld

Self-Hosting a Public Palworld Dedicated Server

A real, always-on Palworld dedicated server. Installed via SteamCMD on plain Linux, exposed to the internet without forwarding a port on your home router or leaking your home IP. Plus two config settings that look like they should work and silently do not.

Share on X

Installing via SteamCMD

Palworld's dedicated server is Steam App ID 2394010, installable anonymously. No Steam account needed for the server itself. On a fresh Ubuntu box, pull in SteamCMD's 32-bit dependencies first:

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y lib32gcc-s1 libsdl2-2.0-0:i386 wget curl

mkdir -p ~/steamcmd && cd ~/steamcmd
wget https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz
tar -xvzf steamcmd_linux.tar.gz
./steamcmd.sh +quit
BE WARNED: argument order matters for anonymous logins. Run +login anonymous before +force_install_dir and it fails outright with ERROR! Failed to install app '2394010' (Missing configuration). A known quirk on some SteamCMD builds. Put +force_install_dir first:
~/steamcmd/steamcmd.sh \
  +force_install_dir /home/<user>/palworld-server \
  +login anonymous \
  +app_update 2394010 validate \
  +quit
A clean run ends with Success! App '2394010' fully installed. That is about 5.1GB.

Configuration without fighting a single-line INI file

Palworld's settings live in one enormous single-line INI, miserable to hand-edit in a terminal editor. sed is faster and less error-prone than opening it at all:

mkdir -p ~/palworld-server/Pal/Saved/Config/LinuxServer
cp DefaultPalWorldSettings.ini Pal/Saved/Config/LinuxServer/PalWorldSettings.ini

sed -i 's/ServerPassword="[^"]*"/ServerPassword="<password>"/' PalWorldSettings.ini
sed -i 's/AdminPassword="[^"]*"/AdminPassword="<admin_password>"/' PalWorldSettings.ini
sed -i 's/ServerName="[^"]*"/ServerName="My Server"/' PalWorldSettings.ini

If you do have to open it by hand, nano -w disables line-wrapping, and Ctrl+W searches within the file. Without those the single giant line is nearly unnavigable.

Firewall and systemd service

sudo ufw allow ssh          # do this FIRST or you'll lock yourself out
sudo ufw allow 8211/udp
sudo ufw allow 8211/tcp
sudo ufw enable
# /etc/systemd/system/palworld.service
[Unit]
Description=Palworld Dedicated Server
After=network.target

[Service]
Type=simple
User=<user>
WorkingDirectory=/home/<user>/palworld-server
ExecStart=/home/<user>/palworld-server/PalServer.sh -log -nosteam -port=8211 -players=32
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now palworld

Exposing it publicly without a port-forward or a leaked home IP

Instead of forwarding 8211/UDP on the home router, the server dials out to a small relay on a cheap VPS running frp, which republishes the port under a real domain. Nobody connecting to the game learns the home IP, and no inbound port opens on the router at all.

BE WARNED: a new game port needs changes in three independent places before it is reachable. Miss any one and it looks identical from the client side.
  1. frpc.toml on the game host. The client-side proxy definition, with localPort matching what the game actually listens on.
  2. frps.toml's allowPorts on the relay VPS. frps silently rejects any proxy for a port not explicitly allow-listed, even though the client's control connection, auth and login all succeed. The failure shows up per-proxy: new proxy [palworld] type [udp] error: acquire port 8211 error: port not allowed.
  3. ufw on the relay VPS. A separate layer again. A proxy showing success in the frps log only proves control-plane registration worked, not that traffic reaches the port from the internet. With no explicit allow rule, the host firewall's default-deny policy drops every packet before frps sees it.
Check all three, in that order, any time a newly tunneled port "registers successfully" and still cannot be reached from outside.
Testing from your own network does not prove the public path works. If your router or DNS setup resolves your domain straight to the LAN IP for local clients, and that is common once you run several tunneled services, connecting from home only proves the server is up. It says nothing about the tunnel. Test from cellular data, a VPN, or ask someone off-network, before you trust a "works" report enough to tear down a fallback tunnel.

The guild-building setting that isn't where you'd look for it

If a guild member reports "no permission to build" on a base someone else in the same guild founded, it is not a permissions or whitelist issue. It is one setting in PalWorldSettings.ini:

bIsMultiplay=True

Left at the default False, only whoever personally founded a base camp can build on or interact with it. Every other guild member, family included, is silently blocked. It has nothing to do with guild roles or in-game permissions. It is one global server setting, easy to miss because the name does not describe what it does.

PvP damage needs three settings, not one

Flipping bIsPvP=True alone enables no player-versus-player combat at all. It is a visibility and matchmaking flag, not a damage switch. Real PvP damage needs a separate master switch, and same-guild friendly fire needs a third:

bIsPvP=True
bEnablePlayerToPlayerDamage=True
bEnableFriendlyFire=True   # only needed if you want damage between guildmates too

All three live in the same INI file. If players report zero damage after you enable PvP, check this first. Not a bug. Three settings doing three separate things that a single "PvP" toggle implies is one.

Back up before every config or version change, not just world saves. Both the guild-permission and PvP fixes above were one-line edits to a live server's PalWorldSettings.ini. Cheap to get wrong, and just as cheap to keep a timestamped backup of the file alongside the change, so a bad edit is a file restore instead of a guessing game. Same before you run a SteamCMD update. This wiki's guide on backup-gated auto-updates for a self-hosted game server fleet has a reusable pattern that checks for a real version change before it touches a running server.