
Every server in your panel has a Backups tab, so Pterodactyl feels covered. It isn't yet: by default those archives sit on the same node as the servers, the panel's own database is in none of them, and a database dump without the panel's encryption key restores a panel that cannot talk to its nodes. This guide covers every part worth backing up, with commands for Ubuntu 24.04 and Debian 12, checked against Panel 1.15.2 and Wings 1.13.4, the stable releases on 30 October 2026.
Quick answer: Back up the panel database (mariadb-dump --single-transaction), the .env file with APP_KEY, the panel folder /var/www/pterodactyl, /etc/pterodactyl/config.yml on every node, the server files in /var/lib/pterodactyl/volumes, the archives in /var/lib/pterodactyl/backups (or your S3 bucket) and the databases on your database hosts. Keep APP_KEY off the server too: without it the panel cannot read node tokens, database passwords, two-factor secrets or API keys. Give each server a Backup Limit above 0, schedule a Create backup task, copy everything to another machine and test a restore on a separate VPS.
What to Back Up
| What | Where (default) | Holds |
|---|---|---|
| Panel database | panel on the panel's MariaDB or MySQL | Users, servers, nodes, allocations, eggs, schedules, API keys, backup records |
.env | /var/www/pterodactyl/.env | APP_KEY, database password, mail settings, S3 keys |
| Panel files | /var/www/pterodactyl | Your exact version, themes, extensions |
| Wings config | /etc/pterodactyl/config.yml on each node | Node token, ports, paths, settings you changed |
| Server files | /var/lib/pterodactyl/volumes/<server uuid> | Worlds, configs, plugins |
| Server backups | /var/lib/pterodactyl/backups/<backup uuid>.tar.gz, or <server uuid>/<backup uuid>.tar.gz in S3 | Archives made in the panel |
| Server databases | Your database hosts | Databases created in a server's Databases tab |
Which machine runs which part is explained in our Panel vs Wings guide.
Step 1: Save APP_KEY Off the Server
The panel encrypts node tokens, database passwords, two-factor secrets and API keys with APP_KEY (2.0 upgrade guide), and the install guide warns that if you lose it, "all encrypted data is irrecoverable, even with database backups."
sudo grep APP_KEY /var/www/pterodactyl/.env
Store the whole APP_KEY=base64:... line in your password manager. Never run php artisan key:generate on a panel with data: the install guide runs it only on a first install.
Step 2: Dump the Panel Every Night
Pterodactyl's 2.0 upgrade guide backs up the panel with three commands: a --single-transaction database dump, a copy of .env and a tar of the panel folder. Put them in a script for root:
sudo install -d -m 700 /var/backups/pterodactyl
sudo nano /usr/local/sbin/pterodactyl-panel-backup
#!/bin/bash
set -euo pipefail
umask 077
DEST=/var/backups/pterodactyl
DAY=$(date +%F)
mariadb-dump --single-transaction --routines --triggers panel | gzip > "$DEST/panel-$DAY.sql.gz.part"
mv "$DEST/panel-$DAY.sql.gz.part" "$DEST/panel-$DAY.sql.gz"
cp /var/www/pterodactyl/.env "$DEST/panel-$DAY.env"
tar -czf "$DEST/panel-files-$DAY.tar.gz" -C /var/www/pterodactyl .
find "$DEST" -type f -mtime +14 -delete
--single-transaction dumps InnoDB tables in a consistent state "without blocking any applications" (mariadb-dump). With pipefail, a failed dump stops the script before find deletes old files, and -mtime +14 only matches files at least 15 days old, as find ignores fractions of a day. umask 077 keeps the files, which hold your key and passwords, readable by root only. Root logs in to MariaDB over the local socket without a password; on MySQL, use mysqldump with the same options.
Run it once and check the dump:
sudo chmod 700 /usr/local/sbin/pterodactyl-panel-backup
sudo /usr/local/sbin/pterodactyl-panel-backup
sudo zcat /var/backups/pterodactyl/panel-$(date +%F).sql.gz | tail -n 1
The last line must start with -- Dump completed on, which mariadb-dump writes at the end of a finished dump. Then add it to root's crontab with sudo crontab -e:
15 3 * * * /usr/local/sbin/pterodactyl-panel-backup >> /var/log/pterodactyl-panel-backup.log 2>&1
Step 3: Give Each Server a Backup Limit
New servers get a Backup Limit of 0, and their Backups tab only says "Backups cannot be created for this server because the backup limit is set to 0." Raise it in the admin area under Servers, the server, Build Configuration, Application Feature Limits.
A backup is a gzipped tar of the server's folder, made while the server runs. It skips what .pteroignore in the server root lists, in gitignore syntax, unless the user types paths in the Ignored Files & Directories field. Databases from the server's Databases tab are not in it: the panel creates them on your database host, named s<server id>_<name>.
Two limits trip people up: a server can start only 2 backups per 600 seconds, deleted ones included (BACKUP_THROTTLE_LIMIT and BACKUP_THROTTLE_PERIOD in .env change that), and at the Backup Limit the Create backup button disappears. The table near the end lists the messages you get.
On each node, the backups: block under system: in /etc/pterodactyl/config.yml sets compression_level (best_speed, gzip level 1, by default; best_compression is level 9; or none) and write_limit, a cap in MiB/s for backup and restore writes (0 is no cap). After a change, run sudo systemctl restart wings; the upgrade guide says running servers "will not be affected".
Step 4: Send the Archives to S3
With the default wings driver, archives land in /var/lib/pterodactyl/backups on the node that runs the server, so one failed disk takes the server and its backups. The S3 driver uploads them to AWS S3 or S3-compatible storage instead. Per the backup docs, set this in the panel's .env:
APP_BACKUP_DRIVER=s3
AWS_DEFAULT_REGION=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_BACKUPS_BUCKET=
AWS_ENDPOINT=
Add AWS_USE_PATH_STYLE_ENDPOINT=true if your storage expects domain.com/bucket instead of bucket.domain.com. Then, in /var/www/pterodactyl, run sudo -u www-data php artisan config:clear and sudo -u www-data php artisan queue:restart: scheduled backups run in the queue worker, and Laravel's queue docs note that workers keep the booted application in memory until restarted.
The panel hands Wings presigned upload URLs, so the S3 keys live only in .env. Wings still writes each archive to its backup folder first and deletes it after the upload, so the node needs room for the largest backup. Old backups keep their driver: users can still download and delete them after a switch, and moving from S3 back to local means keeping the S3 keys in .env.
One trap if your storage runs on your own network: since Wings 1.13.1, a restore refuses to download from private, loopback and other internal addresses until you add the host name, IP or CIDR range to restore_host_allowlist in that backups: block and restart Wings.
Step 5: Schedule the Backups
In the server's Schedules tab, click Create schedule (for example Minute 0, Hour 4). Only When Server Is Online is on by default, so a stopped server skips the whole schedule; turn it off if stopped servers need backups too. Then add a New Task with the action Create backup, and leave its Ignored Files empty to use .pteroignore. The task form spells out the rotation: "If you have reached your backup limit, the oldest backup will be rotated." So the Backup Limit is your retention: a limit of 7 with a daily schedule keeps a week. Locked backups are never rotated, and if every backup is locked, the task fails.
For Minecraft Java, make the first task Send command save-all flush, which saves all chunks at once (Minecraft Wiki), and give the backup task a time offset of a few seconds (0 to 900 allowed). Tick Continue on Failure on the command task: on a stopped server the command fails, and the backup would never run. Skip save-off in schedules: a backup task that fails, for example at the throttle, ends the schedule, so a closing save-on never runs and saving stays off until the next restart.
Schedules run through the panel's cron line and its queue worker, pteroq; if scheduled backups never appear, check both.
Step 6: Copy Everything to Another Machine
A backup on the machine it protects dies with that machine. Copy the panel dumps, each node's config.yml and, with the wings driver, the archives elsewhere with rsync over SSH (sudo apt install rsync on both ends). On the panel host, append this to the cron line from step 2:
&& rsync -a /var/backups/pterodactyl/ backup@backup.example.com:pterodactyl/panel/
And in root's crontab on each node:
0 5 * * * rsync -a /etc/pterodactyl/config.yml /var/lib/pterodactyl/backups backup@backup.example.com:pterodactyl/node1/
Both run as root, so root needs an SSH key the backup user accepts; create the target folders on the backup machine first. Then connect once from each machine with sudo ssh backup@backup.example.com and accept the host key: by default SSH adds a new host key only after you confirm it (ssh_config), which cron cannot do. Leave out --delete: it removes files on the receiving side that are gone from the source, so every rotated backup would vanish from your copy too. Prune the backup machine with find instead.
Why config.yml? Besides the node's uuid, token_id and token, it holds everything you tuned: Wings rewrites it with all its settings at startup, while the panel can only print its own part again (the node's Configuration tab, or php artisan p:node:configuration <id>). To also cover servers without panel backups, add /var/lib/pterodactyl/volumes to the node line. Like a panel backup, it reads files while games write them. It also includes the node's SFTP host key, .sftp/id_ed25519; restore it on a rebuilt node, or clients warn that the key changed (step 9 of our SFTP guide).
On each database host, dump the server databases too:
DBS=$(sudo mariadb -N -e "SHOW DATABASES" | grep -E '^s[0-9]+_')
sudo mariadb-dump --single-transaction --routines --triggers --databases $DBS | gzip > servers-$(date +%F).sql.gz
Step 7: Test the Restore on a Separate VPS
Pterodactyl's guide to testing an upgrade describes a safe copy that doubles as a restore drill: a separate machine with the same panel version, its own Redis, and the queue worker and cron entry kept stopped. The copy holds real node addresses and credentials, so before you sign in on it or start its queue worker, block it from your Wings nodes, database hosts, mail server, backup storage and webhooks. A small Space-Node VPS with full root access works, and you cancel it from the client area afterwards.
Install the dependencies from the install guide (PHP and its extensions, MariaDB, Redis), then:
sudo mkdir -p /var/www/pterodactyl
sudo tar -xzf panel-files-2026-10-30.tar.gz -C /var/www/pterodactyl
sudo chown -R www-data:www-data /var/www/pterodactyl/*
sudo grep DB_PASSWORD /var/www/pterodactyl/.env
sudo mariadb -e "CREATE DATABASE panel; CREATE USER 'pterodactyl'@'127.0.0.1' IDENTIFIED BY 'PASSWORD_FROM_ENV'; GRANT ALL PRIVILEGES ON panel.* TO 'pterodactyl'@'127.0.0.1' WITH GRANT OPTION;"
sudo zcat panel-2026-10-30.sql.gz | sudo mariadb panel
Skip key:generate, p:environment:setup, migrate --seed and p:user:make: the data and the key come from your backup. Then check:
cd /var/www/pterodactyl
sudo -u www-data php artisan p:info
sudo -u www-data php artisan p:node:list
sudo -u www-data php artisan p:node:configuration 1
p:info shows the panel version and database settings, and p:node:list your nodes with their IDs. The last command decrypts the token of node 1; compare it with the token: line in that node's config.yml. If it stops with The MAC is invalid., this .env holds a different APP_KEY from the one that encrypted your data (troubleshooting page). Then compare row counts with the live panel, as the testing guide suggests:
sudo mariadb panel -e "SELECT (SELECT COUNT(*) FROM users) users, (SELECT COUNT(*) FROM servers) servers, (SELECT COUNT(*) FROM eggs) eggs, (SELECT COUNT(*) FROM nodes) nodes, (SELECT COUNT(*) FROM allocations) allocations, (SELECT COUNT(*) FROM schedules) schedules, (SELECT COUNT(*) FROM backups) backups, (SELECT COUNT(*) FROM api_keys) api_keys;"
For the server files, take one archive from your off-site copy, compare it with the checksum the panel shows under that backup (sha1: and a hash), and list it:
sudo sha1sum BACKUP_UUID.tar.gz
sudo tar -tzf BACKUP_UUID.tar.gz | head
Finally, restore a backup in the panel on a server that doesn't matter. Restore stops the server, and the option "Delete all files before restoring backup" empties its folder first. Time every step: that's your real recovery time.
When a Backup or Restore Fails
| Message | Cause | Fix |
|---|---|---|
Backups cannot be created for this server because the backup limit is set to 0. | The default limit | Raise Backup Limit (step 3) |
Cannot create a new backup, this server has reached its limit of 3 backups. | At the limit, with no backup that may be rotated | Delete a backup or raise the limit |
Only 2 backups may be generated within a 600 second span of time. | The throttle | Wait, or change BACKUP_THROTTLE_* |
| Backup turns failed after 6 hours | The panel fails backups that never finish after BACKUP_PRUNE_AGE (360 minutes) | Read /var/log/pterodactyl/wings.log on the node |
The provided backup link resolves to a blocked address. | S3 storage on an internal address | restore_host_allowlist (step 4) |
The MAC is invalid. | A different APP_KEY | Restore the original .env |
A node that shows offline after you restore its config.yml? Our Wings node offline guide has the checks, and our Pterodactyl setup guide covers a rebuild from scratch.
FAQ
Where does Pterodactyl store server backups?
With the default wings driver, on the node in /var/lib/pterodactyl/backups as <backup uuid>.tar.gz (backup_directory in config.yml moves it). With S3, in your bucket as <server uuid>/<backup uuid>.tar.gz.
Do Pterodactyl backups include a server's MySQL databases? No. Wings archives only the server's folder, while those databases live on your database host. Dump them there (step 6).
Can I restore the panel database without APP_KEY? The data loads, but node tokens, database passwords and everything else the panel encrypted stay unreadable, so the panel cannot reach its nodes. Keep the key in a password manager.
What do I back up before upgrading to Pterodactyl 2.0?
The 2.0 upgrade guide (2.0 is in open beta) puts the panel in maintenance mode, stops pteroq, makes the same three backups as step 2 and keeps your 1.x folder for a rollback. Your .env works unchanged, and the guide says not to run php artisan key:generate.
Are backups included with a Space-Node VPS? Not in the plan price of our VPS hosting, but you can arrange them through a support ticket. Keep your own off-site copies from step 6 either way.
Sources
All opened on 30 October 2026:
- Pterodactyl docs: Getting Started, Additional Configuration, Troubleshooting, Updating the Panel, Installing Wings, Wings configuration, Upgrading Wings, Upgrading From 1.x, Testing the Upgrade
- Panel 1.15.2 source: backups.php, InitiateBackupService.php, TooManyBackupsException.php, BackupRepository.php, DeleteBackupService.php, BackupController.php, BackupRemoteUploadController.php, BackupStatusController.php, RunTaskJob.php, ProcessScheduleService.php, Task.php, DaemonCommandRepository.php, Kernel.php, PruneOrphanedBackupsCommand.php, Node.php, DaemonAuthenticate.php, InfoCommand.php, NodeListCommand.php, NodeConfigurationCommand.php, DatabaseManagementService.php, config/app.php, new server form, Build Configuration, Backups tab, backup form, backup menu, backup row, schedule form, task form, releases
- Wings 1.13.4 source: config.go, cmd/root.go, server/backup.go, backup/backup.go, backup_local.go, backup_s3.go, archive.go, router_server_backup.go, router_server.go, address.go, allowlist.go, sftp/server.go, 1.13.1 restore check, releases
- Laravel: Encrypter.php, Queues
- MariaDB: mariadb-dump, mariadb client, authentication from MariaDB 10.4
- Minecraft Wiki: save commands
- Man pages: rsync(1), ssh_config(5), find(1), crontab(5), tar(1), gzip(1), install(1), sha1sum(1), sudo(8), bash(1)