This article is about load testing five popular game server control panels: GameAP 3, GameAP 4, PufferPanel, Pterodactyl and Pelican. All panels are tested on the same hardware, under conditions as close to identical as possible; wherever the conditions could not be equalized, that is explicitly called out in the limitations.

Disclaimer. This article is published on the GameAP blog, and its author is a developer; GameAP 3 and GameAP 4 are two of the five panels under test. That is a conflict of interest, and you are right to expect bias. To counter it, the whole test is built to be verifiable: the k6 scripts, configurations, raw k6 reports and monitoring metrics of every run are published in a GitHub repository. The methodology can be inspected step by step, and the test can be re-run on your own hardware. If you think a panel got a wrong configuration or a conclusion is stretched, open an issue: it will be re-checked and a correction published. And a note for those who came for the short answer: yes, GameAP 4 posted the best numbers here.

The short version:

  • At typical load (10–100 concurrent users) all five panels work without a single error; what differs is latency: single-digit milliseconds for the Go panels, around 9 ms for GameAP 3, tens of milliseconds for Pterodactyl and Pelican.
  • The throughput ceiling on this rig:
    • 1126 requests/s for GameAP 4
    • 696 req/s for PufferPanel
    • 394 req/s for GameAP 3
    • 93 req/s for Pterodactyl
    • and 76 req/s for Pelican
  • Under overload the panels break differently: the Go panels start returning errors, the PHP panels return no errors at all but response times grow to seconds and tens of seconds.
  • The biggest surprise: at saturation, MySQL on Pterodactyl and Pelican consumes almost a full CPU core, while on GameAP 3, running the same MySQL with the same settings, the database is three times less busy at twice the requests per second.
Throughput ceilinghigher is betterrequests per second · median of three runs2505007501 0001 2500GameAP 4.x: 1 126 requests/s1 126GameAP 4.xPufferPanel: 696 requests/s696PufferPanelGameAP 3.x: 394 requests/s394GameAP 3.xPterodactyl: 93 requests/s93PterodactylPelican: 76 requests/s76Pelican

Goals #

  1. Find the breaking point: the limit at which a panel still works properly.
  2. Compare the alternatives under conditions as close as possible.
  3. Compare GameAP 3 and GameAP 4: what exactly the rewrite from PHP to Go achieved.

Besides the overall comparison of all five panels, three head-to-head groups are of particular interest:

  1. GameAP 3.x vs GameAP 4.x: a complete rewrite in Go with an updated architecture.
  2. GameAP 4.x vs PufferPanel: a comparison of panels written in Go.
  3. Pterodactyl vs Pelican: the original panel versus its fork.

The panels #

PanelVersionStackDatabaseDaemon
GameAP 4.x4.xGoPostgreSQLgameap-daemon
PufferPanel3.xGoPostgreSQLbuilt-in
GameAP 3.x3.xPHP 8.4 / LaravelMySQL 8.0gameap-daemon
Pterodactyl1.11.xPHP 8.4 / LaravelMySQL 8.0Wings (Docker)
Pelican1.0.xPHP 8.4 / Laravel, Pterodactyl forkMySQL 8.0Wings (Docker)

All three PHP panels additionally run Redis (cache and sessions). Versions are recorded down to the major branch as of April 2026, when the runs were performed.

What is being tested #

  • HTTP/API latency at different concurrency levels: load testing (10–100 VUs);
  • behavior under overload (degradation, errors, resource consumption): stress testing (800–1200 VUs);
  • the throughput ceiling with no pauses between requests: throughput testing.

Out of scope for this article:

  • write requests;
  • UX, features, security, ecosystem;
  • working with real game servers and their files;
  • long-term stability: no soak or spike tests were run.

Test rig #

Server (bare-metal, Selectel):

  • CPU: Intel Xeon E-2456 (Raptor Lake, 6C/12T, 3.3 GHz base / 5.1 GHz turbo, 18 MB L3);
  • RAM: 32 GB DDR5 ECC (2×16 GB, 4400 MT/s);
  • storage: 2× Samsung 990 PRO 1TB NVMe in mdadm RAID1;
  • hypervisor: Proxmox VE 9.1.5 on Debian 13, kernel 6.17.9-1-pve.
Bare-metal · Selectel · Xeon E-2456 · 32 GB DDR5 · Proxmox VE 9k6-runner4 vCPU · 4 GBload generatorPanel (API)4 vCPU · 8 GB · 40 GBUbuntu 24.04 LTSDaemon / agent6 vCPU · 12 GB · 80 GBUbuntu 24.04 LTSMonitoring2 vCPU · 4 GBPrometheus + GrafanaHTTPmetrics: node_exporter → Prometheus

Virtual machine configuration #

VMvCPURAMDiskRole
Panel48 GB40 GBcontrol panel (API), Ubuntu 24.04 LTS
Daemon612 GB80 GBgame server daemon/agent, Ubuntu 24.04 LTS
k6-runner44 GBload generator
Monitoring24 GBPrometheus + Grafana

Host tuning #

  • CPU governor: performance;
  • C-states limited to C1;
  • Turbo Boost enabled;
  • swap disabled on all virtual machines.

Methodology #

Panels are tested one at a time: while one panel is under test, all the other VMs are shut down to avoid interference. During a run, the only things working are the panel (API), the game server daemon, the k6 load generator and the monitoring stack.

Every panel went through three full independent runs of the whole profile series (April 18–19, 2026). All numbers in this article are medians of the three runs unless explicitly stated otherwise.

The sequence for each panel: start its two VMs (panel and daemon) → restart services → a warm-up run of the smoke scenario (1 VU, 30 s) → the load profiles in order. Before every profile the panel’s services are restarted; after each profile there is a 60-second pause. The sets of restarted services differ between panels: for the PHP panels it is php-fpm, nginx and MySQL (plus Redis on GameAP 3), for PufferPanel — the application itself, for GameAP 4 — only nginx. This is an asymmetry of the methodology; more in the limitations.

Load profiles #

ProfileVUsDurationTest type
smoke130 slatency with no concurrency
baseline104.5 minload
load20 → 50 → 10011 minload
stress50 → 100 → 200 → 400 → 80010 minstress
stress-1000200 → 500 → 1000, held 5 min9 minstress
stress-1200200 → 500 → 800 → 1200, held 5 min10 minstress
max-throughput100, no think-time2 min 40 sthroughput

Scenario #

Every iteration performs three GET requests — the ones real users make most often: the server list, the details of a random server from that list, and its status. There is a 0.3–0.8 s pause between requests and a 1–3 s pause at the end of the iteration. The max-throughput profile runs the same three requests with no pauses. There are no data-modifying requests (POST, PUT, DELETE, etc.) in the test.

PanelListDetailsStatusItems in the list response
GameAP 3.x/api/servers/api/servers/{id}same request as details¹102
GameAP 4.x/api/servers/api/servers/{id}/api/servers/{id}/status102
PufferPanel/api/servers/api/servers/{id}/api/servers/{id}/status20 (first page)
Pterodactyl/api/client/api/client/servers/{id}…/{id}/resources50 (first page)
Pelican/api/client/api/client/servers/{id}…/{id}/resources50 (first page)

¹ GameAP 3.x has no separate status endpoint — the details request is repeated.

Each panel got 100 identical mock game servers (a stub script printing the time; the servers were never installed or started). The list API responses still differ: GameAP returns all records at once (the rig’s database held 102), Pterodactyl and Pelican return the first page of 50, PufferPanel the first page of 20. The average response size on the load profile is 13–16 KB per request for four panels and 1.6 KB for PufferPanel. A direct cross-panel comparison of list_servers latency is therefore not quite fair — one of the main caveats of this test.

Authentication and limits #

GameAP 3/4, Pterodactyl and Pelican use an API key (Bearer). PufferPanel uses OAuth2 client credentials: the token is fetched once in setup() and reused by all VUs. The OAuth request itself (~58 ms) lands in every profile’s statistics, because setup() runs on every k6 invocation. In the short smoke profile (31–34 requests) it lifts PufferPanel’s average latency from ~2 to ~3.7 ms while the median stays put. On baseline it is one request out of ~2,200 — roughly +0.03 ms (~2%) on the average, with the median and percentiles untouched; on the longer profiles the effect is negligible.

On Pterodactyl and Pelican the API rate limits were raised to 10,000 requests per minute — otherwise the test would have hit the limiter, not the panel.

PHP panel tuning (identical on GameAP 3, Pterodactyl and Pelican) #

; PHP-FPM
pm = dynamic, pm.max_children = 50, pm.start_servers = 10
; OPcache
opcache.memory_consumption = 256, opcache.jit = tracing, opcache.jit_buffer_size = 128M
; MySQL
innodb_buffer_pool_size = 2G, max_connections = 200, innodb_flush_log_at_trx_commit = 2

Tooling #

  • k6 v1.7.1; the default request timeout is 60 s;
  • Prometheus, node_exporter 1.8.2 (VM metrics, 15 s step), process_exporter 0.8.7 (process metrics, 30 s step).

The closed-loop load model #

k6 uses a closed-loop model: a virtual user does not send the next request until it has received the response to the previous one. When a panel slows down, the actual load intensity drops automatically. In an open system — real users, auto-refreshing dashboards, integrations — requests would keep arriving regardless of responses, and a degraded panel would fare even worse. Keep that in mind when reading the stress results: the numbers of the slowed-down panels are an optimistic estimate. More on the effect: coordinated omission.

Notes from setting up the rig #

Panel VM resources during the smoke profile — 1 VU, with the database already holding 100 servers; peak over the profile window, median of three runs:

PanelCPURAM
GameAP 4.x0.4 %449 MB
PufferPanel0.6 %462 MB
GameAP 3.x3.2 %1 049 MB
Pterodactyl2.3 %1 096 MB
Pelican2.9 %1 177 MB

RAM is for the whole virtual machine (MemTotal − MemAvailable), including the OS, the database and auxiliary services.

Notes collected while setting up the rig:

  • Pterodactyl and Pelican cannot work behind NAT — neither the panel nor Wings: the browser connects to Wings directly over HTTP/HTTPS.
  • Pelican requires rebuilding its configuration cache after settings changes, otherwise the panel crashes.

Results #

All numbers below are medians of three runs; the raw reports of every run live in the repository. On reproducibility: on the baseline, load and max-throughput profiles the median latency differs between runs by at most 6.1%, RPS by at most 2.7%. Smoke is too short (28–34 requests per run), so its median spread reaches 22%. On the stress profiles behavior is, expectedly, less stable: GameAP 4’s stress-1200 median across the runs, for example, was 23.7 / 24.6 / 31.9 ms.

Latency by load level #

Median latency by load levellower is betterms, log scale · median of three runs1 ms10 ms100 ms1 s10 s11010080010001200target max VUs of the profileGameAP 4.x — 1 VUs: 1.11 msGameAP 4.x — 10 VUs: 0.82 msGameAP 4.x — 100 VUs: 0.59 msGameAP 4.x — 800 VUs: 0.57 msGameAP 4.x — 1000 VUs: 0.76 msGameAP 4.x — 1200 VUs: 24.6 msPufferPanel — 1 VUs: 2.01 msPufferPanel — 10 VUs: 1.54 msPufferPanel — 100 VUs: 1.37 msPufferPanel — 800 VUs: 1.45 msPufferPanel — 1000 VUs: 114 msPufferPanel — 1200 VUs: 97.2 msGameAP 3.x — 1 VUs: 20.2 msGameAP 3.x — 10 VUs: 9.24 msGameAP 3.x — 100 VUs: 8.78 msGameAP 3.x — 800 VUs: 27.7 msGameAP 3.x — 1000 VUs: 1 481 msGameAP 3.x — 1200 VUs: 1 958 msPterodactyl — 1 VUs: 26.9 msPterodactyl — 10 VUs: 12.7 msPterodactyl — 100 VUs: 20.7 msPterodactyl — 800 VUs: 1 144 msPterodactyl — 1000 VUs: 7 664 msPterodactyl — 1200 VUs: 10 455 msPelican — 1 VUs: 29.0 msPelican — 10 VUs: 16.0 msPelican — 100 VUs: 54.5 msPelican — 800 VUs: 1 520 msPelican — 1000 VUs: 10 501 msPelican — 1200 VUs: 12 807 msPelicanPterodactylGameAP 3.xPufferPanelGameAP 4.x

Median latency, ms:

ProfileGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
smoke (1 VU)1.112.0120.226.929.0
baseline (10 VUs)0.821.549.2412.716.0
load (up to 100 VUs)0.591.378.7820.754.5
stress (up to 800 VUs)0.571.4527.71 1441 520
stress-10000.761141 4817 66410 501
stress-120024.697.21 95810 45512 807

95th percentile, ms:

ProfileGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
smoke (1 VU)1.694.9829.287.0106
baseline (10 VUs)1.452.0819.979.497.0
load (up to 100 VUs)1.201.8513.2154473
stress (up to 800 VUs)2.9258.29089 14611 221
stress-100015.23191 58313 60715 247
stress-12002833302 07814 14417 477

What this shows.

Up to and including 100 VUs, all five panels answer without a single error, and the only question is speed: the Go panels stay within 2 ms median, GameAP 3 around 9 ms, Pterodactyl 13–21 ms, Pelican 16–55 ms. Pelican’s median already climbs on the load profile (54.5 ms, p95 — 473 ms): as the resources section will show, at 100 VUs it is practically out of CPU.

On the stress profiles the groups diverge radically. GameAP 4 passes 800 and 1000 VUs with no median degradation (0.6–0.8 ms), PufferPanel slows to 114 ms at 1000 VUs and starts returning errors, GameAP 3 holds 800 VUs at a 28 ms median (p95 — 0.9 s) and moves to 1.5–2 s beyond 1000. Pterodactyl and Pelican already answer in seconds at 800 VUs, and in 8–13 s (median) at 1000–1200.

Note the gap between the median and p95 on the PHP panels even at low load: Pterodactyl’s baseline median is 12.7 ms while its p95 is 79 ms. Their tail of slow responses is long even where the panel copes overall.

Throughput ceiling #

The max-throughput profile: 100 VUs run the same three requests with no pauses — a 30 s ramp-up, 2 min hold, 10 s ramp-down. RPS is computed as the average over the whole window, ramps included.

Throughput ceiling (max-throughput)higher is betterrequests per second · 100 VUs, no think-time · median of three runs · 0% errors on all panels02505007501 0001 250GameAP 4.xGameAP 4.x: 1 126 req/s1 126PufferPanelPufferPanel: 696 req/s696GameAP 3.xGameAP 3.x: 394 req/s394PterodactylPterodactyl: 93 req/s93PelicanPelican: 76 req/s76
PanelRPSAvg, msMed, msp95, msErrors
GameAP 4.x1 12677.668.91790
PufferPanel6961261212570
GameAP 3.x3942222413020
Pterodactyl939417661 8250
Pelican761 1609452 2910

Not a single panel returned a single error — yet the throughput of the extremes differs 15-fold (1126 vs 76 requests/s). It is worth setting this against CPU utilization: in this profile every panel runs at its limit (95–100% of the VM’s CPU — see the resources table). Identical utilization, results apart by multiples: comparing panels by “CPU load” is meaningless; what matters is how much work a panel gets done per core.

Behavior under overload #

Failed requests on the stress profileslower is better% of all requests (HTTP ≥ 400) · median of three runs0%10%20%30%40%PufferPanel — 800 VUs: 0.09%800 VUsPufferPanel — 1000 VUs: 21.3%1000 VUsGameAP 4.x — 1200 VUs: 15.2%PufferPanel — 1200 VUs: 34.6%1200 VUs00.09000< 0.0121.300015.234.6000GameAP 4.xPufferPanelGameAP 3.xPterodactylPelican

Failed requests (HTTP ≥ 400), % of all requests in the profile:

ProfileGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
load (up to 100 VUs)00000
stress (up to 800 VUs)00.09000
stress-1000< 0.0121.3000
stress-120015.234.6000

The breaking points, per these data. PufferPanel is the first to return errors: isolated ones (0.08–0.12%) already at 800 VUs, 21% at 1000, 35% at 1200 (up to 40% on the list request). GameAP 4 passes 1000 VUs almost clean (median — zero errors; 0.011% in one of the three runs) and returns 14.3–15.5% errors at 1200 VUs. The PHP panels return no errors at all — on any profile.

But “0% errors” here does not mean “the panel works”. At stress-1200, GameAP 3’s p95 is 2.1 s, Pterodactyl’s is 14.1 s, Pelican’s is 17.5 s. Formally every request eventually gets a 200 (k6 waits up to 60 s); in practice a panel answering in 10–17 seconds is unusable. These are two different degradation modes, not “PHP endures and Go does not”:

  • Go panels — fail fast: a share of requests quickly ends in an error, the rest are served with acceptable latency (GameAP 4’s median at 1200 VUs is 25 ms, PufferPanel’s is 97 ms). The client knows immediately that the server is struggling.
  • PHP panels — a queue: PHP-FPM queues the requests, there are no errors, but the response time grows without bound. The client waits without knowing whether an answer will come.

Which mode is “right” is a question of requirements, not of a benchmark. The practical consequence is singular: a PHP panel’s overload will not show up in error monitoring — only in latency.

Achieved RPS on the stress profiles:

ProfileGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
stress (up to 800 VUs)2592572108268
stress-10007166153419073
stress-12007646993499074

Read this table carefully: because of the think-time pauses and the gradual VU ramp, the stress profile physically cannot deliver more than ~260 requests/s (the time-averaged number of active VUs is ≈ 268, with ~3.1 s of pauses per three-request iteration). GameAP 4 and PufferPanel hit the profile’s ceiling, not their own: 259 and 257. GameAP 3 manages 210. Pterodactyl and Pelican, meanwhile, deliver roughly their max-throughput on every stress profile (82–90 and 68–74 requests/s against ceilings of 93 and 76) — the panel serves what it can, the rest piles up in the queue.

Resources and bottlenecks #

Peak CPU of the panel VM, %:

ProfileGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
smoke (1 VU)0.40.63.22.32.9
baseline (10 VUs)0.70.94.88.910.9
load (up to 100 VUs)2.43.825.781.296.8
stress (up to 800 VUs)20.465.3100100100
stress-100040.095.6100100100
stress-120087.796.1100100100
max-throughput96.195.3100100100

The key row is load: the “working” load of 100 concurrent users, which every panel passes with zero errors, costs GameAP 4 and PufferPanel 2–4% CPU, GameAP 3 — 26%, Pterodactyl — 81%, Pelican — 97%. Pterodactyl and Pelican are already running on the edge of saturation at this profile — they have no headroom left for spikes.

Where the CPU goes under overload — processes on the stress profile (800 VUs), peak over the window, mean of runs 2–3:

Per-process CPU at the stress profile (800 VUs)lower is betterpeak over the profile window · 400% = 4 vCPU · mean of runs 2–30%100%200%300%400%GameAP 4.xGameAP 4.x — gameap: 31.5% CPU31.5GameAP 4.x — postgresql: 2.9% CPU2.9PufferPanelPufferPanel — pufferpanel: 73.1% CPU73.1PufferPanel — postgresql: 9.5% CPU9.5GameAP 3.xGameAP 3.x — php-fpm: 318% CPU318GameAP 3.x — mysqld: 31.4% CPU31.4PterodactylPterodactyl — php-fpm: 243% CPU243Pterodactyl — mysqld: 109% CPU109PelicanPelican — php-fpm: 260% CPU260Pelican — mysqld: 94.3% CPU94.3app (Go / php-fpm)database (PostgreSQL / MySQL)
ProcessGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
App (Go / php-fpm)31.573.1318243260
Database (PostgreSQL / MySQL)2.99.531.410994.3

100% = one core, 4 vCPU total. Nginx stayed ≤ 3.2% in every run; Redis, where process_exporter saw it, ≤ 5.1% (the monitoring coverage of the Redis process turned out incomplete — in some runs process_exporter did not track it; it does not change the picture, but it is left out of the table).

The most interesting row is the database one. On Pterodactyl and Pelican, MySQL consumes 94–109% CPU — nearly a full core — while serving 68–82 requests per second. On GameAP 3 the same MySQL 8.0 with the same settings is 31% busy — at 210 requests per second. Per request, Pterodactyl and Pelican burn roughly nine times more database CPU time than GameAP 3. It appears the bottleneck of these panels is not PHP as such but their database workload. The cause was not profiled and remains an open question; an EXPLAIN pass over the list endpoints’ queries suggests itself.

Peak RAM of the VM, MB:

ProfileGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
smoke (1 VU)4494621 0491 0961 177
stress (up to 800 VUs)4805391 2881 4631 606
stress-12007017441 3541 4691 610
max-throughput6416721 3421 4451 556

Process RSS at stress-800 (mean of runs 2–3): the application — 65 MB for GameAP 4 and 107 MB for PufferPanel versus 2.7–4.2 GB of total RSS across fifty php-fpm workers; the database — 107 MB of PostgreSQL on GameAP 4 versus ~613–641 MB of MySQL. Two caveats: php-fpm worker RSS counts shared memory many times over (the real VM consumption is in the table above), and PufferPanel’s PostgreSQL RSS varied noticeably between runs (721 MB in the first, ~440 MB in the second and third).

And one anomaly that honestly remains unexplained: PufferPanel is the only panel that actively writes to disk under load — peaks of 140–150 write IOPS versus 4 for GameAP 4 and 21–24 for the rest. The cause (logs? auditing? PostgreSQL usage specifics?) was not investigated.

What the individual endpoints show #

Median latency per endpoint on the load profile, ms:

EndpointGameAP 4.xPufferPanelGameAP 3.xPterodactylPelican
list_servers0.871.4111.187.3141
server_details0.580.748.3412.725.6
server_status0.381.498.3411.520.9

The server list is the heaviest request on every panel, but on Pterodactyl and Pelican the gap is dramatic: the list is 7–8 times slower than the details request (87 and 141 ms versus 13 and 26 ms). It is the list request that degrades first under load.

A reminder of the caveat from the methodology: the list response size differs between panels (102 records for GameAP, 50 for Pterodactyl/Pelican, 20 for PufferPanel), so comparing the list_servers row across panels is not fair. What can be read from this table is the ratio between endpoints within one panel — and the fact that GameAP 4 returns its full list of 102 records (~13 KB) faster than any PHP panel returns its first page.

The three head-to-head comparisons #

GameAP 3 → GameAP 4. This is what the rewrite was for: the baseline median is 9.24 vs 0.82 ms (11×), the load median is 8.78 vs 0.59 ms (15×), the throughput ceiling is 394 vs 1126 requests/s (2.9×), whole-VM memory is 1.0–1.4 GB vs 0.45–0.70 GB, load-profile CPU is 26% vs 2.4%.

GameAP 4 vs PufferPanel. Both are Go + PostgreSQL, and both are an order of magnitude faster than the PHP group. Between the two: GameAP 4 is 1.9–2.3× faster by median on baseline/load and 1.6× by the throughput ceiling (1126 vs 696), breaks later under overload (15% errors at 1200 VUs versus PufferPanel’s 21% already at 1000 and first isolated errors at 800) and barely touches the disk. Keep in mind, though, that in this scenario PufferPanel returns an order of magnitude less data per list request (20 records, ~1.6 KB vs 102 records, ~13 KB) — the gap was measured on work that is lighter for PufferPanel.

Pterodactyl vs Pelican. The fork turned out slower than the original on every profile: on load the median is 54.5 vs 20.7 ms, p95 — 473 vs 154 ms, the ceiling — 76 vs 93 requests/s, and CPU saturation arrives earlier (97% already on load vs 81%). By process, Pelican’s php-fpm is busier (260 vs 243%) and its MySQL less so (94 vs 109%). The causes of the differences were not investigated; at test time Pelican was a young fork (1.0.x), and its performance profile may still change.

Limitations #

A list of what limits the conclusions of this test.

  1. Different databases between the groups. The Go panels ran on PostgreSQL, the PHP panels on MySQL 8.0 (in both cases the configurations recommended by the panels’ developers). Any “Go vs PHP” comparison here really means “Go+PostgreSQL vs PHP+MySQL” — this test does not separate the database’s contribution from the language’s and the architecture’s. Within each group the databases are identical.
  2. Different list_servers response sizes. Every database holds 100 servers, but GameAP returns the whole list (102 records), Pterodactyl/Pelican the first page of 50, PufferPanel the first page of 20; response sizes range from 1.6 to 16 KB. The test did not normalize pagination to a common page size. Direct cross-panel list-latency comparisons are therefore skewed; in whose favor depends on the pair (the skew makes PufferPanel’s work lighter and GameAP’s heavier).
  3. GameAP 3 has no status endpoint: its server_status is a repeat of the details request, so a third of the scenario on GameAP 3 is not equivalent to the other panels.
  4. Read-only, API-only. No writes, no WebSocket, no UI; the mock servers were never installed or started, and the daemons on the neighboring VMs sat effectively idle. This is a test of the panels’ HTTP/API layer, not of the panels as a whole.
  5. k6’s closed-loop model (see methodology): the degraded panels automatically received less load, so their stress numbers are an optimistic estimate. The k6 request timeout is 60 s.
  6. Warm-up. One smoke warm-up run (30 s) before the profile series, and a service restart before every profile. On the panels that are not CPU-bound (GameAP 3/4, PufferPanel) the average latency on baseline is higher than on the heavier load profile (0.85 → 0.66 ms; 1.49 → 1.27; 10.9 → 9.7) — part of the baseline window is apparently still warm-up. It does not change the conclusions, but the absolute baseline numbers are slightly inflated.
  7. Restart asymmetry. Before every profile the PHP panels had php-fpm, nginx and MySQL restarted (buffer pool and caches flushed; on GameAP 3 also Redis), PufferPanel had its application restarted (PostgreSQL kept running), and GameAP 4 only had nginx restarted (the application and PostgreSQL were not). The PHP panels started every profile “colder”. The profiles’ ramp phases partially compensate for this, but the asymmetry was not eliminated entirely.
  8. Versions recorded at the major level (GameAP 4.x / 3.x, Pterodactyl 1.11.x, Pelican 1.0.x, PufferPanel 3.x, April 2026). GameAP 4 is functionally younger and simpler than the rest — doing less work per request may be part of its advantage; that was not quantified.
  9. Modified limits. On Pterodactyl and Pelican the rate limits were raised to 10,000 requests/min — a stock installation would have cut the load off earlier.
  10. Monitoring. CPU and RAM are whole-VM metrics at a 15 s step (short spikes may be smoothed out); process metrics have a 30 s step, and the coverage of the Redis process across runs is incomplete; PufferPanel’s smoke statistics include the OAuth request from setup(). One hardware configuration, one VM layout, 100 servers in the database — on different hardware and data volumes the absolute numbers will differ.

Conclusions #

Back to the three questions from the goals.

How different are the panels at normal load? On the load profile (up to 100 concurrent users) all five panels work with zero errors, and latency differs by an order of magnitude or more: 0.6–1.4 ms median for the Go panels, ~9 ms for GameAP 3, 21–55 ms for Pterodactyl/Pelican. Let’s say it plainly: for a human in a browser, even 55 ms is fast. If you run one panel, a dozen servers and no automation, any of the contenders will feel instant. The difference becomes practical with integrations and automation, with bulk operations, on cheap hardware — and in the price of that speed: the same 100 VUs cost Pelican 97% CPU and GameAP 4 — 2.4%.

Where is the breaking point and how does a panel break? By throughput ceiling: 1126 (GameAP 4) → 696 (PufferPanel) → 394 (GameAP 3) → 93 (Pterodactyl) → 76 (Pelican) requests/s. Under overload there are two modes: the Go panels fail fast with errors (PufferPanel from 800–1000 VUs, GameAP 4 from 1200), the PHP panels return no errors but answer in seconds to tens of seconds. The practical takeaway for monitoring: a PHP panel’s overload is visible only in latency — there will be no error alerts.

What is the bottleneck? For GameAP 4 — the application’s own CPU (the database is barely loaded). For PufferPanel — the application plus notable disk writes whose cause was not investigated. For GameAP 3 — php-fpm (318% CPU with MySQL at 31%). For Pterodactyl and Pelican the picture is different: alongside php-fpm, MySQL eats nearly a full core — per request that is roughly nine times more database CPU time than GameAP 3 spends on the same MySQL. This is the most interesting result of the test, and it deserves a separate investigation with query profiling.

This test measures one slice — the read speed of the HTTP/API layer. It says nothing about features, usability, security or ecosystem, and the choice of a panel is driven by those too. GameAP 4 won this benchmark, but it is also the youngest contender; Pterodactyl lost on the numbers — yet remains the most widespread panel with the largest ecosystem. What to do with these facts is up to you.

What’s next #

  • Phase 2 — game server management: mass start/stop of N servers, steady-state with running servers, parallel WebSocket consoles, server creation via the API. A fake-game-server is being prepared for this — a binary that mimics a real game server.
  • Scaling by data volume: runs with 1 / 100 / 1000 servers in the database, to measure how the list endpoints’ latency depends on the database size.
  • Open questions from this phase: PufferPanel’s disk writes, profiling of Pterodactyl’s/Pelican’s MySQL queries, the difference between Pelican and Pterodactyl.

The scripts, configurations and raw data of all runs are in the game-panels-benchmark repository. If you find a flaw in the methodology or the interpretation — open an issue: it will be re-checked and a correction published.