What you will set up
Pelican is a panel for game servers. From your browser, you create a Minecraft server, or a server for one of hundreds of other games, give it memory and a port, and start it. You and your friends get a console, a file manager, backups and SFTP access, each with their own login.
Pelican has two parts. The Panel is the website, a PHP app. Wings is a daemon that runs each game server in its own Docker container. This guide puts both on the same server, with Caddy or nginx as the web server. Pelican is still in beta, so read the release notes before each update.
Wings runs as root and needs Docker running as root, not rootless Podman like our other guides: it creates a container for every game server, sets its memory and CPU limits, and publishes its game ports, all through Docker's root socket. So give Pelican a server of its own.
Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13, once with each web server:
- The Panel was installed with PHP 8.4 from Debian, and got a Let's Encrypt certificate from Caddy or from Certbot, which Wings uses too. The Panel answered over IPv4 and IPv6, and sent HTTP to HTTPS.
- The web installer was reachable only from our own address until it was done, which we checked from outside. Scanners opened the new name within two minutes of its certificate being issued.
- A Paper Minecraft server was installed and started in about 30 seconds, and answered a Minecraft status ping over IPv4 from outside and over IPv6 from the server itself. The console, the file manager and SFTP worked.
- A backup of the game server took about a second, and restoring it brought back a deleted world.
- With Caddy, a new certificate for Wings was picked up within seconds. The Panel's update script, a restart of Wings and a reboot left everything running, including the game server.
With one idle Minecraft server running, the server used about 1.7 GB of memory, of which the game server took about 1 GB.
Before you start
Pick the web server. Caddy gets and renews its certificates by itself. nginx uses Certbot for them. Both follow Pelican's own configuration.
You need:
- a fresh Melonslab server with Debian 13 and nothing else installed. The Panel and Wings need about 1 GB of memory, and each game server needs its own on top of that: 2 to 4 GB for a small Minecraft server;
With Caddy
- two names for the server, each with an A record and an AAAA record pointing at it: one for the Panel, such as
pelican.example.com, and one for Wings, such aswings.example.com. Caddy gets a certificate for each, and Wings uses its own. A name of its own for Wings also lets you move Wings to another server later.
Set up SSH keys, automatic security updates and the firewall as in steps 1 to 4 of the security guide, including the rules for HTTP and HTTPS. Step 10 here explains which ports ufw controls and which it does not.
The examples use pelican.example.com for the Panel and 198.51.100.20 for the address you browse from. Replace them with your own throughout.
1. Install PHP and the web server
Pelican needs PHP 8.3 or newer. Debian 13 has PHP 8.4. As root:
With Caddy
apt update
apt install -y curl tar unzip caddy \
php8.4-fpm php8.4-gd php8.4-mysql php8.4-mbstring php8.4-bcmath php8.4-xml \
php8.4-curl php8.4-zip php8.4-intl php8.4-sqlite3
This is Debian's own Caddy, version 2.6.2. It is older than the version in Caddy's own package repository, but it runs Pelican's Caddy configuration as it is, and its security updates come from Debian with the rest of the system.
Open the ports for Wings too: 8080 is its API, which your browser connects to for the console, and 2022 is its SFTP server:
ufw allow 8080,2022/tcp
2. Download the Panel
mkdir -p /var/www/pelican
cd /var/www/pelican
curl -L https://github.com/pelican-dev/panel/releases/latest/download/panel.tar.gz | tar -xz
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader
php artisan p:environment:setup
The last command creates /var/www/pelican/.env with an encryption key, APP_KEY, which protects secrets in the database. Keep a copy of it somewhere safe, away from the server, such as in a password manager: without it, a backup of the database cannot be decrypted.
Set the Panel's address before you open it. The installer page loads its styles from this address, and shows up blank without them:
sed -i 's|^APP_URL=.*|APP_URL=https://pelican.example.com|' .env
chmod -R 755 storage/* bootstrap/cache/
chown -R www-data:www-data /var/www/pelican
3. Get a certificate and set up the web server
Until you have finished Pelican's web installer in step 4, anyone who opens it can create the admin account and point the Panel at a database of their own. The two lines marked # installer below let only your own address in until then.
With Caddy
If your connection has IPv6, add your IPv6 address after 198.51.100.20 on the same line, with a space between them.
Replace /etc/caddy/Caddyfile with:
{
servers :443 {
timeouts {
read_body 120s
}
}
}
pelican.example.com {
@notyou not remote_ip 198.51.100.20 # installer
respond @notyou 403 # installer
root * /var/www/pelican/public
file_server
php_fastcgi unix//run/php/php8.4-fpm.sock {
root /var/www/pelican/public
index index.php
env PHP_VALUE "upload_max_filesize = 100M
post_max_size = 100M"
env HTTP_PROXY ""
env HTTPS "on"
read_timeout 300s
dial_timeout 300s
write_timeout 300s
}
header Strict-Transport-Security "max-age=16768000; preload;"
header X-Content-Type-Options "nosniff"
header X-XSS-Protection "1; mode=block;"
header X-Robots-Tag "none"
header Content-Security-Policy "frame-ancestors 'self'"
header X-Frame-Options "DENY"
header Referrer-Policy "same-origin"
request_body {
max_size 100m
}
respond /.ht* 403
log {
output file /var/log/caddy/pelican.log {
roll_size 100MiB
roll_keep_for 7d
}
level INFO
}
}
wings.example.com {
respond 404
}
This is Pelican's own configuration with three changes: it uses PHP 8.4's socket, it has the two # installer lines, and it has a block for wings.example.com, which only makes Caddy get a certificate for Wings. Wings itself listens on port 8080 with that certificate, and nothing is served on the name's port 443. Then load it:
caddy validate --config /etc/caddy/Caddyfile
systemctl restart caddy
Within a minute, Caddy has certificates from Let's Encrypt for both names, and renews them by itself. It listens on IPv4 and IPv6, sends HTTP to HTTPS, and also offers HTTP/3 on UDP port 443, which the security guide's rule allows. The Panel sees each visitor's real address, as the logins under Activity in your profile show.
4. Run the web installer
The Panel needs a scheduled task and a queue worker, which also installs the game templates you pick in the installer. Set both up first:
(crontab -l -u www-data 2>/dev/null; echo "* * * * * php /var/www/pelican/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -
cd /var/www/pelican
php artisan p:environment:queue-service -n
The last command creates and starts pelican-queue.service. Now open https://pelican.example.com/installer, and go through the steps:
- Environment: keep the App URL, and create your admin account under Admin User with an email address, a username and a long password.
- Database: keep SQLite, which Pelican recommends. The database is the file
/var/www/pelican/database/database.sqlite. - Eggs: eggs are Pelican's templates for games. Open the Minecraft tab and tick Paper, and anything else you want. You can import more later under Eggs.
- Cache, Queue and Session: keep the defaults. On Queue, switch on I have done both steps below, as you did them above.
Choose Finish. You are logged in to the admin area, and the installer is gone: /installer now answers 404. Remove the two # installer lines, so that everyone else can reach the Panel too, and make the files with secrets readable only by the web server:
With Caddy
sed -i '/# installer/d' /etc/caddy/Caddyfile
systemctl reload caddy
chmod 600 /var/www/pelican/.env /var/www/pelican/database/database.sqlite
Both files are readable by every user on the server until you do.
5. Install Docker and Wings
This is Docker's own install script, which Pelican's documentation uses:
curl -sSL https://get.docker.com/ | CHANNEL=stable sh
mkdir -p /etc/pelican /var/run/wings
curl -L -o /usr/local/bin/wings https://github.com/pelican-dev/wings/releases/latest/download/wings_linux_amd64
chmod u+x /usr/local/bin/wings
Create /etc/systemd/system/wings.service:
[Unit]
Description=Wings Daemon
After=docker.service
Requires=docker.service
PartOf=docker.service
[Service]
User=root
WorkingDirectory=/etc/pelican
LimitNOFILE=4096
PIDFile=/var/run/wings/daemon.pid
ExecStart=/usr/local/bin/wings
Restart=on-failure
StartLimitInterval=180
StartLimitBurst=30
RestartSec=5s
[Install]
WantedBy=multi-user.target
6. Create the node
A node is a server that runs Wings. In the Panel, open Nodes and choose New Node:
With Caddy
- Domain Name:
wings.example.com.
- Keep Port
8080and HTTPS (SSL), and give it a Display Name, such asStockholm. - Go on to Advanced Settings. The defaults are fine: server files in
/var/lib/pelican/volumesand SFTP on port2022. Choose the plus sign to create the node.
The node's Configuration File tab shows the file Wings needs. Write it to the server:
cd /var/www/pelican
php artisan p:node:configuration 1 > /etc/pelican/config.yml
chmod 600 /etc/pelican/config.yml
1 is the node's number, which you also see in the browser's address bar, as in /admin/nodes/1/edit. The file holds the node's secret token, so keep it to root.
With Caddy
The file expects the certificate where Certbot saves it. Point it at the files Caddy keeps for wings.example.com instead. Wings runs as root, so it can read them, although they belong to the caddy user:
C=/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wings.example.com
sed -i "s|^ cert: .*| cert: $C/wings.example.com.crt|; s|^ key: .*| key: $C/wings.example.com.key|" /etc/pelican/config.yml
grep -E '^ (cert|key):' /etc/pelican/config.yml
The last command shows the two new paths. When you save the node in the Panel later, it sends Wings the Certbot paths again, but Wings keeps the ones you set. Start Wings:
systemctl daemon-reload
systemctl enable --now wings
Wings reads the certificate only when it starts, and Caddy renews it about a month before it runs out, without telling Wings. A systemd path unit restarts Wings when that happens. It watches the .json file that Caddy writes last, after the certificate and the key. Create /etc/systemd/system/wings-cert.path:
[Unit]
Description=Watch the Wings certificate that Caddy renews
[Path]
PathChanged=/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wings.example.com/wings.example.com.json
[Install]
WantedBy=multi-user.target
And /etc/systemd/system/wings-cert.service:
[Unit]
Description=Restart Wings to load a renewed certificate
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart wings.service
systemctl daemon-reload
systemctl enable --now wings-cert.path
We tested it by making Caddy get a new certificate: Wings restarted and served it within seconds.
After a minute, the node's Overview shows the Wings version and the server's memory and disk. Restarting Wings leaves running game servers running.
7. Add a port for players
An allocation is an address and a port that a game server listens on. Wings passes the address to Docker as it is: 0.0.0.0 means every IPv4 address and :: every IPv6 address, but each only one of the two. For players to reach a game over both, give it the same port on both.
Open the node, and under Allocations, choose New allocation:
- Pick
0.0.0.0as the IP Address, type25565, Minecraft's port, under Ports and press Enter, and choose Create. - Do the same again with
::as the IP Address.
8. Create a Minecraft server
Open Servers and choose New Server:
- Information: a Name, such as
Survival, and an Owner. Pick0.0.0.0:25565as the Primary Allocation and[::]:25565under Additional Allocations. - Egg Configuration: pick Paper. The defaults download the latest version of Minecraft and start the server after installing it.
- Environment Configuration: under Memory, choose Limited and give it a Memory Limit, such as
4096MiB. Set Swap Memory to Disabled. Under Backups, allow a number of backups, such as2: the default,0, allows none.
Choose the plus sign to create it. Wings downloads the Java image and Paper, which took about 30 seconds on our test server. The first start stops at Minecraft's licence. Open the server from the Panel's front page, choose Start, and I Accept under Minecraft EULA. The console shows the server starting, and Status shows Running.
Players connect to pelican.example.com, or any other name that points at the server. Minecraft uses port 25565 by default, so they do not need to add it.
9. Users and logins
Pelican has no sign-up page: /register answers 404, and only an admin can create accounts, under Users and New User. To share a server with someone, add their account under the server's Users.
Pelican sends no email until you set up a mail server under Settings and Mail: until then, messages such as password resets are only written to the Panel's log. Give each account a password when you create it.
Under your name, Profile has 2FA, Passkeys and SSH Keys. Switch on two-factor authentication for every admin. SFTP asks only for the password, not the second factor, so a long password matters, or an SSH key added there.
For SFTP, the server's Settings show the address and the username under SFTP Information. The username is yours, a dot and the server's short ID:
With Caddy
sftp -P 2022 anna.1a2b3c4d@wings.example.com
10. What is open to the internet
| Port | Used by | Controlled by |
|---|---|---|
| 80, 443 | Panel, and Let's Encrypt renewals | ufw |
| 8080 | Wings API, for the console in the browser | ufw |
| 2022 | Wings SFTP | ufw |
| 25565 and other game ports | game servers | Docker |
The Panel, Wings and SFTP run directly on the server, so ufw decides who reaches them. Game ports are different. Docker opens a game server's ports when it starts, in its own firewall chain that ufw's rules never see: on our test server, ufw deny 25565/tcp did not stop players from connecting. Docker publishes each game port for both TCP and UDP. Ports that no running server uses stay closed.
So the game ports that are open are those of the servers that are running. To close a game port that a running server uses, add a DROP rule in Docker's DOCKER-USER chain, as in step 5 of the Coolify guide.
11. Back up
Open the game server and Backups, and choose New backup. Wings saves it as a .tar.gz file in /var/lib/pelican/backups. To restore one, open its Actions and choose Restore. The server stops while it runs, and Delete all files before restoring backup? removes files that were added after the backup. On our test server, a 208 MB backup took about a second, and restoring it about two. A server's Schedules can also take backups by themselves, which we did not test.
A backup that stays on the server does not help if the server is lost. Copy /var/lib/pelican/backups elsewhere, or add an S3-compatible bucket under Backup Hosts with Schema set to S3, which we did not test. For the Panel itself, keep a copy of /var/www/pelican/.env and /var/www/pelican/database/database.sqlite.
12. Updates
Pelican's update script backs up the .env file and the SQLite database to /var/www/pelican/backup, replaces the Panel's files and updates the database. Answer its questions with Enter to keep the defaults, then set the permissions it asks for:
bash -c "$(curl -fsSL https://pelican.dev/updatePanel.sh)"
chmod -R 755 /var/www/pelican/storage/* /var/www/pelican/bootstrap/cache
chown -R www-data:www-data /var/www/pelican
Wings updates itself, and running game servers keep running:
wings update
systemctl restart wings
The admin Dashboard shows when a new Panel version is out, and a node's Overview shows its Wings version next to the latest. Pelican checks both with GitHub. We found no telemetry in the Panel or in Wings.
Troubleshooting
The installer page is blank, with no styles. APP_URL in /var/www/pelican/.env is not the address you opened. Set it as in step 2, and reload the page.
With Caddy
The installer answers "403 Forbidden". The address you browse from is not the one on the remote_ip line. If your connection has IPv6, your browser probably uses that. Add it on the same line, and run systemctl reload caddy.
Wings does not start, and journalctl -u wings shows "no such file or directory" for the certificate. Caddy has not got the certificate for wings.example.com yet, or got it from ZeroSSL, which Caddy falls back to when Let's Encrypt fails. ls /var/lib/caddy/.local/share/caddy/certificates/ shows which, and journalctl -u caddy why. Fix the path in /etc/pelican/config.yml and in wings-cert.path if it is under ZeroSSL's directory.
The console stays empty, and Start does nothing. Your browser connects to Wings on port 8080. Check systemctl status wings, and that ufw status allows 8080. Running wings --debug after systemctl stop wings shows why Wings will not start.
Players cannot connect over IPv4, or over IPv6. The server has an allocation for only one of the two. Add the other as in step 7. In the admin area, open the server's Allocations, choose Associate and pick it, then restart the server.
The server stops right after starting, with "You need to agree to the EULA". Choose I Accept in the dialog that the console shows.