Pterodactyl Panel + Wings behind HAProxy — Port 443 from anywhere

Deploying Pterodactyl Panel and Wings on Debian 13 with HAProxy as a TLS reverse proxy — Wings exposed on port 443 to get around corporate network restrictions.

Pterodactyl Panel + Wings behind HAProxy — Port 443 from anywhere Pterodactyl Panel + Wings behind HAProxy — Port 443 from anywhere
Table of Contents

Pterodactyl is an open-source game server management panel. The classic problem: Wings (the daemon) listens on port 8080 by default, which is often blocked on corporate networks. The solution: put Wings behind HAProxy on port 443.

The problem

The Pterodactyl panel reaches the Wings daemon from the client’s browser:

Browser → node.domain.com:8080 → Wings

From a corporate network, port 8080 is blocked by the firewall. The node shows up red in the panel even though Wings works perfectly locally.

Target architecture

Internet (port 443 only)
        │
        ▼
HAProxy — TLS termination
        │
        ├── pterodactyl.domain.com:443 → Panel (nginx:443)
        └── node.domain.com:443        → Wings (HTTP:8080)

# Wings listens in plain HTTP on 8080 — HAProxy handles TLS

Debian 13 installation

# PHP 8.3 from sury.org
curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg \
  https://packages.sury.org/php/apt.gpg
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] \
  https://packages.sury.org/php/ trixie main" \
  > /etc/apt/sources.list.d/php.list

apt update && apt install -y \
  php8.3 php8.3-{cli,fpm,mysql,mbstring,bcmath,xml,curl,zip,gd,tokenizer} \
  nginx mariadb-client certbot python3-certbot-nginx

Installing the Panel

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 -xzf panel.tar.gz
chmod -R 755 storage/* bootstrap/cache/

composer install --no-dev --optimize-autoloader
cp .env.example .env
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

Nginx for the Panel

server {
    listen 443 ssl;
    server_name pterodactyl.domain.com;

    ssl_certificate     /etc/letsencrypt/live/pterodactyl.domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pterodactyl.domain.com/privkey.pem;

    root /var/www/pterodactyl/public;
    index index.php;

    location / { try_files $uri $uri/ /index.php?$query_string; }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
    }
}

Installing Wings

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 configuration — internal HTTP

In the panel: Admin → Nodes → Create a node:

  • FQDN: node.domain.com
  • Daemon Port: 443 ← public port exposed by HAProxy
  • ✅ Behind Proxy checked ← critical

Copy the generated config into /etc/pterodactyl/config.yml.

Make sure Wings listens in HTTP on 8080 (not 443):

# /etc/pterodactyl/config.yml
api:
  port: 8080      # Internal port — HAProxy proxies to it
  ssl:
    enabled: false # HAProxy handles TLS
# systemd service
curl -o /etc/systemd/system/wings.service \
  https://raw.githubusercontent.com/pterodactyl/wings/develop/wings.service
systemctl enable --now wings

# Check
ss -tlnp | grep 8080
# → Wings listening on 0.0.0.0:8080

HAProxy configuration

frontend https_front
    bind *:443 ssl crt /etc/haproxy/combined.pem alpn h2,http/1.1

    acl is_panel hdr(host) -i pterodactyl.domain.com
    acl is_node  hdr(host) -i node.domain.com

    use_backend panel_backend if is_panel
    use_backend wings_backend if is_node

backend panel_backend
    option forwardfor
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    timeout tunnel 1h
    server panel 192.168.x.x:443 check ssl verify none

backend wings_backend
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    timeout tunnel 1h
    server wings 192.168.x.x:8080 check
    # No "ssl" here — Wings runs in plain HTTP
ssl verify none vs check

For the Wings backend: just ‘check’, without ‘ssl’ — Wings listens in HTTP. For the panel’s nginx, which listens in HTTPS: ‘check ssl verify none’.

Checking

user@laptop:~$ curl -sk https://node.domain.com/api/system -o /dev/null -w "%{http_code}"
401
401 = Wings answers through HAProxy (authentication required)

Firewall on the Wings server

# Allow Wings only from HAProxy
iptables -A INPUT -p tcp --dport 8080 -s 192.168.x.11 -j ACCEPT  # HAProxy IP
iptables -A INPUT -p tcp --dport 8080 -j DROP

# Panel nginx port
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Pterodactyl SFTP
iptables -A INPUT -p tcp --dport 2022 -j ACCEPT

# Persist
apt install -y iptables-persistent
netfilter-persistent save

Result

  • Panel reachable from any network on port 443 ✅
  • Wings reachable through HAProxy on node.domain.com:443 ✅
  • Port 8080 not exposed to the internet ✅
  • Game servers reachable (Docker opens the ports automatically) ✅
Certificate renewal

Set up a cron job to renew the Let’s Encrypt certificates automatically and rebuild HAProxy’s combined.pem (cat fullchain.pem privkey.pem > combined.pem).

Comments