
# valheim-proxmox
**Valheim dedicated server in its own LXC, with a web panel to run it.**
**Players get a launcher for Windows, macOS and Linux — with the server's mods working on all three.**
[Polski →](README.pl.md) · **[Update from an older version](#already-running-it-update-once--then-it-updates-itself)** · [Screenshots](#what-it-looks-like) · [Panel](#the-panel) · [Mods](#mods-from-a-share-code)
[](https://buymeacoffee.com/cygan)
---
## Already running it? Update once — then it updates itself
> [!IMPORTANT]
> **Since v1.20.0 the panel updates itself.** A banner shows when a new version is out, and it
> installs on its own while nobody is playing — world backed up first, rolled back if the new
> version does not start. You can switch it off in Settings.
>
> **Installed before v1.20.0?** Update once, and from then on it is automatic. The world,
> settings, logins and mods stay; the game keeps running.
**Installed on 7 September 2026 or later** — the panel has an **Update panel** button in
Settings. Click it once.
**Older install, or no button** — run this once **on the Proxmox host** (put your container's
id in place of `CTID`, `pct list` shows it):
```bash
pct exec CTID -- bash -c "curl -fsSL https://raw.githubusercontent.com/PawelSzymanski89/valheim-proxmox/main/panel/panel-update.sh | bash"
```
Installed with `setup.sh` straight on a Debian machine — run it there, as root:
```bash
curl -fsSL https://raw.githubusercontent.com/PawelSzymanski89/valheim-proxmox/main/panel/panel-update.sh | bash
```
**Docker** — the image is the update there. In the `docker/` folder of your clone:
`git fetch --tags && git checkout vX.Y.Z && docker compose up -d --build` — the panel's banner shows
the exact command for the newest release. A release tag, not `main`: tags cannot be moved or
deleted on GitHub, so the build is the reviewed, signed release and not whatever `main` is that day.
It ends with `updated to v… - world and settings untouched`. Details: [Updating](#updating).
## Install
Three ways in, depending on what you are installing onto. All end with the same thing:
the game server, the panel, backup and update timers.
### On a Proxmox VE host — creates the container for you
Run as root **on the Proxmox host**. It builds an unprivileged Debian LXC, installs
everything into it and prints the address, the login and the game password.
```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/PawelSzymanski89/valheim-proxmox/main/install.sh)"
```
Defaults: 4 cores, 6 GB RAM, 30 GB disk, DHCP. Change any of them with flags —
`--ram 12288 --disk 40 --ip 192.168.1.50/24 --gw 192.168.1.1`, and `--help` lists the rest.
The container gets its own Proxmox firewall rules: the game ports (UDP) open to everyone, the
panel only to private networks (LAN, and CGNAT ranges like Tailscale), everything else dropped
— including the RCON port of the admin tools, which listens on every interface and has no
setting to stop it. The rules only act when the Proxmox firewall is on for the datacenter;
the installer says so if it is not. `--no-firewall` skips them.
```bash
# on the Proxmox host, for an existing container (CTID = its id; ports as installed)
cat > /etc/pve/firewall/CTID.fw <<'FW'
[OPTIONS]
enable: 1
policy_in: DROP
dhcp: 1
ndp: 1
[RULES]
IN ACCEPT -p udp -dport 2456:2458 -log nolog
IN ACCEPT -p tcp -dport 2460 -source 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10 -log nolog
IN Ping(ACCEPT) -log nolog
FW
pct set CTID --net0 "$(pct config CTID | sed -n 's/^net0: //p'),firewall=1"
```
### On any Debian 12/13 machine — installs into the system you are on
No Proxmox, no container: a VPS, a spare box, an LXC you already made. Run as root
**inside that system**.
```bash
curl -fsSL https://raw.githubusercontent.com/PawelSzymanski89/valheim-proxmox/main/setup.sh -o setup.sh && bash setup.sh
```
Same environment variables as the flags above (`RAM=` and `DISK=` do not apply here —
the machine is whatever you are running on).
### Docker — on any Linux box that already runs Docker
One container with systemd inside, so the panel works exactly as on the other two paths.
The game downloads from Steam on the first start; ports, names and passwords come from
`.env` and are read once — after that the panel owns them.
```bash
git clone https://github.com/PawelSzymanski89/valheim-proxmox && cd valheim-proxmox/docker
cp .env.example .env # optional
docker compose up -d --build
```
Host networking: `GAME_PORT` (udp, plus the next one) and `PANEL_PORT` (tcp) are the ports
inside and outside, nothing to map. Details, the plain `docker run` form and what it needs
(`privileged`) are in [docker/README.md](docker/README.md). Not for Docker Desktop on
macOS/Windows.
Either way it takes a few minutes, most of it Steam pulling ~1.5 GB. Afterwards the panel
is on **http://ADDRESS:2460**, login `admin` and the password the installer printed at the
end — generated for this install.
---
## The public page
The login address is the only page strangers ever see, so it doubles as a **status page**:
server up or down, uptime, how many are playing, and how to join — with the login form beside it.
It is **minimal on purpose**. Every field is off until you turn it on in **Settings → Public page**,
because this is visible to the whole internet: a version number narrows down what to try against
the server, a mod list and a load chart tell a stranger what runs there and when nobody is
watching. What you can switch on: player count, their names, machine specs with a live CPU and memory reading, the mod list with the share code
(handy — players get everything they need before asking), load charts, server version, port and
whether a password is required. Plus a free line of your own, e.g. when the server restarts.
**The game password is never exposed, at any setting.** Nor is anything else the panel knows:
disk, logs, checks and settings all sit behind the login, and `/api/public` is the only route
that answers without one.
With a big pack the page rearranges itself: status and login share the top row, the mod list
takes the full width as a grid of tiles with a search box, and the charts sit below. Seventy-two
mods in a narrow column next to empty space was the first version, and it looked it.

## What it looks like
The panel keeps its own dark norse skin — stone, soot and dimmed gold — on the login screen
and inside. Interface in English or Polish, switch in the top right.
| | |
|---|---|
|  |  |
| **Mods** — paste a share code, pick what to install | **Mod config** — a form built from the file's own metadata |

**Summary** — join addresses with copy buttons, live load of the container, and checks that
say what they prove. Addresses and secrets in these shots are masked by the panel itself:
open it with `?demo=1` and every IP, password, join code and profile code is replaced with
a documentation value, so a screenshot never leaks the network it was taken on.

**Settings** — server name, world, ports, listing, crossplay, world preset and modifiers,
plus the panel login.
## What you get
| | |
|---|---|
| **Game server** | Valheim dedicated, systemd unit with a clean stop (`SIGINT`, so the world is saved) |
| **Panel** | web UI on port **2460**, HTTP Basic auth, password generated at install |
| **Backups** | world snapshot every 2 h, 30 kept (both set in the panel), restore with one click |
| **Updates** | checks Steam every 2 h and restarts **only** when there is a new build |
| **Defaults** | 4 cores, 6 GB RAM, 30 GB disk, container starts on boot |
## The panel
| Tab | What it does |
|---|---|
| **Summary** | join addresses for LAN and for the internet (with copy buttons), live load / RAM / disk of the container, and connectivity checks that say what they can and cannot prove |
| **Players** | who is online right now — name, id, **live session timer** — and a persistent login history (first seen / last seen / number of joins) |
| **Access & bans** | admin list, ban list, allowlist; ban straight from the online list or the history. **An allowlist that is not empty locks everyone else out** — that is Valheim's own rule, not the panel's |
| **World** | list worlds, switch the active one, download, delete, upload a world — a Valheim 1.0 world folder, or an old `.db` + `.fwl` pair the server converts |
| **Backups** | restore, download, delete; copies to another machine over SSH; toggles for the auto-backup and auto-update timers |
| **Settings** | server name, world, password, **game port**, **panel port**, public listing, crossplay, world preset and modifiers (combat, death penalty, resources, raids, portals) and the world toggles (`nobuildcost`, `playerevents`, `passivemobs`, `nomap`) |
| **Mods** | paste a Thunderstore Mod Manager / r2modman **share code**: the panel expands it, shows what is inside, and installs the picked packages (BepInEx included, world backed up first). Also installs single packages by name |
| **Log** | server events, with the PlayFab keepalive noise filtered out |
Plus Start / Stop / Restart / Back up now / Check update.
### Panel login and getting back in
Login happens on the panel's own screen, not the browser's grey box: session cookie, signed
with a secret **and** the current password hash, so changing the password ends every session.
HTTP Basic still works for `curl` and scripts.
The first password is generated for each install and printed at the end of it (with Docker:
`docker compose logs | grep "panel login"`). Until v1.20.1 it was `valheim123` everywhere; an
install that still has it shows a red banner until it is changed in **Settings → Panel login**,
which asks for the current password first.
Tried `valheim123` and got told it was retired? Every install up to v1.21.0 started with that
password, and an update never touched it — so from v1.22.0 the panel replaces it with a random
one the first time it starts. Set your own the same way as after a lockout:
Locked out? There is no reset dance — set a new password from the Proxmox host:
```bash
pct exec