
You run Pterodactyl on a VPS, and a port scan shows SSH, the panel, Wings and maybe MariaDB answering anyone who asks. You want to shut out everyone but players and admins, yet one wrong rule and the console stops loading, SFTP times out, certificates stop renewing or players can't join. On a panel host, UFW also controls fewer ports than you'd think. This guide sorts the ports, gives the rules for Ubuntu 24.04 and Debian 12 (on Debian, run sudo apt install ufw first) and shows how to test from outside.
Quick answer: Wings runs each game server in Docker and publishes every allocated port for TCP and UDP, and Docker routes that traffic before UFW's rules see it. So on a Pterodactyl node, UFW neither opens nor closes game ports; it guards the host's own services. Allow SSH first, from your address or with ufw limit. Open 443 on the panel server and 8080 and 2022 on each node to everyone who uses the panel, or only to admin addresses when nobody else does. Keep 80 open for Let's Encrypt renewals, allow 3306 only from the panel and nodes, and never open Redis on 6379. Game servers that run outside Docker need one rule per port with the right protocol. Then scan from another network.
What UFW Controls on a Panel Host
A Pterodactyl machine has two kinds of ports. The host's own services are SSH, Nginx serving the panel on 80 and 443, Wings' API on 8080 and its built-in SFTP server on 2022 (Wings binds both to 0.0.0.0 by default, per its config.go), MariaDB and Redis. UFW filters all of them.
Game allocations are the other kind. Wings binds every port assigned to a server for both TCP and UDP on the allocation's IP (allocations.go), and Docker publishes them. Docker's firewall docs say that traffic "gets diverted before it goes through the ufw firewall settings", so on a node sudo ufw allow 25565 adds nothing and sudo ufw deny 25565 closes nothing. Our Docker and UFW guide explains why.
| Port | Service | Who must reach it |
|---|---|---|
| 22/tcp | SSH | You |
| 80/tcp | Panel redirect to HTTPS, Let's Encrypt's HTTP challenge | Anyone |
| 443/tcp | Panel | Panel users and every node |
| 8080/tcp | Wings API: console, uploads, downloads | Panel users' browsers, the panel server, other nodes during transfers |
| 2022/tcp | Wings SFTP | Panel users' SFTP clients |
| 3306/tcp | Database host for game servers | The panel and the nodes |
| 6379/tcp | Redis for the panel | Nothing outside the server |
| Allocations | Game servers | Players, through Docker |
Our Panel vs Wings guide shows what each connection carries.
Step 1: Allow SSH Before You Enable UFW
UFW's packaged defaults drop incoming and allow outgoing traffic (ufw.defaults), so the first rule has to keep you in. With a fixed address, allow SSH from it only (198.51.100.7 stands for yours in these examples):
sudo ufw allow proto tcp from 198.51.100.7 to any port 22
If your address changes, rate-limit SSH instead. A limit rule "will deny connections if an IP address attempts to initiate 6 or more connections within 30 seconds" (ufw(8)):
sudo ufw limit 22/tcp
Use your own SSH port if you moved it, and keep the key-only logins and Fail2Ban from our UFW and Fail2Ban basics. Then switch UFW on (on a panel in use, add the Step 2 and 3 rules first, or users lose the panel until you do):
sudo ufw enable
sudo ufw status verbose
Over SSH it asks Command may disrupt existing ssh connections. Proceed with operation (y|n)?, because enabling flushes UFW's chains, which "may drop existing connections", and then loads the rules you added. Test a new login from a second terminal before you close this one.
Step 2: Open Panel and Wings Ports to the Right People
Players or customers use the panel. Open the panel's ports on the panel server and Wings' ports on every node. On a single VPS, all four go on the same machine:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8080/tcp
sudo ufw allow 2022/tcp
Browsers and SFTP clients connect to Wings directly, so its ports must be open to every user. Pterodactyl's troubleshooting page says: "Check that the Wings ports are open on your firewall. Wings uses ports 8080 or 8443 for HTTP(s) traffic, and 2022 for SFTP traffic." Close 8080 to a user and their console shows "We're having some trouble connecting to your server, please wait..." (WebsocketHandler.tsx).
Only admins use the panel. Then 443, 8080 and 2022 can be limited to admin addresses. UFW takes a port list as long as you name the protocol:
sudo ufw allow 80/tcp
sudo ufw allow proto tcp from 198.51.100.7 to any port 443,8080,2022
Add a line per admin. If the panel's or node's name also has an AAAA record, add your IPv6 address or prefix too, such as from 2001:db8:1234::/64, because a rule with an IPv4 source doesn't match IPv6 traffic. If Cloudflare proxies the panel's DNS record, connections to 443 come from Cloudflare's addresses, not yours, so this allowlist needs a DNS-only record.
On separate machines, allow the servers too. Wings calls the panel at its remote address "to collect data and send events" (config.go), so each node needs 443 on the panel server. The panel needs 8080 on every node, which the troubleshooting page tests with curl https://domain.com:8080 "on the Panel server". For server transfers, the old node connects to the new node's daemon port (DaemonTransferRepository.php, Node.php). With 203.0.113.10 as the panel and 203.0.113.21 as a node:
sudo ufw allow proto tcp from 203.0.113.21 to any port 443 # on the panel server
sudo ufw allow proto tcp from 203.0.113.10 to any port 8080 # on each node
On a single VPS you need neither: ip route get 203.0.113.5 (the server's own address) starts with local 203.0.113.5 dev lo, since such packets are "looped back and delivered locally" (ip-route(8)), and UFW accepts loopback traffic before your rules (before.rules).
Port 80 stays open in both cases. Let's Encrypt's HTTP-01 challenge "can only be done on port 80" (challenge types), and "We don't publish a list of IP addresses we use to validate" (FAQ), so an allowlist on 80 breaks renewals, also on a node that renews with certbot's standalone mode. A DNS challenge doesn't need port 80; Pterodactyl's SSL guide has an acme.sh method that uses the Cloudflare API and adds itself to the crontab for renewals.
Step 3: Keep Database and Redis Ports Private
The panel's own database and Redis serve only the panel, so they need no rule. sudo ufw show listening (Step 6) shows the address each one listens on: 127.0.0.1 means only the server itself can connect.
A database host for game servers is different: allow 3306 from the panel and every node. If game servers on the same machine use it, also allow Wings' Docker network, 172.18.0.0/16 by default (config_docker.go). A container connecting to MariaDB on the host's 172.18.0.1 is incoming traffic like any other, so UFW filters it.
sudo ufw allow proto tcp from 203.0.113.10 to any port 3306
sudo ufw allow proto tcp from 172.18.0.0/16 to any port 3306
Our database host guide covers bind-address and the grants.
Step 4: Game Ports Inside and Outside Docker
On a Wings node you open game ports in the panel, not in UFW. Wings publishes only the ports allocated to a server, so a game that needs a second port, such as Rust's query port or Satisfactory's 8888, needs a second allocation; no UFW rule can open a port Wings never published. An allocation you add for RCON or a web map is public too, so give it a strong password or filter it in DOCKER-USER (Fix 3 in the Docker and UFW guide).
Game servers outside Docker's port publishing are different: one you start with systemd or a script, or any server on a node whose Wings network runs in host mode, where "port mapping doesn't take effect" (host driver) and UFW filters the game like any program. Pterodactyl advises against host mode for public installs: "Panel users can bind to any IP or Port even if it's not allocated to their container" (Wings configuration).
There, every port needs a rule with the right protocol (defaults from each game's documentation):
| Game | Port | Protocol | Notes |
|---|---|---|---|
| Minecraft Java | 25565 | TCP | Query (UDP, query.port) and RCON (TCP 25575) are off by default |
| Bedrock players through Geyser | 19132 | TCP and UDP | TCP to find the server, UDP to play |
| Rust | 28015 and the query port | UDP | RCON (28016 in Facepunch's example) and Rust+ use TCP; only 28015 is needed to play |
| FiveM | 30120 | TCP and UDP | endpoint_add_tcp and endpoint_add_udp in server.cfg |
| Palworld | 8211 | UDP | |
| Satisfactory | 7777 and 8888 | 7777 TCP and UDP, 8888 TCP | 8888 is the reliable messaging port |
| Terraria | 7777 | TCP |
Rust's query port defaults to one above the higher of the game and RCON ports. A rule without a protocol opens both: ufw(8) says ufw allow 53 "will allow tcp and udp port 53".
sudo ufw allow 25565/tcp
sudo ufw allow 19132
sudo ufw allow 8211/udp
The Geyser row is new. Mojang made NetherNet the default transport for dedicated servers in Bedrock 26.50 (15 September 2026), and 26.60 (27 October 2026) calls RakNet deprecated. GeyserMC's setup page now says: "Before NetherNet, forwarding UDP was enough. Now, players use TCP to find your server and UDP to play on it, so without TCP your server will not show up." An old ufw allow 19132/udp rule hides your server; our Geyser NetherNet guide covers the config side.
Step 5: Rate Limits That Don't Lock Out Players
SSH gets the limit rule from Step 1.
Game ports don't. A limit counts new connections per address. A Minecraft client opens a new connection for each server list ping, which the server closes after its reply (Server List Ping), and another to join, so a player who refreshes the list a few times and joins can reach six in 30 seconds, and players sharing one address count as one. On a node, a limit on a Docker-published port wouldn't apply anyway. Leave floods to the network: every Space-Node VPS includes DDoS protection.
The panel throttles logins itself. Panel 1.15.2 allows 10 requests a minute per IP address on its login and two-factor endpoints, and 2 a minute for password reset emails (RouteServiceProvider.php, routes/auth.php). The login page also has invisible reCAPTCHA, and Pterodactyl's docs recommend replacing its default global keys with your own (Settings, Advanced). Under Settings, General, set Require 2-Factor Authentication to Admin Only or All Users (settings view).
SFTP is capped too. By default Wings lets 16 connections per IP address (or IPv6 /64) authenticate at once and each user keep 32 open (sftp/server.go), and the panel, which checks every SFTP login, refuses logins to a server for the rest of the minute after five failures (SftpAuthenticationController.php, ThrottlesLogins.php). So leave limit off 8080 and 2022.
Behind a proxy or tunnel, these limits need the visitor's real address. Symfony, whose request class Laravel extends (Laravel docs), says that behind a proxy REMOTE_ADDR "will now be the IP address of your reverse proxy" (proxy docs), so all visitors would share one login limit. Set TRUSTED_PROXIES in the panel's .env, such as TRUSTED_PROXIES=127.0.0.1 if "your proxy is running on the same machine as the server" (Pterodactyl docs).
Step 6: Test From Outside
Scan from your computer, then again from a network not on your allowlist, such as a phone hotspot (203.0.113.5 is the server):
nmap -Pn -p 22,80,443,2022,3306,6379,8080,25565 203.0.113.5
sudo nmap -Pn -sU -p 19132,28015 203.0.113.5
filtered means a firewall dropped the probe, which 3306, 6379 and, from the hotspot, admin-only ports should show; open means something accepted the connection, and closed that nothing listens (port states). UDP scans need root and prove less: a port that sends nothing back is open|filtered, meaning it "could be open, or perhaps packet filters are blocking the communication" (scan techniques). For a UDP game, join from the game itself.
On the server:
sudo ufw status verbose
sudo ufw show listening
sudo docker ps --format 'table {{.Names}}\t{{.Ports}}'
ufw show listening pairs each listening port with the rules that affect it, and docker ps shows what Docker publishes for each server. Last, open a console in the panel from an allowed address.
If It Still Fails
- The console says "We're having some trouble connecting to your server, please wait..." Your browser can't reach Wings on 8080. Check the rule for your current address, IPv6 included, or see our node offline guide.
- SFTP times out. UFW drops 2022 for your address; see our SFTP guide.
- Certificate renewals fail after the lockdown. Port 80 is closed. Reopen it or move to a DNS challenge.
- The game works, but query, RCON or a web map doesn't. That port has no allocation in the panel.
- A game server outside Docker is unreachable. Check the protocol in the table; Geyser needs TCP and UDP now.
- You locked yourself out of SSH. Log in through your provider's web console, then allow your new address or run
sudo ufw disable.
FAQ
Do I need ufw allow 25565 on a Pterodactyl node?
No. Docker publishes allocations past UFW, so the rule does nothing there, and neither does a deny rule. You need it only for a game server that runs outside Docker's port publishing.
Can I put the panel behind Cloudflare Tunnel?
The website, yes. cloudflared "creates outbound-only connections to Cloudflare's global network" (Cloudflare Tunnel), so 443 can stay closed, and 80 too unless a certificate on that machine, such as Wings', renews over HTTP-01; our Tunnel vs reverse proxy guide shows the setup, and Step 5 explains TRUSTED_PROXIES. Wings, no: Pterodactyl says proxying it "isn't beneficial since users will be connecting to the machine directly", and SFTP can't go through Cloudflare without the enterprise plan (Wings configuration).
Can I change rules while players are online? Yes. Once UFW is enabled, adding or removing a rule doesn't flush its chains; enabling it, modifying a rule or changing the default policy does, and a flush "may drop existing connections" (ufw(8)). Docker-published game ports bypass UFW either way.
My address changed and the panel won't load. What now?
Log in over SSH, which is why limit suits a changing address better than an allowlist. Find the old rule with sudo ufw status numbered, remove it with sudo ufw delete and its number, then add the new address.
Sources
All opened on 31 October 2026:
- Pterodactyl docs: Troubleshooting, Wings configuration, Additional configuration, Webserver configuration, SSL certificates
- Panel 1.15.2 source: RouteServiceProvider.php, routes/auth.php, WebsocketHandler.tsx, FileUploadController.php, FileController.php, SftpAuthenticationController.php, admin settings view, Node.php, DaemonTransferRepository.php
- Wings 1.13.4 source: allocations.go, config.go, config_docker.go, sftp/server.go
- Laravel: ThrottlesLogins.php (laravel/ui 4.6.3), Requests; Symfony: Proxies
- Docker: Packet filtering and firewalls, Host network driver, docker container ls
- UFW: ufw(8), ufw.defaults, before.rules, frontend.py
- Linux: ip-route(8)
- Let's Encrypt: Challenge types, FAQ
- Cloudflare: Cloudflare Tunnel
- Nmap: Port scanning basics, Port scanning techniques
- Minecraft: server.properties, Server List Ping, Bedrock 26.50 changelog, Bedrock 26.60 changelog, GeyserMC setup
- Other games: Rust: Creating a server, FiveM: Setting up a server (vanilla), Palworld: Requirements, Satisfactory: Dedicated servers, Terraria: Server