Configuration
Yggdrasil Panel reads one YAML file at startup, /etc/yggdrasil/config.yaml. The installer
generates it on first install and never touches it again on re-runs. Everything else — servers,
users, integrations, backup targets — lives in the database and is configured in the web UI.
Restart the service after editing the file:
sudo systemctl restart yggdrasil
Full file
This is what the installer writes, with the generated values replaced:
server:
host: "0.0.0.0"
port: 8080
database:
path: "/var/lib/yggdrasil/yggdrasil.db"
auth:
secret_key: "<44-char random base64>"
session_ttl_hours: 168
docker:
socket: "unix:///var/run/docker.sock"
ports:
range_min: 25000
range_max: 30000
admin:
username: "admin"
password: "<generated on first install>"
Every key is optional — a missing file or a missing key falls back to the defaults below. The file
is written chmod 640, owned root:yggdrasil, because it holds auth.secret_key.
Keys
| Key | Default | Meaning |
|---|---|---|
server.host |
0.0.0.0 |
Listen address. Set to 127.0.0.1 if a reverse proxy on the same host is the only thing that should reach the panel. |
server.port |
8080 |
Listen port. Must be 1–65535. |
database.path |
/var/lib/yggdrasil/yggdrasil.db |
SQLite file. Created on first boot. |
auth.secret_key |
(none) | Signs JWTs and derives the encryption key for secrets at rest. See below. |
auth.session_ttl_hours |
168 |
Login session lifetime (7 days). Also sets the ygg_token cookie’s max age. |
docker.socket |
unix:///var/run/docker.sock |
Docker endpoint. The panel talks to Docker over this; it does not mount it into any container. |
ports.range_min |
25000 |
Low end of the automatic port-allocation range. |
ports.range_max |
30000 |
High end. Must be greater than range_min. |
admin.username |
(none) | Bootstrap admin. See below. |
admin.password |
(none) | Bootstrap admin password. See below. |
auth.secret_key
This one key protects everything. It signs session tokens and derives the AES-GCM key that encrypts stored secrets — UniFi and Nginx Proxy Manager credentials, Cloudflare and BattleMetrics tokens, the Discord webhook, and backup-target credentials.
It must be at least 16 characters; the panel fails closed and refuses to encrypt with a shorter
one rather than silently weakening it. The installer generates 44 characters of base64 from
/dev/urandom.
Changing it invalidates every existing session and makes every already-encrypted secret undecryptable — you would have to re-enter each integration’s credentials. Back it up with the database; a restored database is useless without the matching key.
admin.username / admin.password
These bootstrap the first admin account, and only that. On boot, if a user with the admin role
already exists, the block is ignored entirely — it will not reset a password or create a second
admin.
If admin.username is set with no password, the panel generates one and prints it to stdout once,
as a plain banner rather than through the logger (so it doesn’t linger in journalctl):
sudo journalctl -u yggdrasil --since "10 minutes ago" | head -20
Because the account is created once and never re-checked, the right move after first login is to
change the password in the UI and then delete the whole admin: block from the config, so a
plaintext password isn’t sitting on disk. Recovering a lost admin password means deleting the admin
user from the database and letting the block re-bootstrap it.
ports
Yggdrasil allocates a free host port per server port by walking this range from range_min upward,
and tracks it so two servers never collide. A port counts as free only if it isn’t already
allocated, isn’t claimed earlier in the same request, and can actually be bound right now — which
also catches ports held by processes Yggdrasil doesn’t know about.
The game’s well-known port is deliberately ignored. A Minecraft server does not land on 25565 and DayZ does not land on 2302; both get a port from this range. Off-default ports attract noticeably less scanning, and every server still gets a unique one. The rune’s declared port is the container side of the mapping only.
The exception is Steam games, which publish 1:1 (host port == container port) because the game binds, publishes, and advertises the same number to the Steam master.
Steam games publish 1:1 (host port == container port) so the game binds, publishes, and advertises the same number to the Steam master. See the rune schema for how ports are declared and injected.
Whatever range you choose has to be reachable from the internet for players to connect — see Networking.
Environment variables
The config file has no environment-variable overrides. The installer reads a few for its own behavior, not the panel’s:
| Variable | Used by | Meaning |
|---|---|---|
YGG_REPO |
install.sh |
Source repo. Default kristianwind/yggdrasil. |
YGG_VERSION |
install.sh |
Release tag to install. Default latest. |
YGG_BINARY_URL |
install.sh |
Install a prebuilt binary from a URL instead of a GitHub release. |
YGG_BINARY_FILE |
install.sh |
Install a prebuilt binary from a local path. |
The last two exist for testing a build before it’s released.
Paths
| Path | Contents |
|---|---|
/usr/local/bin/yggdrasil |
The binary. UI and builtin runes are embedded in it. |
/etc/yggdrasil/config.yaml |
This file. |
/var/lib/yggdrasil/yggdrasil.db |
SQLite database — everything not in the config file. |
/var/lib/yggdrasil/servers/<uuid>/ |
One server’s data directory, bind-mounted into its container. |
/var/lib/yggdrasil/steam-cache/ |
Shared SteamCMD cache across Steam-game servers. |
/etc/systemd/system/yggdrasil.service |
The unit. Runs as the yggdrasil system user. |
Back up config.yaml and yggdrasil.db together. The server data directories are large and are
what the panel’s own backup system is for.
See also
- Getting started — installing for the first time
- Networking — making servers reachable
- API reference — the HTTP API