What you will set up
Pterodactyl is a panel for game servers. You create a server in your browser, pick a game such as Minecraft, and Pterodactyl installs and runs it in a Docker container of its own, with limits on memory, CPU and disk. You, or the players you give access to, get a console, a file manager, schedules and backups for each server.
Pterodactyl has two parts. The panel is the website, a PHP app with a MariaDB database. Wings runs on each machine that hosts game servers and starts the containers. This guide puts both on the same server. Wings cannot run with rootless Podman: it drives the root Docker daemon directly, creating a container for every game server and changing the ownership of their files, so it runs as root and needs a server of its own. Pelican is a newer fork of Pterodactyl, if you want to compare.
Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:
- Pterodactyl's documentation lists Debian 13 as supported. The panel ran on Debian's own PHP 8.4, MariaDB 11.8 and Redis 8.0, without the extra package repositories the documentation adds. Step 1 lists what else was adjusted.
- The admin was created on the command line. Both web servers were tested on the same server: Caddy, and before it nginx with Certbot. Each got Let's Encrypt certificates for the panel and for Wings and sent HTTP to HTTPS. With Caddy, Wings restarted by itself when its certificate was replaced. With Certbot, a test renewal passed.
- The panel logged each visitor's real address.
- A location, a node and allocations were created, and a Minecraft server from the default Paper egg was installed and running within a minute. A status ping from outside reached it over IPv4. Over IPv6 it answered from the server itself, as our test machine has no IPv6.
- The console worked and a backup of the game server was taken and restored with both. The file manager and SFTP were tested with Caddy, also after a reboot.
- From the internet, only SSH, the website, Wings' ports 8080 and 2022 and the game port were open. The database and Redis were not.
- The panel and Wings were updated, and everything came back by itself after a reboot, including the game server.
With one idle Minecraft server, the whole server used about 1.5 GB of memory with Caddy and 1.6 GB with nginx, of which the Minecraft server used about 820 MB.
Before you start
You need:
- a fresh Melonslab server with Debian 13 and nothing else installed. Each game server needs its own memory on top of the panel, so pick the size by the games you want to run;
- two names with A and AAAA records pointing at the server: one for the panel, such as
panel.example.com, and one for Wings, such aswings.example.com.
Set up SSH keys and automatic security updates as in steps 1 to 3 of the security guide, and skip its step 4: step 10 here sets up the firewall with the ports Pterodactyl needs.
The examples use panel.example.com and wings.example.com, and 203.0.113.10 and 2001:db8::10 for the server's addresses. Replace them with your own throughout.
The panel needs a web server, and this guide covers two. Caddy gets and renews certificates by itself. nginx with Certbot is what Pterodactyl's documentation shows first. Pick one, and the steps below follow it:
1. Install what the panel needs
The panel needs PHP, a web server, MariaDB and Redis. Pterodactyl's guide for Debian adds a package repository for PHP 8.3, and on older Debian releases ones for MariaDB and Redis too. Debian 13 has recent enough versions of all of them. As root:
With Caddy
apt update
apt install -y php8.4 php8.4-{common,cli,gd,mysql,mbstring,bcmath,xml,fpm,curl,zip} mariadb-server caddy tar unzip git redis-server curl composer
This installs PHP 8.4, which Pterodactyl's documentation does not name. The panel's own requirements allow it, and no step below showed an error or warning with it. The other changes from the documentation, for Debian 13 and for this setup, are:
- the scheduled task in step 4 runs as the web server's user, not as root;
.env, which holds the database password and the encryption key, is made readable only by the web server's PHP in step 3;- Composer comes from Debian's packages instead of its own installer.
With Caddy
The web server is Caddy from Debian's own package, with Pterodactyl's own Caddy configuration pointed at PHP 8.4 in step 5.
2. Download the panel and create its database
mkdir -p /var/www/pterodactyl
cd /var/www/pterodactyl
curl -Lo panel.tar.gz https://github.com/pterodactyl/panel/releases/latest/download/panel.tar.gz
tar -xzvf panel.tar.gz
chmod -R 755 storage/* bootstrap/cache/
Open the database shell with mariadb, and create a user and a database for the panel, with a password of your own:
CREATE USER 'pterodactyl'@'127.0.0.1' IDENTIFIED BY 'Choose-a-long-password';
CREATE DATABASE panel;
GRANT ALL PRIVILEGES ON panel.* TO 'pterodactyl'@'127.0.0.1' WITH GRANT OPTION;
exit
Then install the panel's PHP libraries and create its encryption key:
cp .env.example .env
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader
php artisan key:generate --force
The key is the line starting with APP_KEY= in /var/www/pterodactyl/.env. The panel encrypts secrets such as API keys with it, and a database backup cannot be read without it. Keep a copy of the line somewhere safe, away from the server, such as in a password manager.
3. Configure the panel and create the admin
php artisan p:environment:setup
This asks a few questions. Answer:
| Question | Answer |
|---|---|
| Egg Author Email | your email address |
| Application URL | https://panel.example.com |
| Application Timezone | your timezone, such as Europe/Stockholm |
| Cache Driver, Session Driver and Queue Driver | redis for all three |
| Enable UI based settings editor? | yes |
| Enable sending anonymous telemetry data? | no, or yes if you want to help |
Keep the defaults for the Redis questions. The telemetry is anonymous: once a day, it sends a random ID, the versions of the panel, PHP, the database and Docker, and counts of servers, users and backups to telemetry.pterodactyl.io, with no names, email addresses or IP addresses in the data.
Then connect the panel to its database. Keep the defaults, and enter the password from step 2:
php artisan p:environment:database
Create the tables and load the default eggs, the templates for each game:
php artisan migrate --seed --force
Pterodactyl has no sign-up page for the first admin, so no one else can take the panel before you. Create your admin on the command line:
php artisan p:user:make
Answer yes to Is this user an administrator?, then enter an email address, a username, a first and last name, and a password with at least 8 characters, a capital letter and a number.
Finally, give the files to the web server, and keep .env from other users on the server:
chown -R www-data:www-data /var/www/pterodactyl/*
chown www-data:www-data /var/www/pterodactyl/.env
chmod 600 /var/www/pterodactyl/.env
The panel sends mail for password resets and new users. We did not set it up for this guide. If you have an SMTP server, php artisan p:environment:mail asks for its details. Until then, the panel's log shows an error each time it tries to send.
4. Start the background jobs
The panel runs scheduled tasks every minute and a queue worker for work in the background. Add the scheduled task to the web server's crontab, so that the files it creates belong to the web server:
echo "* * * * * php /var/www/pterodactyl/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data -
Create /etc/systemd/system/pteroq.service for the queue worker:
[Unit]
Description=Pterodactyl Queue Worker
After=redis-server.service
[Service]
User=www-data
Group=www-data
Restart=always
ExecStart=/usr/bin/php /var/www/pterodactyl/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
StartLimitInterval=180
StartLimitBurst=30
RestartSec=5s
[Install]
WantedBy=multi-user.target
Then start it:
systemctl enable --now redis-server pteroq.service
5. Get certificates and set up the web server
With Caddy
Caddy serves the panel, gets its certificate from Let's Encrypt, renews it, and sends HTTP to HTTPS, all by itself. It also gets the certificate for Wings' name, which step 8 points Wings at. Debian's Caddy is version 2.6.2, which is older than Caddy's own releases but understands every line of Pterodactyl's configuration, and it gets security updates from Debian.
Replace /etc/caddy/Caddyfile with this. It is Pterodactyl's own Caddy configuration with PHP 8.4, and a second block at the end that only exists to get the certificate for wings.example.com:
{
servers :443 {
timeouts {
read_body 120s
}
}
}
panel.example.com {
root * /var/www/pterodactyl/public
file_server
php_fastcgi unix//run/php/php8.4-fpm.sock {
root /var/www/pterodactyl/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/pterodactyl.log {
roll_size 100MiB
roll_keep_for 7d
}
level INFO
}
}
wings.example.com {
respond 404
}
Then restart Caddy:
systemctl restart caddy
Within a minute, https://panel.example.com answers with a certificate, over IPv4 and IPv6. The Strict-Transport-Security header tells browsers to use only HTTPS for the panel's name for the next six months.
Caddy talks to PHP directly, not as a proxy in front of another web server, so the panel sees each visitor's real address without a trusted proxies setting. Debian's Caddy package adds the caddy user to the www-data group, so it can reach PHP. That is also why .env is readable only by its owner.
6. Log in and switch off reCAPTCHA
Open https://panel.example.com and log in with the admin from step 3. The cog icon at the top right opens the admin area, at https://panel.example.com/admin.
By default, the login page uses Google's reCAPTCHA, with keys that ship with every copy of Pterodactyl, so every visitor's browser loads it from Google. Logins are limited without it too: a few failed attempts lock the login for a while. To switch it off, open Settings, then Advanced, set Status under reCAPTCHA to Disabled, and choose Save. If you want to keep it, create keys of your own with Google and enter them there.
Under Settings and General, Require 2-Factor Authentication can be set to Admin Only or All Users.
7. Install Docker and Wings
Install Docker with Docker's own script, as Pterodactyl's documentation does. It sets Docker to start at boot:
curl -sSL https://get.docker.com/ | CHANNEL=stable bash
Then download Wings:
mkdir -p /etc/pterodactyl
curl -L -o /usr/local/bin/wings "https://github.com/pterodactyl/wings/releases/latest/download/wings_linux_$([[ "$(uname -m)" == "x86_64" ]] && echo "amd64" || echo "arm64")"
chmod u+x /usr/local/bin/wings
Create /etc/systemd/system/wings.service:
[Unit]
Description=Pterodactyl Wings Daemon
After=docker.service
Requires=docker.service
PartOf=docker.service
[Service]
User=root
WorkingDirectory=/etc/pterodactyl
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
Do not start it yet. It needs its configuration from the panel first.
8. Create a location and a node
A node is a machine that runs Wings, and every node belongs to a location. In the admin area:
- Open Locations and choose Create New. Enter a Short Code, such as
se-sto, and a Description, such asStockholm, and choose Create. - Open Nodes and choose Create New. Enter a Name, pick the Location, and enter
wings.example.comas the FQDN. Keep Use SSL Connection and Not Behind Proxy. - Under Configuration, enter how much memory and disk the game servers may use in total, in MiB, such as
6144for Total Memory and30720for Total Disk Space, and0for both over-allocations. Leave some memory for the panel. Keep Daemon Port8080and Daemon SFTP Port2022, and choose Create Node.
Open the node's Configuration tab, and choose Generate Token. The panel shows a command starting with cd /etc/pterodactyl && sudo wings configure. Run it on the server. As root, you can leave out sudo. It writes /etc/pterodactyl/config.yml and ends with Successfully configured wings.
With Caddy
The file expects the certificate where Certbot would put it, under /etc/letsencrypt. Point it at Caddy's certificate for wings.example.com instead. Wings runs as root, so it can read Caddy's files:
C=/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wings.example.com
sed -i "s#cert: /etc/letsencrypt/live/wings.example.com/fullchain.pem#cert: $C/wings.example.com.crt#; s#key: /etc/letsencrypt/live/wings.example.com/privkey.pem#key: $C/wings.example.com.key#" /etc/pterodactyl/config.yml
grep -A4 "ssl:" /etc/pterodactyl/config.yml
The last command shows the two new paths under cert: and key:. When you change the node in the panel later, Wings keeps them.
Wings reads the certificate only when it starts, and Caddy renews it about a month before it expires. So have systemd restart Wings whenever Caddy writes a new one. Create /etc/systemd/system/wings-cert.path:
[Unit]
Description=Restart Wings when Caddy renews its certificate
[Path]
PathChanged=/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wings.example.com/wings.example.com.crt
[Install]
WantedBy=multi-user.target
And /etc/systemd/system/wings-cert.service, which waits ten seconds so that Caddy has written the key too:
[Unit]
Description=Restart Wings for a renewed certificate
[Service]
ExecStartPre=/bin/sleep 10
Type=oneshot
ExecStart=/usr/bin/systemctl restart wings.service
Restarting Wings does not stop the game servers. Then start Wings and the watch:
systemctl daemon-reload
systemctl enable --now wings wings-cert.path
The token is an API key with access to your nodes, and the command also prints it in full. Wings does not need it once it is configured, so delete it: open Application API, and remove the key with the memo Automatically generated node deployment key.
9. Add allocations
An allocation is an address and a port that a game server can use. Open the node's Allocation tab. Under Assign New Allocations:
- Enter
0.0.0.0as the IP Address,25565in Ports, and a name that points at the server as the IP Alias, such aswings.example.com, and choose Submit. The alias is the address players see in the panel. - Add the same port again with
::as the IP Address, for IPv6.
0.0.0.0 accepts players on every IPv4 address of the server and :: on every IPv6 address. Ports takes a range too, such as 25565-25570, for more servers.
10. Open the firewall
Wings' API on port 8080 is what your browser connects to for the console, and port 2022 is its SFTP server for game server files. Both need to be reachable, as do SSH and the website.
With Caddy
Caddy also answers on UDP port 443 for HTTP/3:
apt install -y ufw
ufw allow 22/tcp
ufw allow 80,443/tcp
ufw allow 443/udp
ufw allow 8080,2022/tcp
ufw enable
Game ports need no rule. Docker opens the ports it publishes past ufw, so a game port is open to the internet as soon as a server uses its allocation, and closed again when no server does. Ports without a server stay closed. MariaDB and Redis only listen on the server itself.
11. Create a game server
In the admin area, open Servers and choose Create New:
- Enter a Server Name, and type your email address in Server Owner to pick your account.
- Pick the node, and
0.0.0.0:25565as the Default Allocation. - Under Application Feature Limits, set Backup Limit to the number of backups to keep, such as
2. It starts at0, which means no backups for this server. - Set Memory and Disk Space in MiB, such as
2048and5120. - Under Nest Configuration, pick Minecraft and the Paper egg. The defaults under Docker Configuration and Service Variables install the latest Minecraft version.
- Choose Create Server.
For IPv6, open the server's Build Configuration tab, pick :::25565 under Assign Additional Ports, and choose Update Build Configuration.
Then open the server from your own list at https://panel.example.com. The install took seconds on our test server. Choose Start. Minecraft stops at first until you accept its licence, and the panel asks you to: read it, and choose I Accept. The server starts, and the console shows its output.
Players connect to wings.example.com, the address shown under Address. Minecraft's default port, 25565, does not need to be typed.
12. Back up
On the server's Backups tab, choose Create backup, give it a Backup name, and choose Start backup. Our Minecraft server's backup was 208 MB. To restore, open the menu next to a backup, choose Restore, and tick Delete all files before restoring backup to get exactly the backed up files back. The server stops while it restores. Schedules can create backups by themselves.
These backups are saved in /var/lib/pterodactyl/backups on the same server, so they do not help if the server is lost. Pterodactyl can also store them in an S3 bucket, which we did not test. To rebuild the panel itself, keep a copy of these away from the server:
- a dump of the database, from
mariadb-dump --single-transaction panel | gzip > panel.sql.gz; /var/www/pterodactyl/.env, with the encryption key;/etc/pterodactyl/config.yml;- the game servers' files in
/var/lib/pterodactyl/volumes, or their backups.
13. Updates
Check Pterodactyl's releases before you update. To update the panel:
cd /var/www/pterodactyl
php artisan down
curl -L https://github.com/pterodactyl/panel/releases/latest/download/panel.tar.gz | tar -xzv
chmod -R 755 storage/* bootstrap/cache
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader
php artisan view:clear
php artisan config:clear
php artisan migrate --seed --force
chown -R www-data:www-data /var/www/pterodactyl/*
php artisan queue:restart
php artisan up
To update Wings, stop it, download it again, and start it. Game servers keep running while Wings is stopped:
systemctl stop wings
curl -L -o /usr/local/bin/wings "https://github.com/pterodactyl/wings/releases/latest/download/wings_linux_$([[ "$(uname -m)" == "x86_64" ]] && echo "amd64" || echo "arm64")"
chmod u+x /usr/local/bin/wings
systemctl restart wings
PHP, the web server, MariaDB, Redis and Docker are Debian and Docker packages, covered by the automatic security updates from the security guide.
Troubleshooting
The console says it cannot connect, or shows no stats. Your browser connects to Wings at wings.example.com on port 8080. Check that Wings runs with systemctl status wings, that port 8080 is allowed in ufw, and that the node's FQDN matches the name on Wings' certificate. journalctl -u wings shows why Wings stopped.
With Caddy
Wings does not start, and journalctl -u wings shows failed to configure HTTPS server with no such file or directory. Caddy has not got the certificate for wings.example.com yet, usually because its A or AAAA record does not point at the server. journalctl -u caddy shows why. Once the file exists in the directory from step 8, systemctl restart wings starts Wings.
Players cannot connect over IPv6. The server has no allocation on ::. Add one as in step 9, assign it as in step 11, and restart the game server from its console.
The Backups tab says "Backups cannot be created for this server because the backup limit is set to 0." Raise Backup Limit on the server's Build Configuration tab in the admin area, and choose Update Build Configuration.