Skip to content
Current coupon:10% offUse it in your order

Pterodactyl Panel: complete installation on your own VPS

Install Pterodactyl Panel on Ubuntu 24.04 or Debian 13: PHP 8.3, MariaDB, Redis, Nginx with HTTPS, the queue worker, cron and the first admin. Every step and option explained.

  • expert
  • ~90 min
  • Updated 2 Oct 2026

We'll install Pterodactyl Panel, a web interface for managing game servers, on your VPS. When we're done, it will run at https://panel.example.cz with a Let's Encrypt certificate, a job queue, cron and your admin account.

What you'll need

  • A StormHost VPS with a fresh installation of Ubuntu 24.04 or Debian 13
  • A domain or subdomain for the panel (panel.example.cz in this guide) and access to its DNS settings
  • An SSH connection as root (guide)
  • Basic terminal skills: editing files in the nano editor, copying commands
  • About 90 minutes

How it works#

Pterodactyl has two parts. The Panel is a web application (PHP, Laravel framework) where you create users and servers and where players control the console. Wings is the service that actually runs the game servers in Docker containers. Panel and Wings can run on the same VPS. This guide installs the Panel; you'll add Wings in the follow-up guide Pterodactyl Wings.

The Panel needs several other programs:

ComponentWhat the Panel needs it for
PHP 8.3 + PHP-FPMThe Panel is written in PHP. PHP-FPM is the process that runs PHP code for the web server.
MariaDBThe database. Stores users, servers, settings and game templates.
RedisFast in-memory storage for the cache, logins (sessions) and the job queue.
NginxThe web server. Accepts requests from the browser and passes them to PHP-FPM.
ComposerThe PHP library manager. Downloads the libraries the Panel needs.
Cron and the pteroq serviceCron runs the Panel's scheduler every minute; pteroq processes the job queue in the background (emails, scheduled server tasks).

The Panel itself is lightweight. If game servers will run on the same VPS, plan your RAM mainly around them and add a reserve for the system, the database and the Panel (roughly 1–⁠2 GB).

Do you really need your own panel?

Pterodactyl pays off when you want to run several game servers on one VPS, manage them through the web and give other people access. If one game server is enough for you, you can order it directly as a game server and manage it in the customer area without installing anything.

Installation plan#

PhaseWhat you'll do
1. DomainPoint panel.example.cz at the VPS IP address.
2. FirewallOpen ports 80 and 443.
3. DependenciesInstall PHP 8.3, MariaDB, Redis, Nginx and Composer.
4. PanelDownload the Panel files, create the database and set up .env.
5. Database and adminCreate the tables and the first administrator.
6. BackgroundSet up cron and the pteroq queue service.
7. HTTPS and NginxGet a certificate and configure the web server.
8. CheckLog in and verify that everything runs.

Domain and DNS#

The Panel needs its own domain or subdomain. HTTPS certificates are issued for domains, not for IP addresses.

  1. In the customer area, open My services, click your VPS and copy the IPv4 address (and optionally the IPv6 address) in the Network tab.

  2. At your DNS provider, create an A record for panel.example.cz with the VPS's IPv4 address (for example 203.0.113.10).

  3. Optionally, also create an AAAA record with the VPS's IPv6 address (for example 2001:db8::10). The Nginx configuration below listens on IPv6 too.

  4. On the VPS, check that the domain already points to the right place:

    bash
    getent hosts panel.example.cz
    • getent hosts – asks the system DNS for the domain's address and prints it.

    You'll see a line with your VPS's IP address. If nothing is printed, wait a few minutes (depending on the record's TTL) and try again.

Cloudflare: gray cloud only during installation

If your domain is managed in Cloudflare, keep the record set to DNS only (gray cloud). That way you can verify the certificate and the first login directly. You can switch to Proxied (orange cloud) at the end; see Panel behind Cloudflare. Cloudflare setup: Domain on a VPS and Cloudflare.

Firewall#

The browser connects to the Panel on port 443 (HTTPS). Port 80 (HTTP) is needed for domain validation when issuing the certificate and for redirecting to HTTPS.

  • Firewall in the customer area: if inbound traffic is set to Block everything except allowed, add the rules Inbound · Allow · TCP · port 80,443. The SSH rule (port 22) must stay. Guide: Firewall in the customer area.

  • UFW on the VPS: if you use it, allow the ports there too:

    bash
    ufw allow 80/tcp
    ufw allow 443/tcp
    • ufw allow 80/tcp – allows incoming TCP connections to port 80.
    • ufw allow 443/tcp – the same for port 443.

You'll open the ports for Wings (8080 and 2022) and for game servers in the Pterodactyl Wings guide.

Installing dependencies#

First update the system so you install on an up-to-date base:

bash
apt update && apt -y upgrade
  • apt update – downloads the current package list.
  • apt -y upgrade – installs available updates; -y answers “yes” to the confirmation.

Then install the dependencies. The steps depend on the system: Ubuntu 24.04 has PHP 8.3 directly in its repository, while Debian 13's default repository has PHP 8.4, which the Panel doesn't support (the current Panel requires PHP 8.2 or 8.3). On Debian you'll therefore add the packages.sury.org repository, which the official Pterodactyl documentation recommends as well.

Ubuntu 24.04

bash
apt -y install php8.3 php8.3-{common,cli,gd,mysql,mbstring,bcmath,xml,fpm,curl,zip} mariadb-server nginx tar unzip git redis-server curl

Don't add any PHP repository on Ubuntu 24.04. The official documentation only mentions the ppa:ondrej/php repository for the older Ubuntu 22.04.

Debian 13

First add the repository with PHP 8.3:

bash
apt -y install lsb-release ca-certificates curl
curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
dpkg -i /tmp/debsuryorg-archive-keyring.deb
echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
apt update
  • apt -y install lsb-release ca-certificates curl – a tool for finding the system's release name, certificates for HTTPS and the curl download tool.
  • curl -sSLo … debsuryorg-archive-keyring.deb – downloads the package with the repository's signing key (-s silent, -S show errors, -L follow redirects, -o save to a file).
  • dpkg -i … – installs the key into /usr/share/keyrings/; apt then uses it to verify packages from the repository.
  • echo "deb …" > /etc/apt/sources.list.d/php.list – writes the repository address. $(lsb_release -sc) is replaced with the system's codename (trixie).
  • apt update – loads the packages from the new repository.

Then install the dependencies:

bash
apt -y install php8.3 php8.3-{common,cli,gd,mysql,mbstring,bcmath,xml,fpm,curl,zip} mariadb-server nginx tar unzip git redis-server

On Debian 13, install MariaDB and Redis from the default repository; they don't need any extra repository.

What the install command contains:

PackageWhat it's for
php8.3, php8.3-cliPHP 8.3 and its command line (for the php artisan commands).
php8.3-fpmThe process Nginx uses to run PHP. Listens on the socket /run/php/php8.3-fpm.sock.
php8.3-common, -gd, -mysql, -mbstring, -bcmath, -xml, -curl, -zipPHP extensions the Panel requires: images, MariaDB connections, UTF-⁠8 text handling, precise math, XML, HTTP requests and ZIP archives. The php8.3-{common,cli,…} notation is a bash shortcut that expands to php8.3-common php8.3-cli ….
mariadb-serverThe database server.
nginxThe web server.
redis-serverRedis.
tar, unzip, git, curlUnpacking archives and downloading; Composer uses them when installing libraries.

Check that you have the right PHP version:

bash
php -v
Outputtext
PHP 8.3.x (cli) …

The first line must say PHP 8.3. If you see PHP 8.4, see Troubleshooting.

Check that the services are running:

bash
systemctl is-active mariadb redis-server nginx php8.3-fpm
  • systemctl is-active – prints the state of each listed service.

You'll see active four times. If any of them is inactive, start it and enable it at boot with systemctl enable --now <SERVICE_NAME>, for example systemctl enable --now redis-server.

Composer#

Composer downloads the Panel's PHP libraries. Install it with the official installer:

bash
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  • curl -sS https://getcomposer.org/installer – downloads the Composer install script.
  • | php -- – passes the script to PHP to run; -- separates PHP's parameters from the installer's.
  • --install-dir=/usr/local/bin – where Composer gets installed (a directory where the system looks for programs).
  • --filename=composer – the program name, so you then run it with the composer command.

Check: composer --version prints Composer version 2.…. As root you'll also see the warning Do not run Composer as root/super user! and possibly the question Continue as root/super user [yes]?. Confirm it with Enter. During the Panel installation below, the COMPOSER_ALLOW_SUPERUSER=1 variable suppresses the warning.

Downloading the Panel#

  1. Create a directory for the Panel and switch to it:

    bash
    mkdir -p /var/www/pterodactyl
    cd /var/www/pterodactyl
    • mkdir -p – creates the directory (including its parents); if it already exists, it doesn't print an error.
    • cd – switches to the directory. Run all further php artisan commands in it.
  2. Download and unpack the latest Panel release:

    bash
    curl -Lo panel.tar.gz https://github.com/pterodactyl/panel/releases/latest/download/panel.tar.gz
    tar -xzvf panel.tar.gz
    • curl -Lo panel.tar.gz … – downloads the latest release archive from GitHub and saves it as panel.tar.gz.
    • tar -xzvf – unpacks the archive (x extract, z gzip format, v list files, f from a file).
  3. Set permissions on the directories the Panel writes to:

    bash
    chmod -R 755 storage/* bootstrap/cache/
    • chmod -R 755 – the owner can read, write and enter the directories; everyone else can only read and enter them. -R applies to all contents.
    • storage/ – logs, cache and uploaded files; bootstrap/cache/ – the compiled configuration.

A database for the Panel#

The Panel needs its own database and its own user. The Pterodactyl authors explicitly forbid using the database user root for the Panel: if someone compromised the Panel, they would get access to all databases on the VPS.

  1. Generate a strong password for the database user, for example with the password generator. Use only letters and digits (at least 24 characters) so you don't have to deal with special characters in SQL and in .env. Save the password; you'll need it again shortly.

  2. Log in to MariaDB as the administrator:

    bash
    mariadb
    • mariadb – the database client. On both Ubuntu and Debian, the system root user logs in to MariaDB without a password (via unix socket authentication). You'll see the MariaDB [(none)]> prompt.
  3. Create the user and the database. Replace <PANEL_DB_PASSWORD> with the password from step 1 (keep the single quotes around it):

    SQL
    CREATE USER 'pterodactyl'@'127.0.0.1' IDENTIFIED BY '<PANEL_DB_PASSWORD>';
    CREATE DATABASE panel;
    GRANT ALL PRIVILEGES ON panel.* TO 'pterodactyl'@'127.0.0.1';
    exit
LineWhat it does
CREATE USER 'pterodactyl'@'127.0.0.1' …Creates the user pterodactyl, who may only log in from 127.0.0.1, that is, only from this VPS over TCP. Nobody can connect with this account from outside.
IDENTIFIED BY '…'Sets the user's password.
CREATE DATABASE panel;Creates an empty panel database.
GRANT ALL PRIVILEGES ON panel.* …Gives the user all privileges, but only on the panel database (panel.* = all its tables). It has no access to other databases.
exitExits the MariaDB client.

Why 127.0.0.1 and not localhost

The Panel connects to the database over TCP at 127.0.0.1. The Pterodactyl authors explicitly advise against using localhost, because PHP then tries a socket connection, which causes problems. So the database user must allow host 127.0.0.1.

Environment configuration (.env)#

The Panel's settings live in /var/www/pterodactyl/.env. You'll create it from a template, install the libraries and generate an encryption key.

  1. Copy the template:

    bash
    cp .env.example .env
  2. Install the Panel's PHP libraries:

    bash
    COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader
    • COMPOSER_ALLOW_SUPERUSER=1 – lets Composer run as root without asking.
    • composer install – downloads the libraries in exactly the versions the Panel specifies (the composer.lock file).
    • --no-dev – skips developer tools you don't need on a production server.
    • --optimize-autoloader – prepares faster loading of PHP classes.

    The installation takes a few minutes and finishes without error messages (a yellow warning about deprecated packages is fine).

  3. Generate the encryption key:

    bash
    php artisan key:generate --force
    • php artisan – the Panel's command-line management tool.
    • key:generate – creates a random APP_KEY and writes it to .env.
    • --force – runs the command without confirmation (the Panel runs in production mode, where it would otherwise ask).

Back up APP_KEY right now

The Panel uses APP_KEY to encrypt sensitive data in the database (for example API keys and database host passwords). If you lose it, you can't decrypt the data, not even from a database backup. Only run key:generate on the first installation. On a Panel that already has data, a new key would mean losing access to it.

  1. Display the key and save the whole line somewhere off the server (in a password manager or an encrypted file on your computer):

    bash
    grep APP_KEY /var/www/pterodactyl/.env
    Outputtext
    APP_KEY=base64:…

The p:environment:setup wizard#

A wizard takes care of the basic setup. Run it:

bash
php artisan p:environment:setup

It asks the following questions in turn (the default value is in square brackets; confirm it with Enter):

QuestionWhat to enterWhy
Author email (… Author Email)Your email, for example [email protected]Used as the author of game templates you export from the Panel. It has to be a valid email format, otherwise the wizard fails.
Application URLhttps://panel.example.czThe Panel's address including https://. Used for links in emails and when generating URLs. Without https:// the links will be wrong.
Application TimezoneEurope/PragueThe time zone for displaying times and for scheduled tasks.
Cache DriverredisWhere to store the cache. Redis is the fastest and the recommended option.
Session DriverredisWhere to store user logins.
Queue DriverredisWhere to store the job queue for the pteroq service.
Enable UI based settings editor?yesSome settings (Panel name, mail, reCAPTCHA) can then be changed in the admin area, not just in .env.
Enable sending anonymous telemetry data?your callOnce a day the Panel sends anonymous statistics to its authors (number of servers, system version). If you don't want that, answer no.
Redis Host127.0.0.1Redis runs on the same VPS.
Redis Passwordnothing, just EnterA local Redis has no password after installation and only listens on 127.0.0.1.
Redis Port6379The default Redis port.

When you enter an address starting with https://, the wizard turns on secure cookies by itself (SESSION_SECURE_COOKIE=true).

The p:environment:database wizard#

bash
php artisan p:environment:database
QuestionWhat to enterWhy
Database Host127.0.0.1The database runs on the same VPS. Don't enter localhost (see above).
Database Port3306The default MariaDB port.
Database NamepanelThe database from the previous section.
Database UsernamepterodactylThe user from the previous section.
Database Passwordthe <PANEL_DB_PASSWORD> passwordIt isn't shown while typing.

The wizard tries to connect right away. If the details are wrong, it prints Unable to connect to the MySQL server…, saves nothing and asks whether you want to try again.

The p:environment:mail wizard#

The Panel sends emails (invitations for new users, password resets). Without mail settings the Panel works, but these emails won't go out. You can also set this up later with the same command:

bash
php artisan p:environment:mail
QuestionWhat to enterWhy
Which driver should be used for sending emails?smtpSending through your email provider's SMTP server is the most reliable.
SMTP Hostthe SMTP server address, for example smtp.example.czYou'll find it in your email provider's help pages.
SMTP Port587 (or 465)587 is used with TLS encryption (STARTTLS), 465 with SSL encryption.
SMTP Usernamethe mailbox's full email addressThe mailbox login name.
SMTP Passwordthe mailbox passwordSome services require a separate “app password”.
Encryption method to usetls for port 587, ssl for 465Must match the port, otherwise the connection fails.
Email address emails should originate fromfor example [email protected]The sender address. It should belong to the mailbox's domain, otherwise messages end up in spam.
Name that emails should appear fromfor example Game PanelThe sender name shown in emails.

Important keys in .env#

The wizards write these values to .env. You can also edit the file by hand (nano /var/www/pterodactyl/.env); after a manual edit, run php artisan config:clear.

SettingWhat it doesRecommended valueWhen to change
APP_URLThe Panel's public address.https://panel.example.czWhen the domain changes (then also in Nginx and the certificate).
APP_KEYThe encryption key for data in the database.generated, never change itNever. When restoring from a backup, put the original back.
APP_TIMEZONEThe Panel's time zone.Europe/PragueWhen the admins are in a different time zone.
APP_ENVIRONMENT_ONLYtrue = settings only in .env, false = also in the admin area.falseWhen you want to prevent settings changes from the web.
DB_HOST, DB_PORTWhere the Panel's database runs.127.0.0.1, 3306When you move the database elsewhere.
DB_DATABASE, DB_USERNAME, DB_PASSWORDThe Panel's database and credentials.panel, pterodactyl, a strong passwordWhen you change the user's password in MariaDB.
REDIS_HOST, REDIS_PASSWORD, REDIS_PORTThe Redis connection.127.0.0.1, null, 6379When you set a Redis password.
CACHE_DRIVER, SESSION_DRIVER, QUEUE_CONNECTIONWhere the cache, logins and queue are stored.redisPractically never.
SESSION_SECURE_COOKIELogin cookies are only sent over HTTPS.trueNever, as long as the Panel runs on HTTPS.
MAIL_*Sending email (server, port, encryption, sender).depends on the providerWhen you change the mailbox.
MAIL_EHLO_DOMAINThe name the Panel introduces itself with to the SMTP server.panel.example.czWhen, for example, Gmail rejects your emails because of localhost.
TRUSTED_PROXIESProxy addresses the Panel trusts (Cloudflare, another Nginx).emptyWhen the Panel runs behind a proxy; see Panel behind Cloudflare.
PTERODACTYL_TELEMETRY_ENABLEDSending anonymous telemetry.your callfalse turns telemetry off.

Database tables and the first administrator#

  1. Create the tables and load the default data:

    bash
    php artisan migrate --seed --force
    • migrate – creates all tables in the panel database.
    • --seed – fills them with default data, including game templates (Minecraft, Rust and others).
    • --force – runs the command without confirmation in production mode.

    The command can take several minutes. Don't interrupt it, otherwise the database stays half-created.

  2. Create the admin account:

    bash
    php artisan p:user:make
QuestionWhat to enter
Is this user an administrator?yes – the account gets access to the admin area.
Email AddressYour email; used for logging in and password resets.
UsernameThe login name, for example jan.
First Name, Last NameFirst and last name (shown in the Panel).
PasswordAt least 8 characters, at least one uppercase letter and one digit. It isn't shown while typing.

At the end you'll see a table with the account details and the row Admin | Yes.

File permissions#

The Panel files now belong to root. The web server runs as the www-data user and has to write to the storage and bootstrap/cache directories:

bash
chown -R www-data:www-data /var/www/pterodactyl/*
  • chown -R – changes the owner of the files and the entire contents of directories.
  • www-data:www-data – the user and group Nginx and PHP-FPM run as on Ubuntu and Debian.

After every artisan command run as root

php artisan commands run as root can create files (for example logs) that www-data then can't overwrite. If you get a 500 error after working in the terminal, run the chown command above again.

Cron and the job queue#

Cron#

Cron runs the Panel's scheduler every minute (cleaning up old logins, scheduled server tasks).

  1. Open root's crontab:

    bash
    crontab -e

    On the first run it asks which editor to use. Choose nano (type its number and press Enter). If the system says crontab: command not found, cron is missing. Install it with apt -y install cron and repeat this step.

  2. Add this line at the end of the file:

    text
    * * * * * php /var/www/pterodactyl/artisan schedule:run >> /dev/null 2>&1
  3. Save (Ctrl+O, Enter) and close the editor (Ctrl+X). You'll see crontab: installing new crontab.

Part of the lineMeaning
* * * * *Minute, hour, day of month, month, day of week. Five asterisks = every minute.
php /var/www/pterodactyl/artisan schedule:runRuns the Panel's scheduler, which decides by itself which tasks are due.
>> /dev/null 2>&1Discards output and errors so cron doesn't send an email every minute.

The pteroq queue service#

The queue handles background work: sending emails and the tasks users schedule for their servers (the Schedules section, for example a nightly restart or backup). Without it, emails don't go out and scheduled tasks don't run.

  1. Create the service file:

    bash
    nano /etc/systemd/system/pteroq.service
  2. Paste the content:

    /etc/systemd/system/pteroq.serviceini
    # Pterodactyl Queue Worker File
    [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
  3. Save and close the editor.

LineWhat it does
Description=The service description you'll see in systemctl status.
After=redis-server.serviceThe service starts only after Redis, which it reads the queue from.
User=www-data, Group=www-dataThe queue runs as the same user as the website, so both have the same file permissions.
Restart=alwaysWhen the process exits (with an error or normally), systemd starts it again.
ExecStart=… queue:workStarts processing the queue. --queue=high,standard,low sets the queue order by priority, --sleep=3 = wait 3 seconds when the queue is empty, --tries=3 = retry a failed job at most three times.
StartLimitInterval=180, StartLimitBurst=30If the service crashes more than 30 times in 180 seconds, systemd stops restarting it (protection against an endless loop).
RestartSec=5sWaits 5 seconds before restarting.
WantedBy=multi-user.targetThe service starts at every VPS boot (after enable).
  1. Enable Redis and the queue at boot and start them right away:

    bash
    systemctl enable --now redis-server
    systemctl enable --now pteroq.service
    • enable – turns on automatic start after a VPS reboot.
    • --now – also starts the service immediately.
  2. Check the status:

    bash
    systemctl status pteroq

    You'll see Active: active (running). Close the output with q.

The Let's Encrypt certificate#

The Nginx configuration below refers to the certificate files. Nginx won't start with it until the certificate exists, so you get the certificate first.

  1. Install Certbot and its Nginx plugin:

    bash
    apt -y install certbot python3-certbot-nginx
    • certbot – the program that gets and renews Let's Encrypt certificates.
    • python3-certbot-nginx – a plugin that verifies the domain through the running Nginx without stopping it.
  2. Get the certificate. Replace panel.example.cz with your domain:

    bash
    certbot certonly --nginx -d panel.example.cz
    • certonly – only gets the certificate and saves it, without editing the Nginx configuration (you'll write that yourself).
    • --nginx – domain validation goes through Nginx on port 80.
    • -d panel.example.cz – the domain you want the certificate for.
  3. On the first run, answer the questions:

    • Email (Enter email address…) – enter your email. Let's Encrypt sends important account notices there. Since 2025 it no longer sends certificate expiry reminders, so renewal is handled automatically (see step 5).
    • Terms of Service (Please read the Terms of Service… Do you agree?) – answer Y; without agreeing you won't get a certificate.
    • Sharing your email with the EFF (Would you be willing… to share your email address with the Electronic Frontier Foundation…) – your call; it has no effect on the certificate.

    At the end you'll see Successfully received certificate. and the file paths:

    Outputtext
    Certificate is saved at: /etc/letsencrypt/live/panel.example.cz/fullchain.pem
    Key is saved at:         /etc/letsencrypt/live/panel.example.cz/privkey.pem
  4. Make Nginx load the new certificate after every renewal. Create a deploy hook (a script Certbot runs after a successful renewal):

    bash
    cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'EOF'
    #!/bin/sh
    systemctl reload nginx
    EOF
    chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
    • cat > … <<'EOF' … EOF – writes the lines between <<'EOF' and EOF to the file.
    • systemctl reload nginx – loads the new configuration and certificates without dropping connections.
    • chmod 755 – makes the script executable; Certbot only runs executable files in the renewal-hooks/deploy directory.
  5. Check that automatic renewal works. On both Ubuntu and Debian, the Certbot package includes a timer that tries renewal twice a day:

    bash
    systemctl list-timers | grep certbot
    certbot renew --dry-run
    • systemctl list-timers – lists scheduled timers; certbot.timer should be among them.
    • certbot renew --dry-run – tries a renewal against the Let's Encrypt staging server without touching the real certificate.

    The test ends with Congratulations, all simulated renewals succeeded.

The certificate is valid for 90 days (as of October 2026; Let's Encrypt is gradually shortening the validity). Certbot renews it by itself when roughly a third of its validity is left.

Nginx#

  1. Disable Nginx's default website:

    bash
    rm /etc/nginx/sites-enabled/default
    • This only deletes the link in sites-enabled; the file itself stays in sites-available. Otherwise the default website would serve port 80 instead of the Panel.
  2. Create the Panel configuration:

    bash
    nano /etc/nginx/sites-available/pterodactyl.conf
  3. Paste the content and replace all four occurrences of panel.example.cz with your domain:

    /etc/nginx/sites-available/pterodactyl.confnginx
    server {
        listen 80;
        listen [::]:80;
        server_name panel.example.cz;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name panel.example.cz;
        server_tokens off;
    
        root /var/www/pterodactyl/public;
        index index.php;
    
        access_log /var/log/nginx/pterodactyl.app-access.log;
        error_log  /var/log/nginx/pterodactyl.app-error.log error;
    
        # larger uploads and longer script run times
        client_max_body_size 100m;
        client_body_timeout 120s;
    
        sendfile off;
    
        ssl_certificate /etc/letsencrypt/live/panel.example.cz/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/panel.example.cz/privkey.pem;
        ssl_session_cache shared:SSL:10m;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
        ssl_prefer_server_ciphers on;
    
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Robots-Tag none;
        add_header Content-Security-Policy "frame-ancestors 'self'";
        add_header X-Frame-Options DENY;
        add_header Referrer-Policy same-origin;
    
        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }
    
        location ~ \.php$ {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param HTTP_PROXY "";
            fastcgi_intercept_errors off;
            fastcgi_buffer_size 16k;
            fastcgi_buffers 4 16k;
            fastcgi_connect_timeout 300;
            fastcgi_send_timeout 300;
            fastcgi_read_timeout 300;
        }
    
        location ~ /\.ht {
            deny all;
        }
    }

The configuration is based on the official Pterodactyl documentation. We added IPv6 listening (listen [::]:…) and hid the Nginx version. What each part does:

DirectiveWhat it doesWhen to change
server_tokens off;Nginx doesn't reveal its version in headers and on the Panel's error pages. On Debian 13 this is already the default in /etc/nginx/nginx.conf, on Ubuntu 24.04 it isn't.Never.
first server block with listen 80Accepts HTTP requests and permanently (301) redirects them to HTTPS.Never.
listen 443 ssl http2;Listens on port 443 with TLS and HTTP/2. [::] is the same for IPv6.Delete the lines with [::] if the VPS has no IPv6.
server_nameThe domain this block applies to.When the domain changes.
root /var/www/pterodactyl/public;The directory files are served from. Only public; the Panel's other files (including .env) aren't reachable from the web.Never.
index index.php;Which file to use when the URL ends with /.Never.
access_log, error_logWhere the access and error logs are written.Never.
client_max_body_size 100m;The maximum allowed upload size (for example through the Panel's file manager).When you need to upload larger files; also change upload_max_filesize and post_max_size below.
client_body_timeout 120s;How long Nginx waits for more data from the browser during an upload, so a slow upload of a large file isn't cut off.When uploading over a very slow connection.
sendfile off;Turns off sending files directly by the kernel. Taken from the official Pterodactyl configuration.Never.
ssl_certificate, ssl_certificate_keyPaths to the certificate and private key from Certbot.When the domain changes.
ssl_session_cache shared:SSL:10m;Memory (10 MB) for stored TLS sessions, so repeat browser connections are set up faster.Never.
ssl_protocols TLSv1.2 TLSv1.3;Allowed TLS versions; older and insecure ones are off.Never.
ssl_ciphers, ssl_prefer_server_ciphersAllowed ciphers and their order.Never.
add_header …Security headers: prevents embedding the Panel in other pages, blocks indexing by search engines and similar.Never.
location / + try_filesSends existing files directly and passes everything else to index.php (the application).Never.
location ~ \.php$Rules for all requests to .php files.Never.
fastcgi_pass unix:/run/php/php8.3-fpm.sock;How Nginx passes PHP requests to PHP-FPM.When switching to a different PHP version.
fastcgi_split_path_info, fastcgi_index, include fastcgi_params, fastcgi_param SCRIPT_FILENAMEStandard request handoff to PHP: splits the URL into the script and the rest of the path, sets the default script and passes the request variables to PHP, including the path to the script being run.Never.
fastcgi_intercept_errors off;Error pages come straight from the Panel; Nginx doesn't replace them with its own.Never.
fastcgi_buffer_size, fastcgi_buffersThe size of the memory Nginx uses to buffer responses from PHP.Never.
fastcgi_param PHP_VALUE …Raises PHP's upload limits to 100 MB.Together with client_max_body_size.
fastcgi_param HTTP_PROXY "";Protection against the “httpoxy” attack.Never.
fastcgi_*_timeout 300How long Nginx waits for PHP (slow operations).Never.
location ~ /\.ht + deny allBlocks access to hidden .ht… files.Never.

http2 warning on Debian 13

Debian 13 ships Nginx 1.26, where the listen … http2 syntax is marked as deprecated. The configuration test prints the warning the "listen ... http2" directive is deprecated. It doesn't affect anything. If the warning bothers you, on Debian 13 remove the word http2 from both listen 443… lines and add the line http2 on; below them. On Ubuntu 24.04 (Nginx 1.24) the http2 on; directive doesn't exist, so keep the syntax from the example there.

  1. Enable the configuration, test it and restart Nginx:

    bash
    ln -s /etc/nginx/sites-available/pterodactyl.conf /etc/nginx/sites-enabled/pterodactyl.conf
    nginx -t
    systemctl restart nginx
    • ln -s – creates a link to the file in sites-enabled; Nginx only loads configurations in sites-enabled.
    • nginx -t – checks the syntax. It must end with the line test is successful. If it doesn't, don't restart Nginx; fix the line the error points to.
    • systemctl restart nginx – loads the new configuration.

First login#

  1. Open https://panel.example.cz in your browser. You'll see the Panel's login page with a padlock (valid certificate) in the address bar.
  2. Log in with the admin's username or email and password.
  3. Click the gear icon (admin area) in the top right. In the Settings section, set the Panel name (Company Name).
  4. Turn on two-factor authentication: click your account in the top right, open Account and in the two-factor section scan the QR code with an app (for example Google Authenticator or Aegis). The admin account controls all servers, so protect it with more than a password.

Verification#

Check the whole installation:

bash
systemctl is-active nginx php8.3-fpm mariadb redis-server pteroq
curl -sI https://panel.example.cz | head -n 1
  • The first command must print active five times.
  • curl -sI downloads only the response headers; the first line will be HTTP/2 302 (redirect to the login) or HTTP/2 200.

The Panel is ready. The next step is adding a node with the Wings service, where the game servers will run: Pterodactyl Wings.

Panel behind Cloudflare#

You can put the Panel behind the Cloudflare proxy (orange cloud). This applies to the Panel only, not to Wings (see Pterodactyl Wings).

A node with the same domain as the Panel

If you entered the Panel's domain (panel.example.cz) as the node's FQDN in the Wings guide, don't put the Panel behind the Cloudflare proxy. Browsers couldn't connect to Wings on port 8080, and the server console and files wouldn't work. Either keep the Panel's domain on DNS only, or give the node its own subdomain (for example node.example.cz) with a gray cloud and its own certificate.

  1. In Cloudflare, set the SSL/TLS mode to Full (strict). Cloudflare then connects to the VPS over HTTPS and verifies your Let's Encrypt certificate.

  2. Switch the panel.example.cz record to Proxied.

  3. Add Cloudflare's IP ranges to .env on a single line. Behind the proxy, all requests reach the Panel from Cloudflare's addresses. The Panel then takes the visitor's real IP address from the X-Forwarded-For header, but only from addresses it trusts. Without this line, the Panel would only see Cloudflare's addresses for all users, both in activity logs and in the brute-force protection, which counts attempts per IP address. The list below is current as of October 1, 2026; you'll find the latest one at cloudflare.com/ips (opens in a new window):

    /var/www/pterodactyl/.envini
    TRUSTED_PROXIES=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32
    • TRUSTED_PROXIES – a list of addresses and ranges (comma-separated, no spaces) from which the Panel accepts X-Forwarded-* headers with the visitor's real IP address and protocol. It ignores these headers from any other address, so nobody can spoof them.
  4. Load the new configuration: cd /var/www/pterodactyl && php artisan config:clear.

Don't use Flexible mode

In Flexible mode, traffic between Cloudflare and the VPS goes unencrypted over HTTP, but Nginx redirects it to HTTPS. The result is an endless redirect loop (ERR_TOO_MANY_REDIRECTS). Use Full (strict).

Updating the Panel#

New Panel versions fix bugs and security holes, so update regularly. Before updating, read the release notes on GitHub and check which PHP version the new release requires (php -v).

Back up first

Before updating, back up the database and the .env file:

bash
mariadb-dump --single-transaction panel > /root/panel-$(date +%F).sql
cp /var/www/pterodactyl/.env /root/pterodactyl-env-$(date +%F).bak
  • mariadb-dump --single-transaction panel – exports the panel database to an SQL file without locking tables.
  • $(date +%F) – inserts today's date (for example 2026-10-01) into the file name.

Then run the update:

bash
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
CommandWhat it does
php artisan downTurns on maintenance mode; users see a maintenance page instead of errors during the update.
curl -L … | tar -xzvDownloads the latest release and unpacks it straight over the existing files. .env isn't overwritten; it isn't in the archive.
chmod -R 755 …Restores write permissions on the directories.
composer install …Installs the libraries for the new version.
view:clear, config:clearClears compiled templates and the configuration cache so the new ones are loaded.
migrate --seed --forceUpdates the tables for the new version and refreshes the default game templates. Your own edits to default templates are overwritten; if you change a template, make a copy of it.
chown -R www-data:www-data …Gives ownership of the files back to the web server.
queue:restartTells the pteroq service to load the new code after finishing its current job.
php artisan upTurns off maintenance mode.

After updating the Panel, update Wings too; the steps are in the Pterodactyl Wings guide. Panel and Wings versions should go hand in hand, so use the latest release of both.

Panel backups#

To restore the Panel you need two things: the panel database and the .env file (above all APP_KEY). Game server data lives on the Wings node, not in the Panel, so back it up separately. How to automate backups and get them off the server is covered in Backups on a VPS.

Troubleshooting#

502 Bad Gateway#

Nginx couldn't reach PHP-FPM. Check that PHP-FPM is running and that the socket path matches:

bash
systemctl status php8.3-fpm
ls /run/php/

The ls output must include php8.3-fpm.sock, and the same path must be in the fastcgi_pass directive. If the service isn't running, start it: systemctl enable --now php8.3-fpm.

Error 500 or a blank page#

Look at the Panel's log:

bash
tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log
  • tail -n 100 – prints the last 100 lines of today's log.

Look for a line with a date and the word ERROR. The most common causes:

  • Permission denied – wrong file owner. Run chown -R www-data:www-data /var/www/pterodactyl/* again.
  • No application encryption key has been specified. – APP_KEY is missing. On the first installation, run php artisan key:generate --force. If the Panel already has data, restore the original key from your backup.
  • SQLSTATE[HY000] [1045] Access denied – the database password or user in .env is wrong.

The MAC is invalid#

The database was encrypted with a different APP_KEY than the one now in .env. This typically happens after restoring the database onto a new installation. The only fix is to put the original APP_KEY back into .env. Without it, the encrypted data can't be recovered.

Emails aren't sent#

Most often the queue isn't running. Check the service and its log:

bash
systemctl status pteroq
journalctl -u pteroq -n 50
  • journalctl -u pteroq -n 50 – the last 50 lines of the service log.

Also check that Redis is running (systemctl status redis-server). Once fixed, run systemctl restart pteroq. If the queue is running, check the mail settings (php artisan p:environment:mail) and errors in the Panel log (see Error 500).

A server is stuck installing#

Server installation is done by Wings, not the Panel. See the “Troubleshooting” section of the Pterodactyl Wings guide (most often broken DNS in containers or an unreachable node).

Scheduled server tasks don't run#

Check that the queue is running (systemctl status pteroq), the cron line (crontab -l) and the time zones: the system (timedatectl) and the Panel (APP_TIMEZONE in .env). Clearing the scheduler cache can also help:

bash
php /var/www/pterodactyl/artisan schedule:clear-cache
systemctl restart pteroq

Nginx won't start: cannot load certificate#

The certificate for the domain in the configuration doesn't exist: you didn't get it, or the domain in server_name and in the /etc/letsencrypt/live/… path differs from the one it was issued for. certbot certificates lists your certificates.

Certbot: Timeout during connect (likely firewall problem)#

Let's Encrypt couldn't reach port 80 of your VPS. Check that the domain's A record points to the VPS's IPv4 address, that port 80 is allowed in both the customer area firewall and UFW, and that Nginx is running. If the domain has an AAAA record, it must point to this VPS's IPv6 address, because Let's Encrypt tries IPv6 too.

Composer or artisan reports the wrong PHP version#

The message says something like requires php ^8.2 || ^8.3 but your php version (8.4…) does not satisfy that requirement. The php command runs a version other than 8.3, typically on Debian 13 after the default php package got installed. Set the default PHP to 8.3:

bash
update-alternatives --set php /usr/bin/php8.3
php -v
  • update-alternatives --set php … – sets which version the php command runs.

If /usr/bin/php8.3 doesn't exist, PHP 8.3 isn't installed. Go back to Installing dependencies.

The page loops in redirects behind Cloudflare (ERR_TOO_MANY_REDIRECTS)#

Cloudflare is in Flexible mode. Switch it to Full (strict) and also check TRUSTED_PROXIES as described in Panel behind Cloudflare.

The server console doesn't work behind Cloudflare#

The node's domain goes through the Cloudflare proxy (orange cloud). This typically happens when the node uses the same domain as the Panel. Switch the node's domain to DNS only, or give the node its own subdomain.

Summary

  • The Panel runs on Nginx + PHP 8.3 (on Debian 13 from the packages.sury.org repository), MariaDB and Redis.
  • The Panel's database user has host 127.0.0.1 and privileges on the panel database only.
  • You have APP_KEY from .env saved off the server; without it, encrypted data can't be restored.
  • Cron runs schedule:run every minute; the pteroq service processes the queue.
  • The certificate renews itself and Nginx loads it after renewal thanks to the deploy hook.
  • Next step: Pterodactyl Wings and the database host for servers.

Was this guide helpful?

Applies to

  • Ubuntu 24.04
  • Debian 13
  • Pterodactyl Panel 1.x

Services

Report a mistake in this guide

Search the guides