Instalar Pterodactyl Panel para servidores de juegos
Panel y Wings son dos programas separados con dos tareas separadas. Quien lo ha entendido instala Pterodactyl en media hora; quien no lo ha entendido se pasa días buscando el fallo.
Pterodactyl es la interfaz libre más extendida para servidores de juegos. Su fama de ser complicado casi nunca viene de la instalación en sí, sino de un único malentendido: Pterodactyl no es un programa, son dos. Esta guía separa ambos con claridad, muestra las diferencias entre Debian y Ubuntu y después repasa los mensajes de error que acaban copiados literalmente en el buscador.
Panel y Wings: dos programas, dos funciones
El Panel es una aplicación PHP basada en Laravel. Aporta la interfaz web, gestiona usuarios, permisos, bases de datos y tareas programadas, y lo guarda todo en su propia base de datos MySQL o MariaDB. El Panel no inicia nunca por sí mismo un servidor de juegos. Ni siquiera conoce Docker.
Wings es un único programa escrito en Go. Se ejecuta en cada máquina en la que realmente deben correr los servidores de juegos, habla con el daemon de Docker, arranca contenedores, transmite la consola y ofrece el acceso SFTP. Wings no tiene interfaz web ni base de datos. Escucha en un puerto HTTP y espera instrucciones del Panel.
Ambos se comunican exclusivamente por HTTP, en las dos direcciones, con tokens firmados. De ahí se derivan tres cosas que conviene aceptar desde el principio:
- El Panel tiene que llegar al nodo a través de un nombre de dominio, no de una IP. El certificado va ligado al nombre.
- Panel y Wings deben hablar el mismo protocolo. Un Panel por HTTPS con Wings por HTTP no funciona, porque el navegador bloquea la conexión de la consola.
- Los dos relojes tienen que estar en hora. Los tokens duran solo unos minutos.
Quien haya interiorizado estas tres frases ya se ha ahorrado la mitad de los problemas típicos de Pterodactyl.
Requisitos y elección del sistema
Pterodactyl 1.11 y posteriores exigen PHP 8.2 u 8.3. Este es el punto en el que la mayoría de las guías se vuelve imprecisa, porque las distribuciones entregan versiones muy distintas. A fecha de julio de 2026, la situación en los repositorios estándar es esta:
| Sistema | PHP | Base de datos | nginx |
| Debian 12 | 8.2 (válida) | MariaDB 10.11 | 1.22 |
| Debian 13 | 8.4 (demasiado nueva) | MariaDB 11.8 | 1.26 |
| Ubuntu 24.04 | 8.3 (válida) | MySQL 8.0 o MariaDB 10.11 | 1.24 |
| Ubuntu 22.04 | 8.1 (demasiado antigua) | MySQL 8.0 o MariaDB 10.6 | 1.18 |
| Debian 11 | 7.4 (demasiado antigua) | MariaDB 10.5 | 1.18 |
Esta guía es material puro de Debian y Ubuntu. En AlmaLinux, Rocky Linux y Oracle Linux no existe apt, así que allí no sirve ninguno de los comandos de paquetes que siguen.
Consecuencia práctica: Debian 12 y Ubuntu 24.04 son los dos sistemas en los que el Panel funciona sin repositorios de terceros. En Ubuntu 22.04 necesitas el PPA de Ondřej Surý y en Debian 13 su equivalente para Debian, porque allí PHP 8.4 es quien provee el metapaquete y el composer.json del Panel exige expresamente ^8.2 || ^8.3. En la práctica, la ejecución de Composer bajo PHP 8.4 llega hasta el final, pero es terreno no confirmado y no es un estado en el que quieras tener un servidor en producción. Si prefieres ahorrarte el repositorio adicional, elige Debian 12 o Ubuntu 24.04.
Por cierto, los dos sistemas demasiado antiguos no fallan en el comando de paquetes, sino dos pasos más tarde, y eso es lo que los hace tan traicioneros. apt install php termina con código de salida 0 tanto en Ubuntu 22.04 (PHP 8.1.2) como en Debian 11 (PHP 7.4.33), y es la ejecución de Composer la que se interrumpe después, en Ubuntu 22.04 con brick/math requires php (^8.2) failed y en Debian 11 con aws/aws-sdk-php requires php (>=8.1) failed. Si tienes que quedarte en uno de estos sistemas, instala desde el repositorio de terceros los paquetes con versión explícita (php8.3, php8.3-cli, php8.3-fpm y demás) en lugar del metapaquete php, porque si no vuelve a imponerse la versión de la distribución.
Segunda diferencia que sorprende con regularidad: Debian no ofrece ningún paquete mysql-server. Allí lo estándar es MariaDB, y está perfectamente bien, porque Pterodactyl pide MariaDB 10.2 o superior. Quien escriba apt install mysql-server en Debian recibirá E: Unable to locate package mysql-server y se pondrá a buscar en el sitio equivocado.
Para el nodo con Wings, por cierto, estas reglas de PHP no se aplican en absoluto. Wings es un programa en Go enlazado estáticamente y solo necesita Docker y una versión de kernel razonablemente actual. El nodo puede ser perfectamente Debian 13 mientras el Panel corre sobre Debian 12.
Instalar el Panel
Todos los comandos que siguen se ejecutan como root. Primero, los paquetes base. Presta atención a incluir las extensiones de PHP al completo, porque la falta de php-bcmath no se nota hasta la ejecución de Composer.
apt update
apt -y install curl ca-certificates gnupg lsb-release tar unzip git
apt -y install mariadb-server nginx redis-server
apt -y install php php-cli php-common php-gd php-mysql php-mbstring php-bcmath php-xml php-fpm php-curl php-zip
Comprueba de inmediato que la versión es la correcta antes de seguir:
php -v
php -m | grep -E "bcmath|mbstring|curl|zip|gd|xml"
Después, Composer y los archivos del Panel:
curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
mkdir -p /var/www/pterodactyl
curl -Lo /var/www/pterodactyl/panel.tar.gz https://github.com/pterodactyl/panel/releases/latest/download/panel.tar.gz
tar -xzf /var/www/pterodactyl/panel.tar.gz -C /var/www/pterodactyl
La base de datos. Crea el usuario sobre 127.0.0.1 y no sobre localhost, porque si no entra en juego el acceso por socket y Laravel acabará recibiendo un acceso denegado aunque la contraseña sea correcta. Cómo distinguir un caso del otro lo explicamos en nuestro artículo sobre Access denied for user.
mariadb -u root -e "CREATE DATABASE panel;"
mariadb -u root -e "CREATE USER 'pterodactyl'@'127.0.0.1' IDENTIFIED BY 'AquiUnaContrasenaLarga';"
mariadb -u root -e "GRANT ALL PRIVILEGES ON panel.* TO 'pterodactyl'@'127.0.0.1' WITH GRANT OPTION;"
mariadb -u root -e "FLUSH PRIVILEGES;"
Después conviene proteger la base de datos, para lo que tenemos un artículo propio sobre cómo asegurar MariaDB y MySQL.
Ahora, la configuración propiamente dicha. Los comandos p:environment son interactivos y preguntan por la URL del Panel, la zona horaria, el driver de caché y el acceso a la base de datos:
cd /var/www/pterodactyl
cp .env.example .env
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader
php artisan key:generate --force
php artisan p:environment:setup
php artisan p:environment:database
php artisan migrate --seed --force
php artisan p:user:make
chown -R www-data:www-data /var/www/pterodactyl/*
En p:environment:setup elige Redis como driver de sesión y de caché, así funcionará limpiamente la cola de trabajos que se describe más abajo. Y escribe la URL del Panel con https://. Un http:// en este punto genera después contenido mixto y una consola que se queda esperando conexión eternamente.
nginx y la trampa de las versiones
Los fundamentos de la configuración del servidor web están en nuestro artículo sobre cómo instalar nginx. Para Pterodactyl hay dos detalles importantes con los que fallan una y otra vez las plantillas ya hechas que circulan por la red.
Primero, el socket de PHP-FPM. El nombre del archivo contiene la versión de PHP y varía según el sistema. Míralo en lugar de adivinar:
systemctl status php8.2-fpm
ls /run/php/
En Debian 12 el socket se llama php8.2-fpm.sock y en Ubuntu 24.04 php8.3-fpm.sock. Una ruta equivocada aquí produce justo esa página 502 Bad Gateway que tanta gente busca. La consulta de estado va antes a propósito: el archivo de socket no aparece hasta que el servicio FPM está en marcha. Si no está arrancado, /run/php/ está vacío y darás la ruta por incorrecta sin que lo sea. Si el servicio todavía no corre, ayuda systemctl enable --now php8.2-fpm con el número de versión que corresponda a tu instalación.
Segundo, la forma de escribir HTTP/2. La directiva nueva http2 on; no existe hasta nginx 1.25.1. En Debian 12 (1.22), Ubuntu 22.04 (1.18) y también en Ubuntu 24.04 (1.24) tienes que usar la forma antigua listen 443 ssl http2;, porque si no el arranque se interrumpe con nginx: [emerg] unknown directive "http2". Solo Debian 13, con nginx 1.26, entiende las dos formas.
server {
listen 443 ssl http2;
server_name panel.example.com;
root /var/www/pterodactyl/public;
index index.php;
client_max_body_size 100m;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_PROXY "";
}
}
Cola de trabajos y planificador
Sin estos dos componentes el Panel parece funcional, pero no envía ningún correo ni ejecuta ninguna tarea programada. La estructura del archivo de unidad está explicada en detalle en nuestro artículo sobre cómo crear un servicio systemd, y aquí va la versión ya terminada:
[Unit]
Description=Pterodactyl Queue Worker
After=redis-server.service
[Service]
User=www-data
Group=www-data
Restart=always
RestartSec=5s
ExecStart=/usr/bin/php /var/www/pterodactyl/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now redis-server
systemctl enable --now pteroq.service
Añade además una entrada en la crontab de root; los detalles de la sintaxis los encuentras en el artículo Configurar un cron job en Linux:
* * * * * php /var/www/pterodactyl/artisan schedule:run >> /dev/null 2>&1
Instalar Wings en el nodo
A partir de aquí trabajas en la máquina que debe ejecutar los servidores de juegos. Puede ser el mismo servidor, pero no tiene por qué. El requisito es Docker, cuya instalación describimos en el artículo Instalar Docker en Debian y Ubuntu.
mkdir -p /etc/pterodactyl
curl -L -o /usr/local/bin/wings https://github.com/pterodactyl/wings/releases/latest/download/wings_linux_amd64
chmod u+x /usr/local/bin/wings
wings version
En servidores ARM el nombre del archivo es wings_linux_arm64. Si descargas el paquete equivocado, la shell responde simplemente con cannot execute binary file: Exec format error. No te extrañe la salida de la última línea: wings version contesta con una doble v, algo como wings vv1.13.1. Viene así de fábrica y no es señal de una instalación rota.
El archivo /etc/pterodactyl/config.yml no lo escribes tú. Se genera automáticamente enseguida. De momento, crea solo el servicio:
[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
Todavía no lo arranques. Sin configuración, Wings se detiene de inmediato con un mensaje que viene a decir que falta el archivo de configuración en /etc/pterodactyl/config.yml. A estas alturas es completamente normal y no es ningún fallo.
Una palabra sobre el swap: muchas guías antiguas exigen swapaccount=1 en /etc/default/grub. Eso afecta únicamente a los sistemas con cgroup v1. Debian 12 y 13, así como Ubuntu 22.04 y 24.04, usan cgroup v2 por defecto y allí la entrada sobra. Puedes comprobarlo con docker info. Si aparece WARNING: No swap limit support, el límite de memoria para el swap no se aplica. Cómo dimensionar con criterio el espacio de swap lo tienes en el artículo Configurar swap.
Certificado para el nodo, no solo para el Panel
El error de razonamiento más habitual: se consigue un certificado para panel.example.com y luego uno se extraña de que el nodo no funcione. Wings necesita un certificado propio para su propio nombre de dominio, por ejemplo node1.example.com. Los dos nombres pueden apuntar a la misma IP, pero son dos nombres.
En un nodo puro no corre ningún servidor web, así que el modo standalone de Certbot es el camino más sencillo. Para ello, el puerto 80 tiene que ser accesible desde fuera durante un momento:
apt -y install certbot
certbot certonly --standalone -d node1.example.com
Quien gestione muchos nodos irá mejor con un certificado wildcard, algo que describimos en el artículo Certificado wildcard de Let's Encrypt.
Dos piedras en el camino que cuestan mucho tiempo:
- Wings lee los archivos del certificado al arrancar. Tras una renovación hay que recargar el servicio. Deja en
/etc/letsencrypt/renewal-hooks/deploy/un pequeño script consystemctl restart wings. Sin eso, el nodo funciona 90 días de maravilla y luego falla en apariencia sin motivo. - Desactiva el proxy de Cloudflare para el nombre del nodo. La nube naranja rompe la conexión TLS y sustituye el certificado. El Panel recibe entonces un certificado que no coincide con el emisor esperado, y la conexión de consola por WebSocket se comporta de forma impredecible. El registro A del nodo tiene que estar en gris.
Crear el nodo y conectarlo
En el Panel, en Admin, Locations, crea primero una ubicación y después el nodo en Nodes. Los campos que de verdad cuentan:
- FQDN:
node1.example.com, exactamente el nombre del certificado. - Communicate over SSL: activado si el Panel funciona por HTTPS. Si no, mejor ni empezar.
- Behind Proxy: actívalo solo si delante de Wings hay realmente un reverse proxy y es este quien termina el TLS.
- Daemon Port: 8080. Daemon SFTP Port: 2022.
- Memory y Disk: los límites que el Panel respeta al repartir servidores.
Después de guardar, abre la pestaña Configuration del nodo. Allí el Panel genera un comando con un token de un solo uso. Ejecútalo en el nodo:
cd /etc/pterodactyl
wings configure --panel-url https://panel.example.com --token TOKEN --node 1
Con eso Wings obtiene su configuración completa y escribe /etc/pterodactyl/config.yml. Revisa el contenido: si en la dirección del Panel pone http:// en lugar de https://, has copiado el comando de un entorno en el que la URL del Panel está mal guardada. Corrígelo de raíz con php artisan p:environment:setup, no a mano en el archivo YAML.
Después, en la pestaña Allocations, introduce la IP del nodo y los rangos de puertos que quieras, por ejemplo de 25565 a 25600 para Minecraft. Sin al menos una asignación libre no se puede crear ningún servidor.
Solo ahora hay que arrancar:
systemctl enable --now wings
systemctl status wings
Los puertos tienen que estar abiertos en el firewall. Con ufw, cuyos fundamentos tratamos en el artículo Configurar el firewall ufw:
ufw allow 8080/tcp
ufw allow 2022/tcp
ufw allow 25565:25600/tcp
ufw allow 25565:25600/udp
Cuando Wings no se conecta
El Panel muestra un icono rojo en el nodo o lanza un error al crear un servidor. Ve repasando los mensajes en orden, porque son sorprendentemente claros.
cURL error 7: Failed to connect ... Connection refused
El servidor del Panel no alcanza el puerto. O Wings no está funcionando, o el firewall bloquea, o el servicio escucha en la dirección equivocada. Comprueba en este orden:
systemctl status wings
ss -tlnp | grep 8080
journalctl -u wings -n 50 --no-pager
Y desde el servidor del Panel, que es la prueba decisiva:
curl -v https://node1.example.com:8080
Una respuesta HTTP, incluso un 404 con contenido JSON, es en este punto un éxito. Demuestra que DNS, firewall, puerto y TLS encajan.
cURL error 60: SSL certificate problem
El certificado del nodo no se acepta. Si aparece self signed certificate, estás usando un certificado autofirmado, y el Panel no sabe manejarlo porque la biblioteca subyacente no admite excepciones. Si aparece unable to get local issuer certificate, lo habitual es que falte la cadena intermedia, es decir, que tu configuración de Wings apunte a cert.pem en lugar de a fullchain.pem. Si aparece certificate has expired, la renovación se ha completado, pero Wings sigue con el archivo antiguo en memoria (mira el apunte sobre el reinicio más arriba).
cURL error 28: Operation timed out
Ni respuesta ni reset. Eso huele a un firewall que descarta los paquetes en lugar de rechazarlos, o a un nodo detrás de NAT. Un caso especial: Panel y Wings en el mismo servidor, con el Panel dirigiéndose a la propia IP pública. Algunas redes no devuelven ese bucle. La solución es una entrada en /etc/hosts del servidor del Panel que apunte el nombre del nodo a la dirección interna.
Cannot connect to the Docker daemon at unix:///var/run/docker.sock
Wings funciona, Docker no. systemctl status docker lo aclara en una línea. Aun así, el nodo suele aparecer como accesible en el Panel, porque la consulta de estado responde, pero cualquier arranque de servidor falla.
El nodo responde, pero rechaza todas las acciones
Hay un token que no coincide. Pasa después de repetir wings configure con un token antiguo, o cuando el nodo se ha borrado y vuelto a crear en el Panel. Solución: genera un token nuevo en la pestaña Configuration del Panel, vuelve a ejecutar el comando y reinicia Wings. Nada de andar adivinando en el archivo YAML.
Para todos los casos en los que el mensaje siga sin estar claro, para el servicio y arranca Wings en primer plano. La salida es bastante más locuaz que el journal:
systemctl stop wings
wings --debug
También resulta útil el modo de informe integrado, que recopila la configuración, el estado de Docker y los datos del sistema:
wings diagnostics
Desfase horario entre el Panel y el nodo
Este fallo es traicionero porque parece un problema de red. Síntomas: el nodo se muestra como accesible en el Panel, se pueden crear servidores, pero la consola se queda colgada estableciendo la conexión y el acceso SFTP rechaza credenciales correctas.
El motivo está en el diseño. El Panel firma tokens de vida corta, cuya validez se mide en minutos. Wings comprueba el momento de emisión y el vencimiento contra su propio reloj. Si los dos sistemas se separan más de unos pocos minutos, Wings descarta cada token por caducado o por todavía no válido, aunque acabe de generarse.
Importante para entenderlo: no se trata de la zona horaria. El Panel y el nodo pueden tener configuradas zonas horarias distintas, eso da igual. Se trata del instante absoluto. Comprueba en las dos máquinas:
date -u
timedatectl status
En la salida de timedatectl tiene que aparecer System clock synchronized: yes y NTP service: active. Si no es así:
timedatectl set-ntp true
En sistemas sin systemd-timesyncd, por ejemplo tras una instalación mínima, instala chrony y comprueba la sincronización:
apt -y install chrony
chronyc tracking
El valor de System time debería moverse en el rango de los milisegundos. Las máquinas virtuales clonadas a partir de una imagen o restauradas desde un snapshot son la fuente más frecuente de desviaciones grandes.
Cómo saber que de verdad funciona
Un icono verde en el Panel es solo la primera de cinco pruebas. Repasa la lista y lo sabrás con certeza:
systemctl is-active wingsdevuelveactive, y unjournalctl -u wings -n 20no muestra errores recurrentes.- El nodo, en la vista general, informa de la capacidad real de memoria y de disco del servidor de destino, no solo de los límites introducidos en el Panel. Esas cifras llegan en vivo desde el nodo y son la prueba de que la comunicación funciona.
- Creas un servidor de prueba. En la vista general pasa por el estado Installing y después se muestra con normalidad. En el nodo,
docker ps -amuestra el contenedor correspondiente. - Arrancas el servidor y ves en el navegador la salida de consola en marcha. Esa es la prueba de la conexión WebSocket y, con ella, del certificado y de la hora a la vez.
- Te conectas por SFTP al puerto 2022 con tus credenciales del Panel y ves los archivos del servidor. Con eso queda confirmado también el segundo puerto de Wings.
La instalación no está completa hasta que los cinco puntos son correctos. Por experiencia, los que más fallan son el cuarto y el quinto, aunque hasta ese momento el Panel parezca totalmente normal.
Ejecutar el Panel y Wings juntos o por separado
Las dos opciones son posibles. En un único servidor solo tienes que tener en cuenta dos cosas: usa dos nombres de dominio distintos sobre la misma IP, uno para el Panel en el puerto 443 y otro para el nodo en el puerto 8080. Y cuenta con que el apetito de memoria de los servidores de juegos frenará también al Panel cuando las cosas se pongan justas.
Separarlos es, a partir del segundo nodo, el caso normal de todos modos, y tiene un efecto secundario agradable: un servidor de juegos sobrecargado o bajo ataque no se lleva por delante la interfaz de administración. Para la protección básica de las dos máquinas merece la pena echar un vistazo a nuestra lista de comprobación para servidores root nuevos y a los artículos sobre cómo asegurar SSH y fail2ban.
Para terminar, un apunte práctico para el día a día: las imágenes Docker de los servidores de juegos traen su propio entorno Java. En el nodo no necesitas instalar Java. Si aun así quieres probar algo alguna vez fuera de Pterodactyl, encontrarás el camino adecuado en nuestros artículos sobre Java 21 en Debian y sobre el servidor de Minecraft en Debian. Y vigila el espacio en disco, porque las imágenes y los backups crecen deprisa; para eso encaja el artículo Disco lleno en Linux.
Preguntas frecuentes
¿Cuál es la diferencia entre Pterodactyl Panel y Wings?
¿Qué versión de PHP necesito para Pterodactyl?
¿Por qué Wings no se conecta con el Panel?
¿Qué puertos tengo que abrir para Pterodactyl?
¿Necesito un certificado SSL propio para cada nodo?
¿Por qué la consola del servidor se queda colgada en el navegador al conectar?
¿Tengo que instalar Java en el nodo?
2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

