<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>News — GameAP Blog</title><link>https://blog.gameap.com/news/</link><description>Latest posts from the GameAP blog</description><language>en-US</language><generator>Hugo 0.164.0</generator><atom:link href="https://blog.gameap.com/news/index.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Mon, 03 Aug 2026 13:00:00 +0300</lastBuildDate><item><title>Load testing game server control panels</title><link>https://blog.gameap.com/news/game-panels-performance-tests/</link><pubDate>Mon, 03 Aug 2026 13:00:00 +0300</pubDate><guid>https://blog.gameap.com/news/game-panels-performance-tests/</guid><description>GameAP 3 and 4, PufferPanel, Pterodactyl and Pelican under load: the test rig, the methodology and the API benchmark results.</description><content:encoded>&lt;p&gt;This article is about load testing five popular game server control panels: &lt;strong&gt;GameAP 3&lt;/strong&gt;, &lt;strong&gt;GameAP 4&lt;/strong&gt;, &lt;strong&gt;PufferPanel&lt;/strong&gt;, &lt;strong&gt;Pterodactyl&lt;/strong&gt; and &lt;strong&gt;Pelican&lt;/strong&gt;. 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 &lt;a href="#limitations"&gt;limitations&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer.&lt;/strong&gt; 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 &lt;a href="https://github.com/et-nik/game-panels-benchmark" target="_blank" rel="noopener"&gt;GitHub repository&lt;/a&gt;. 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The short version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;The throughput ceiling on this rig:
&lt;ul&gt;
&lt;li&gt;1126 requests/s for GameAP 4&lt;/li&gt;
&lt;li&gt;696 req/s for PufferPanel&lt;/li&gt;
&lt;li&gt;394 req/s for GameAP 3&lt;/li&gt;
&lt;li&gt;93 req/s for Pterodactyl&lt;/li&gt;
&lt;li&gt;and 76 req/s for Pelican&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;style&gt;
.bench-chart { --c-g4:#2a78d6; --c-pp:#eb6834; --c-g3:#1baf7a; --c-pt:#eda100; --c-pl:#e87ba4; --c-app:#008300; --c-db:#4a3aa7; }
[data-theme="dark"] .bench-chart { --c-g4:#3987e5; --c-pp:#d95926; --c-g3:#199e70; --c-pt:#c98500; --c-pl:#d55181; --c-app:#008300; --c-db:#9085e9; }
@media (prefers-color-scheme: dark) {
 :root:not([data-theme]) .bench-chart { --c-g4:#3987e5; --c-pp:#d95926; --c-g3:#199e70; --c-pt:#c98500; --c-pl:#d55181; --c-app:#008300; --c-db:#9085e9; }
}
&lt;/style&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 268" role="img" aria-label="Throughput ceiling: GameAP 4 — 1126 requests per second, PufferPanel — 696, GameAP 3 — 394, Pterodactyl — 93, Pelican — 76. Higher is better" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Throughput ceiling&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;higher is better&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;requests per second · median of three runs&lt;/text&gt;
&lt;line x1="66" y1="198.4" x2="700" y2="198.4" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="202.4" fill="var(--muted)" font-size="11" text-anchor="end"&gt;250&lt;/text&gt;
&lt;line x1="66" y1="160.8" x2="700" y2="160.8" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="164.8" fill="var(--muted)" font-size="11" text-anchor="end"&gt;500&lt;/text&gt;
&lt;line x1="66" y1="123.2" x2="700" y2="123.2" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="127.2" fill="var(--muted)" font-size="11" text-anchor="end"&gt;750&lt;/text&gt;
&lt;line x1="66" y1="85.6" x2="700" y2="85.6" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="89.6" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 000&lt;/text&gt;
&lt;line x1="66" y1="48.0" x2="700" y2="48.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="52.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 250&lt;/text&gt;
&lt;line x1="66" y1="236" x2="700" y2="236" stroke="var(--muted)" stroke-width="1"/&gt;
&lt;text x="58.0" y="240.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;0&lt;/text&gt;
&lt;path d="M91.4,236.0 v-165.4 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v165.4 z" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x: 1 126 requests/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="129.4" y="58.6" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;1 126&lt;/text&gt;
&lt;text x="129.4" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;GameAP 4.x&lt;/text&gt;
&lt;path d="M218.2,236.0 v-100.7 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v100.7 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel: 696 requests/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="256.2" y="123.3" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;696&lt;/text&gt;
&lt;text x="256.2" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;PufferPanel&lt;/text&gt;
&lt;path d="M345.0,236.0 v-55.3 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v55.3 z" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x: 394 requests/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="383.0" y="168.7" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;394&lt;/text&gt;
&lt;text x="383.0" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;GameAP 3.x&lt;/text&gt;
&lt;path d="M471.8,236.0 v-10.0 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v10.0 z" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl: 93 requests/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="509.8" y="214.0" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;93&lt;/text&gt;
&lt;text x="509.8" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;Pterodactyl&lt;/text&gt;
&lt;path d="M598.6,236.0 v-7.4 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v7.4 z" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican: 76 requests/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="636.6" y="216.6" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;76&lt;/text&gt;
&lt;text x="636.6" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;Pelican&lt;/text&gt;
&lt;/svg&gt;
&lt;h2 id="goals"&gt;Goals &lt;a class="hanchor" href="#goals" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Find the breaking point: the limit at which a panel still works properly.&lt;/li&gt;
&lt;li&gt;Compare the alternatives under conditions as close as possible.&lt;/li&gt;
&lt;li&gt;Compare GameAP 3 and GameAP 4: what exactly the rewrite from PHP to Go achieved.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Besides the overall comparison of all five panels, three head-to-head groups are of particular interest:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GameAP 3.x vs GameAP 4.x&lt;/strong&gt;: a complete rewrite in Go with an updated architecture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameAP 4.x vs PufferPanel&lt;/strong&gt;: a comparison of panels written in Go.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pterodactyl vs Pelican&lt;/strong&gt;: the original panel versus its fork.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-panels"&gt;The panels &lt;a class="hanchor" href="#the-panels" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;Version&lt;/th&gt;
					&lt;th&gt;Stack&lt;/th&gt;
					&lt;th&gt;Database&lt;/th&gt;
					&lt;th&gt;Daemon&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;4.x&lt;/td&gt;
					&lt;td&gt;Go&lt;/td&gt;
					&lt;td&gt;PostgreSQL&lt;/td&gt;
					&lt;td&gt;gameap-daemon&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;3.x&lt;/td&gt;
					&lt;td&gt;Go&lt;/td&gt;
					&lt;td&gt;PostgreSQL&lt;/td&gt;
					&lt;td&gt;built-in&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;3.x&lt;/td&gt;
					&lt;td&gt;PHP 8.4 / Laravel&lt;/td&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;gameap-daemon&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;1.11.x&lt;/td&gt;
					&lt;td&gt;PHP 8.4 / Laravel&lt;/td&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;Wings (Docker)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;1.0.x&lt;/td&gt;
					&lt;td&gt;PHP 8.4 / Laravel, Pterodactyl fork&lt;/td&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;Wings (Docker)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id="what-is-being-tested"&gt;What is being tested &lt;a class="hanchor" href="#what-is-being-tested" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;HTTP/API latency at different concurrency levels: load testing (10–100 VUs);&lt;/li&gt;
&lt;li&gt;behavior under overload (degradation, errors, resource consumption): stress testing (800–1200 VUs);&lt;/li&gt;
&lt;li&gt;the throughput ceiling with no pauses between requests: throughput testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Out of scope for this article:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;write requests;&lt;/li&gt;
&lt;li&gt;UX, features, security, ecosystem;&lt;/li&gt;
&lt;li&gt;working with real game servers and their files;&lt;/li&gt;
&lt;li&gt;long-term stability: no soak or spike tests were run.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="test-rig"&gt;Test rig &lt;a class="hanchor" href="#test-rig" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Server (bare-metal, Selectel):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: Intel Xeon E-2456 (Raptor Lake, 6C/12T, 3.3 GHz base / 5.1 GHz turbo, 18 MB L3);&lt;/li&gt;
&lt;li&gt;RAM: 32 GB DDR5 ECC (2×16 GB, 4400 MT/s);&lt;/li&gt;
&lt;li&gt;storage: 2× Samsung 990 PRO 1TB NVMe in mdadm RAID1;&lt;/li&gt;
&lt;li&gt;hypervisor: Proxmox VE 9.1.5 on Debian 13, kernel 6.17.9-1-pve.&lt;/li&gt;
&lt;/ul&gt;
&lt;svg viewBox="0 0 720 330" role="img" aria-label="Rig diagram: a bare-metal server running Proxmox hosts four virtual machines — k6-runner, panel, daemon and monitoring" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
 &lt;defs&gt;
 &lt;marker id="ar-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"&gt;&lt;path d="M0,0L10,5L0,10z" fill="var(--accent)"/&gt;&lt;/marker&gt;
 &lt;marker id="ar-m" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"&gt;&lt;path d="M0,0L10,5L0,10z" fill="var(--muted)"/&gt;&lt;/marker&gt;
 &lt;/defs&gt;
 &lt;text x="8" y="14" fill="var(--muted)"&gt;Bare-metal · Selectel · Xeon E-2456 · 32 GB DDR5 · Proxmox VE 9&lt;/text&gt;
 &lt;rect x="8" y="26" width="704" height="296" rx="10" fill="none" stroke="var(--border)"/&gt;
 &lt;rect x="36" y="56" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="52" y="82" fill="var(--fg)" font-weight="600" font-size="13"&gt;k6-runner&lt;/text&gt;
 &lt;text x="52" y="104" fill="var(--muted)"&gt;4 vCPU · 4 GB&lt;/text&gt;
 &lt;text x="52" y="124" fill="var(--muted)"&gt;load generator&lt;/text&gt;
 &lt;rect x="272" y="56" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="288" y="82" fill="var(--fg)" font-weight="600" font-size="13"&gt;Panel (API)&lt;/text&gt;
 &lt;text x="288" y="104" fill="var(--muted)"&gt;4 vCPU · 8 GB · 40 GB&lt;/text&gt;
 &lt;text x="288" y="124" fill="var(--muted)"&gt;Ubuntu 24.04 LTS&lt;/text&gt;
 &lt;rect x="508" y="56" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="524" y="82" fill="var(--fg)" font-weight="600" font-size="13"&gt;Daemon / agent&lt;/text&gt;
 &lt;text x="524" y="104" fill="var(--muted)"&gt;6 vCPU · 12 GB · 80 GB&lt;/text&gt;
 &lt;text x="524" y="124" fill="var(--muted)"&gt;Ubuntu 24.04 LTS&lt;/text&gt;
 &lt;rect x="36" y="200" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="52" y="226" fill="var(--fg)" font-weight="600" font-size="13"&gt;Monitoring&lt;/text&gt;
 &lt;text x="52" y="248" fill="var(--muted)"&gt;2 vCPU · 4 GB&lt;/text&gt;
 &lt;text x="52" y="268" fill="var(--muted)"&gt;Prometheus + Grafana&lt;/text&gt;
 &lt;line x1="212" y1="102" x2="266" y2="102" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#ar-a)"/&gt;
 &lt;text x="239" y="92" fill="var(--accent)" font-size="11" text-anchor="middle"&gt;HTTP&lt;/text&gt;
 &lt;line x1="448" y1="102" x2="502" y2="102" stroke="var(--muted)" stroke-width="1.5" marker-end="url(#ar-m)"/&gt;
 &lt;path d="M 596 148 L 596 246 L 218 246" fill="none" stroke="var(--muted)" stroke-dasharray="4 4" marker-end="url(#ar-m)"/&gt;
 &lt;path d="M 360 148 L 360 246" fill="none" stroke="var(--muted)" stroke-dasharray="4 4"/&gt;
 &lt;text x="430" y="264" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;metrics: node_exporter → Prometheus&lt;/text&gt;
&lt;/svg&gt;
&lt;h3 id="virtual-machine-configuration"&gt;Virtual machine configuration &lt;a class="hanchor" href="#virtual-machine-configuration" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;VM&lt;/th&gt;
					&lt;th&gt;vCPU&lt;/th&gt;
					&lt;th&gt;RAM&lt;/th&gt;
					&lt;th&gt;Disk&lt;/th&gt;
					&lt;th&gt;Role&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Panel&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;8 GB&lt;/td&gt;
					&lt;td&gt;40 GB&lt;/td&gt;
					&lt;td&gt;control panel (API), Ubuntu 24.04 LTS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Daemon&lt;/td&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;12 GB&lt;/td&gt;
					&lt;td&gt;80 GB&lt;/td&gt;
					&lt;td&gt;game server daemon/agent, Ubuntu 24.04 LTS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;k6-runner&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;4 GB&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;load generator&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Monitoring&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;4 GB&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;Prometheus + Grafana&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="host-tuning"&gt;Host tuning &lt;a class="hanchor" href="#host-tuning" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU governor: performance;&lt;/li&gt;
&lt;li&gt;C-states limited to C1;&lt;/li&gt;
&lt;li&gt;Turbo Boost enabled;&lt;/li&gt;
&lt;li&gt;swap disabled on all virtual machines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="methodology"&gt;Methodology &lt;a class="hanchor" href="#methodology" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Every panel went through &lt;strong&gt;three full independent runs&lt;/strong&gt; of the whole profile series (April 18–19, 2026). All numbers in this article are &lt;strong&gt;medians of the three runs&lt;/strong&gt; unless explicitly stated otherwise.&lt;/p&gt;
&lt;p&gt;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&amp;rsquo;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 &lt;a href="#limitations"&gt;limitations&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="load-profiles"&gt;Load profiles &lt;a class="hanchor" href="#load-profiles" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;VUs&lt;/th&gt;
					&lt;th&gt;Duration&lt;/th&gt;
					&lt;th&gt;Test type&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke&lt;/td&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;30 s&lt;/td&gt;
					&lt;td&gt;latency with no concurrency&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;4.5 min&lt;/td&gt;
					&lt;td&gt;load&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load&lt;/td&gt;
					&lt;td&gt;20 → 50 → 100&lt;/td&gt;
					&lt;td&gt;11 min&lt;/td&gt;
					&lt;td&gt;load&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress&lt;/td&gt;
					&lt;td&gt;50 → 100 → 200 → 400 → 800&lt;/td&gt;
					&lt;td&gt;10 min&lt;/td&gt;
					&lt;td&gt;stress&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;200 → 500 → 1000, held 5 min&lt;/td&gt;
					&lt;td&gt;9 min&lt;/td&gt;
					&lt;td&gt;stress&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;200 → 500 → 800 → 1200, held 5 min&lt;/td&gt;
					&lt;td&gt;10 min&lt;/td&gt;
					&lt;td&gt;stress&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;max-throughput&lt;/td&gt;
					&lt;td&gt;100, no think-time&lt;/td&gt;
					&lt;td&gt;2 min 40 s&lt;/td&gt;
					&lt;td&gt;throughput&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="scenario"&gt;Scenario &lt;a class="hanchor" href="#scenario" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;List&lt;/th&gt;
					&lt;th&gt;Details&lt;/th&gt;
					&lt;th&gt;Status&lt;/th&gt;
					&lt;th&gt;Items in the list response&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;same request as details¹&lt;/td&gt;
					&lt;td&gt;102&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}/status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;102&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}/status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;20 (first page)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;…/{id}/resources&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;50 (first page)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;…/{id}/resources&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;50 (first page)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;¹ GameAP 3.x has no separate status endpoint — the details request is repeated.&lt;/p&gt;
&lt;p&gt;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&amp;rsquo;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 &lt;code&gt;list_servers&lt;/code&gt; latency is therefore not quite fair — one of the main caveats of this test.&lt;/p&gt;
&lt;h3 id="authentication-and-limits"&gt;Authentication and limits &lt;a class="hanchor" href="#authentication-and-limits" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;GameAP 3/4, Pterodactyl and Pelican use an API key (Bearer). PufferPanel uses OAuth2 client credentials: the token is fetched once in &lt;code&gt;setup()&lt;/code&gt; and reused by all VUs. The OAuth request itself (~58 ms) lands in every profile&amp;rsquo;s statistics, because &lt;code&gt;setup()&lt;/code&gt; runs on every k6 invocation. In the short smoke profile (31–34 requests) it lifts PufferPanel&amp;rsquo;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3 id="php-panel-tuning-identical-on-gameap-3-pterodactyl-and-pelican"&gt;PHP panel tuning (identical on GameAP 3, Pterodactyl and Pelican) &lt;a class="hanchor" href="#php-panel-tuning-identical-on-gameap-3-pterodactyl-and-pelican" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; PHP-FPM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;pm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dynamic, pm.max_children = 50, pm.start_servers = 10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; OPcache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;opcache.memory_consumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256, opcache.jit = tracing, opcache.jit_buffer_size = 128M&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; MySQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;innodb_buffer_pool_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2G, max_connections = 200, innodb_flush_log_at_trx_commit = 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="tooling"&gt;Tooling &lt;a class="hanchor" href="#tooling" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;k6 v1.7.1; the default request timeout is 60 s;&lt;/li&gt;
&lt;li&gt;Prometheus, node_exporter 1.8.2 (VM metrics, 15 s step), process_exporter 0.8.7 (process metrics, 30 s step).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="the-closed-loop-load-model"&gt;The closed-loop load model &lt;a class="hanchor" href="#the-closed-loop-load-model" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;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: &lt;a href="https://www.scylladb.com/2021/04/22/on-coordinated-omission/" target="_blank" rel="noopener"&gt;coordinated omission&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="notes-from-setting-up-the-rig"&gt;Notes from setting up the rig &lt;a class="hanchor" href="#notes-from-setting-up-the-rig" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;CPU&lt;/th&gt;
					&lt;th&gt;RAM&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;0.4 %&lt;/td&gt;
					&lt;td&gt;449 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;0.6 %&lt;/td&gt;
					&lt;td&gt;462 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;3.2 %&lt;/td&gt;
					&lt;td&gt;1 049 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;2.3 %&lt;/td&gt;
					&lt;td&gt;1 096 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;2.9 %&lt;/td&gt;
					&lt;td&gt;1 177 MB&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RAM is for the whole virtual machine (MemTotal − MemAvailable), including the OS, the database and auxiliary services.&lt;/p&gt;
&lt;p&gt;Notes collected while setting up the rig:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pterodactyl&lt;/strong&gt; and &lt;strong&gt;Pelican&lt;/strong&gt; cannot work behind NAT — neither the panel nor Wings: the browser connects to Wings directly over HTTP/HTTPS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pelican&lt;/strong&gt; requires rebuilding its configuration cache after settings changes, otherwise the panel crashes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="results"&gt;Results &lt;a class="hanchor" href="#results" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All numbers below are medians of three runs; the raw reports of every run live in the &lt;a href="https://github.com/et-nik/game-panels-benchmark" target="_blank" rel="noopener"&gt;repository&lt;/a&gt;. 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&amp;rsquo;s stress-1200 median across the runs, for example, was 23.7 / 24.6 / 31.9 ms.&lt;/p&gt;
&lt;h3 id="latency-by-load-level"&gt;Latency by load level &lt;a class="hanchor" href="#latency-by-load-level" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 430" role="img" aria-label="Median latency by load level. Go panels stay in milliseconds up to 800–1000 VUs, PHP panels climb to seconds" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Median latency by load level&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;lower is better&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;ms, log scale · median of three runs&lt;/text&gt;
&lt;line x1="56" y1="355.9" x2="540" y2="355.9" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="359.9" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 ms&lt;/text&gt;
&lt;line x1="56" y1="285.2" x2="540" y2="285.2" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="289.2" fill="var(--muted)" font-size="11" text-anchor="end"&gt;10 ms&lt;/text&gt;
&lt;line x1="56" y1="214.6" x2="540" y2="214.6" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="218.6" fill="var(--muted)" font-size="11" text-anchor="end"&gt;100 ms&lt;/text&gt;
&lt;line x1="56" y1="143.9" x2="540" y2="143.9" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="147.9" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 s&lt;/text&gt;
&lt;line x1="56" y1="73.3" x2="540" y2="73.3" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="77.3" fill="var(--muted)" font-size="11" text-anchor="end"&gt;10 s&lt;/text&gt;
&lt;line x1="56" y1="384" x2="540" y2="384" stroke="var(--muted)" stroke-width="1"/&gt;
&lt;text x="96.3" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1&lt;/text&gt;
&lt;text x="177.0" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;10&lt;/text&gt;
&lt;text x="257.7" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;100&lt;/text&gt;
&lt;text x="338.3" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;800&lt;/text&gt;
&lt;text x="419.0" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1000&lt;/text&gt;
&lt;text x="499.7" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1200&lt;/text&gt;
&lt;text x="298.0" y="416.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;target max VUs of the profile&lt;/text&gt;
&lt;polyline points="96.3,352.7 177.0,361.9 257.7,371.9 338.3,373.1 419.0,364.2 499.7,257.6" fill="none" stroke="var(--c-g4)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="352.7" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1 VUs: 1.11 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="361.9" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 10 VUs: 0.82 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="371.9" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 100 VUs: 0.59 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="373.1" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 800 VUs: 0.57 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="364.2" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1000 VUs: 0.76 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="257.6" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1200 VUs: 24.6 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,334.4 177.0,342.6 257.7,346.3 338.3,344.6 419.0,210.6 499.7,215.5" fill="none" stroke="var(--c-pp)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="334.4" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1 VUs: 2.01 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="342.6" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 10 VUs: 1.54 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="346.3" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 100 VUs: 1.37 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="344.6" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 800 VUs: 1.45 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="210.6" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1000 VUs: 114 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="215.5" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1200 VUs: 97.2 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,263.7 177.0,287.7 257.7,289.2 338.3,254.0 419.0,131.9 499.7,123.3" fill="none" stroke="var(--c-g3)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="263.7" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 1 VUs: 20.2 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="287.7" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 10 VUs: 9.24 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="289.2" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 100 VUs: 8.78 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="254.0" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 800 VUs: 27.7 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="131.9" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 1000 VUs: 1 481 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="123.3" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 1200 VUs: 1 958 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,254.9 177.0,277.8 257.7,262.9 338.3,139.8 419.0,81.4 499.7,71.9" fill="none" stroke="var(--c-pt)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="254.9" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 1 VUs: 26.9 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="277.8" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 10 VUs: 12.7 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="262.9" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 100 VUs: 20.7 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="139.8" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 800 VUs: 1 144 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="81.4" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 1000 VUs: 7 664 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="71.9" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 1200 VUs: 10 455 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,252.6 177.0,270.8 257.7,233.2 338.3,131.1 419.0,71.8 499.7,65.7" fill="none" stroke="var(--c-pl)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="252.6" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 1 VUs: 29.0 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="270.8" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 10 VUs: 16.0 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="233.2" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 100 VUs: 54.5 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="131.1" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 800 VUs: 1 520 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="71.8" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 1000 VUs: 10 501 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="65.7" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 1200 VUs: 12 807 ms&lt;/title&gt;&lt;/circle&gt;
&lt;rect x="550" y="60.7" width="9" height="9" rx="2" fill="var(--c-pl)"/&gt;
&lt;text x="564.0" y="69.7" fill="var(--fg)" font-size="12" text-anchor="start"&gt;Pelican&lt;/text&gt;
&lt;rect x="550" y="75.7" width="9" height="9" rx="2" fill="var(--c-pt)"/&gt;
&lt;text x="564.0" y="84.7" fill="var(--fg)" font-size="12" text-anchor="start"&gt;Pterodactyl&lt;/text&gt;
&lt;rect x="550" y="118.3" width="9" height="9" rx="2" fill="var(--c-g3)"/&gt;
&lt;text x="564.0" y="127.3" fill="var(--fg)" font-size="12" text-anchor="start"&gt;GameAP 3.x&lt;/text&gt;
&lt;rect x="550" y="210.5" width="9" height="9" rx="2" fill="var(--c-pp)"/&gt;
&lt;text x="564.0" y="219.5" fill="var(--fg)" font-size="12" text-anchor="start"&gt;PufferPanel&lt;/text&gt;
&lt;rect x="550" y="252.6" width="9" height="9" rx="2" fill="var(--c-g4)"/&gt;
&lt;text x="564.0" y="261.6" fill="var(--fg)" font-size="12" text-anchor="start"&gt;GameAP 4.x&lt;/text&gt;
&lt;/svg&gt;
&lt;p&gt;Median latency, ms:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;1.11&lt;/td&gt;
					&lt;td&gt;2.01&lt;/td&gt;
					&lt;td&gt;20.2&lt;/td&gt;
					&lt;td&gt;26.9&lt;/td&gt;
					&lt;td&gt;29.0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline (10 VUs)&lt;/td&gt;
					&lt;td&gt;0.82&lt;/td&gt;
					&lt;td&gt;1.54&lt;/td&gt;
					&lt;td&gt;9.24&lt;/td&gt;
					&lt;td&gt;12.7&lt;/td&gt;
					&lt;td&gt;16.0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load (up to 100 VUs)&lt;/td&gt;
					&lt;td&gt;0.59&lt;/td&gt;
					&lt;td&gt;1.37&lt;/td&gt;
					&lt;td&gt;8.78&lt;/td&gt;
					&lt;td&gt;20.7&lt;/td&gt;
					&lt;td&gt;54.5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (up to 800 VUs)&lt;/td&gt;
					&lt;td&gt;0.57&lt;/td&gt;
					&lt;td&gt;1.45&lt;/td&gt;
					&lt;td&gt;27.7&lt;/td&gt;
					&lt;td&gt;1 144&lt;/td&gt;
					&lt;td&gt;1 520&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;0.76&lt;/td&gt;
					&lt;td&gt;114&lt;/td&gt;
					&lt;td&gt;1 481&lt;/td&gt;
					&lt;td&gt;7 664&lt;/td&gt;
					&lt;td&gt;10 501&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;24.6&lt;/td&gt;
					&lt;td&gt;97.2&lt;/td&gt;
					&lt;td&gt;1 958&lt;/td&gt;
					&lt;td&gt;10 455&lt;/td&gt;
					&lt;td&gt;12 807&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;95th percentile, ms:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;1.69&lt;/td&gt;
					&lt;td&gt;4.98&lt;/td&gt;
					&lt;td&gt;29.2&lt;/td&gt;
					&lt;td&gt;87.0&lt;/td&gt;
					&lt;td&gt;106&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline (10 VUs)&lt;/td&gt;
					&lt;td&gt;1.45&lt;/td&gt;
					&lt;td&gt;2.08&lt;/td&gt;
					&lt;td&gt;19.9&lt;/td&gt;
					&lt;td&gt;79.4&lt;/td&gt;
					&lt;td&gt;97.0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load (up to 100 VUs)&lt;/td&gt;
					&lt;td&gt;1.20&lt;/td&gt;
					&lt;td&gt;1.85&lt;/td&gt;
					&lt;td&gt;13.2&lt;/td&gt;
					&lt;td&gt;154&lt;/td&gt;
					&lt;td&gt;473&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (up to 800 VUs)&lt;/td&gt;
					&lt;td&gt;2.92&lt;/td&gt;
					&lt;td&gt;58.2&lt;/td&gt;
					&lt;td&gt;908&lt;/td&gt;
					&lt;td&gt;9 146&lt;/td&gt;
					&lt;td&gt;11 221&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;15.2&lt;/td&gt;
					&lt;td&gt;319&lt;/td&gt;
					&lt;td&gt;1 583&lt;/td&gt;
					&lt;td&gt;13 607&lt;/td&gt;
					&lt;td&gt;15 247&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;283&lt;/td&gt;
					&lt;td&gt;330&lt;/td&gt;
					&lt;td&gt;2 078&lt;/td&gt;
					&lt;td&gt;14 144&lt;/td&gt;
					&lt;td&gt;17 477&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;What this shows.&lt;/p&gt;
&lt;p&gt;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&amp;rsquo;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Note the gap between the median and p95 on the PHP panels even at low load: Pterodactyl&amp;rsquo;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.&lt;/p&gt;
&lt;h3 id="throughput-ceiling"&gt;Throughput ceiling &lt;a class="hanchor" href="#throughput-ceiling" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 268" role="img" aria-label="Throughput ceiling (max-throughput)" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Throughput ceiling (max-throughput)&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;higher is better&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;requests per second · 100 VUs, no think-time · median of three runs · 0% errors on all panels&lt;/text&gt;
&lt;line x1="118.0" y1="48" x2="118.0" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="118.0" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;line x1="226.4" y1="48" x2="226.4" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="226.4" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;250&lt;/text&gt;
&lt;line x1="334.8" y1="48" x2="334.8" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="334.8" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;500&lt;/text&gt;
&lt;line x1="443.2" y1="48" x2="443.2" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="443.2" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;750&lt;/text&gt;
&lt;line x1="551.6" y1="48" x2="551.6" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="551.6" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1 000&lt;/text&gt;
&lt;line x1="660.0" y1="48" x2="660.0" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="660.0" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1 250&lt;/text&gt;
&lt;text x="110.0" y="76.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 4.x&lt;/text&gt;
&lt;path d="M118.0,60.0 h484.0 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-484.0 z" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x: 1 126 req/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="614.0" y="76.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;1 126&lt;/text&gt;
&lt;text x="110.0" y="114.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;PufferPanel&lt;/text&gt;
&lt;path d="M118.0,98.0 h297.6 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-297.6 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel: 696 req/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="427.6" y="114.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;696&lt;/text&gt;
&lt;text x="110.0" y="152.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 3.x&lt;/text&gt;
&lt;path d="M118.0,136.0 h166.9 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-166.9 z" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x: 394 req/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="296.9" y="152.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;394&lt;/text&gt;
&lt;text x="110.0" y="190.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pterodactyl&lt;/text&gt;
&lt;path d="M118.0,174.0 h36.5 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-36.5 z" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl: 93 req/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="166.5" y="190.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;93&lt;/text&gt;
&lt;text x="110.0" y="228.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pelican&lt;/text&gt;
&lt;path d="M118.0,212.0 h29.0 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-29.0 z" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican: 76 req/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="159.0" y="228.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;76&lt;/text&gt;
&lt;/svg&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;RPS&lt;/th&gt;
					&lt;th&gt;Avg, ms&lt;/th&gt;
					&lt;th&gt;Med, ms&lt;/th&gt;
					&lt;th&gt;p95, ms&lt;/th&gt;
					&lt;th&gt;Errors&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;1 126&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;77.6&lt;/td&gt;
					&lt;td&gt;68.9&lt;/td&gt;
					&lt;td&gt;179&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;696&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;126&lt;/td&gt;
					&lt;td&gt;121&lt;/td&gt;
					&lt;td&gt;257&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;394&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;222&lt;/td&gt;
					&lt;td&gt;241&lt;/td&gt;
					&lt;td&gt;302&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;93&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;941&lt;/td&gt;
					&lt;td&gt;766&lt;/td&gt;
					&lt;td&gt;1 825&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;76&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;1 160&lt;/td&gt;
					&lt;td&gt;945&lt;/td&gt;
					&lt;td&gt;2 291&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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&amp;rsquo;s CPU — see the &lt;a href="#resources-and-bottlenecks"&gt;resources table&lt;/a&gt;). Identical utilization, results apart by multiples: comparing panels by &amp;ldquo;CPU load&amp;rdquo; is meaningless; what matters is how much work a panel gets done per core.&lt;/p&gt;
&lt;h3 id="behavior-under-overload"&gt;Behavior under overload &lt;a class="hanchor" href="#behavior-under-overload" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 316" role="img" aria-label="Failed requests on the stress profiles. Only the Go panels return errors; the PHP panels return none but slow down" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Failed requests on the stress profiles&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;lower is better&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;% of all requests (HTTP ≥ 400) · median of three runs&lt;/text&gt;
&lt;line x1="46" y1="236.0" x2="700" y2="236.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="240.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;0%&lt;/text&gt;
&lt;line x1="46" y1="190.0" x2="700" y2="190.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="194.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;10%&lt;/text&gt;
&lt;line x1="46" y1="144.0" x2="700" y2="144.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="148.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;20%&lt;/text&gt;
&lt;line x1="46" y1="98.0" x2="700" y2="98.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="102.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;30%&lt;/text&gt;
&lt;line x1="46" y1="52.0" x2="700" y2="52.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="56.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;40%&lt;/text&gt;
&lt;line x1="90.0" y1="236" x2="108.0" y2="236" stroke="var(--c-g4)" stroke-width="2"/&gt;
&lt;path d="M115.0,236.0 v-0.6 a0.6,0.6 0 0 1 0.6,-0.6 h22.8 a0.6,0.6 0 0 1 0.6,0.6 v0.6 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 800 VUs: 0.09%&lt;/title&gt;&lt;/path&gt;
&lt;line x1="146.0" y1="236" x2="164.0" y2="236" stroke="var(--c-g3)" stroke-width="2"/&gt;
&lt;line x1="174.0" y1="236" x2="192.0" y2="236" stroke="var(--c-pt)" stroke-width="2"/&gt;
&lt;line x1="202.0" y1="236" x2="220.0" y2="236" stroke="var(--c-pl)" stroke-width="2"/&gt;
&lt;text x="155.0" y="254.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;800 VUs&lt;/text&gt;
&lt;line x1="308.0" y1="236" x2="326.0" y2="236" stroke="var(--c-g4)" stroke-width="2"/&gt;
&lt;path d="M333.0,236.0 v-93.9 a4,4 0 0 1 4,-4 h16.0 a4,4 0 0 1 4,4 v93.9 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1000 VUs: 21.3%&lt;/title&gt;&lt;/path&gt;
&lt;line x1="364.0" y1="236" x2="382.0" y2="236" stroke="var(--c-g3)" stroke-width="2"/&gt;
&lt;line x1="392.0" y1="236" x2="410.0" y2="236" stroke="var(--c-pt)" stroke-width="2"/&gt;
&lt;line x1="420.0" y1="236" x2="438.0" y2="236" stroke="var(--c-pl)" stroke-width="2"/&gt;
&lt;text x="373.0" y="254.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1000 VUs&lt;/text&gt;
&lt;path d="M523.0,236.0 v-66.0 a4,4 0 0 1 4,-4 h16.0 a4,4 0 0 1 4,4 v66.0 z" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1200 VUs: 15.2%&lt;/title&gt;&lt;/path&gt;
&lt;path d="M551.0,236.0 v-155.4 a4,4 0 0 1 4,-4 h16.0 a4,4 0 0 1 4,4 v155.4 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1200 VUs: 34.6%&lt;/title&gt;&lt;/path&gt;
&lt;line x1="582.0" y1="236" x2="600.0" y2="236" stroke="var(--c-g3)" stroke-width="2"/&gt;
&lt;line x1="610.0" y1="236" x2="628.0" y2="236" stroke="var(--c-pt)" stroke-width="2"/&gt;
&lt;line x1="638.0" y1="236" x2="656.0" y2="236" stroke="var(--c-pl)" stroke-width="2"/&gt;
&lt;text x="591.0" y="254.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1200 VUs&lt;/text&gt;
&lt;text x="99.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="127.0" y="228.8" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;0.09&lt;/text&gt;
&lt;text x="155.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="183.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="211.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="317.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;&amp;lt; 0.01&lt;/text&gt;
&lt;text x="345.0" y="132.1" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;21.3&lt;/text&gt;
&lt;text x="373.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="401.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="429.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="535.0" y="160.0" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;15.2&lt;/text&gt;
&lt;text x="563.0" y="70.6" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;34.6&lt;/text&gt;
&lt;text x="591.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="619.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="647.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;rect x="46.0" y="271" width="9" height="9" rx="2" fill="var(--c-g4)"/&gt;
&lt;text x="60.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;GameAP 4.x&lt;/text&gt;
&lt;rect x="166.0" y="271" width="9" height="9" rx="2" fill="var(--c-pp)"/&gt;
&lt;text x="180.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;PufferPanel&lt;/text&gt;
&lt;rect x="294.0" y="271" width="9" height="9" rx="2" fill="var(--c-g3)"/&gt;
&lt;text x="308.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;GameAP 3.x&lt;/text&gt;
&lt;rect x="414.0" y="271" width="9" height="9" rx="2" fill="var(--c-pt)"/&gt;
&lt;text x="428.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;Pterodactyl&lt;/text&gt;
&lt;rect x="542.0" y="271" width="9" height="9" rx="2" fill="var(--c-pl)"/&gt;
&lt;text x="556.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;Pelican&lt;/text&gt;
&lt;/svg&gt;
&lt;p&gt;Failed requests (HTTP ≥ 400), % of all requests in the profile:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;load (up to 100 VUs)&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (up to 800 VUs)&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0.09&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;&amp;lt; 0.01&lt;/td&gt;
					&lt;td&gt;21.3&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;15.2&lt;/td&gt;
					&lt;td&gt;34.6&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;But &amp;ldquo;0% errors&amp;rdquo; here does not mean &amp;ldquo;the panel works&amp;rdquo;. At stress-1200, GameAP 3&amp;rsquo;s p95 is 2.1 s, Pterodactyl&amp;rsquo;s is 14.1 s, Pelican&amp;rsquo;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 &amp;ldquo;PHP endures and Go does not&amp;rdquo;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go panels — fail fast&lt;/strong&gt;: a share of requests quickly ends in an error, the rest are served with acceptable latency (GameAP 4&amp;rsquo;s median at 1200 VUs is 25 ms, PufferPanel&amp;rsquo;s is 97 ms). The client knows immediately that the server is struggling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP panels — a queue&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which mode is &amp;ldquo;right&amp;rdquo; is a question of requirements, not of a benchmark. The practical consequence is singular: a PHP panel&amp;rsquo;s overload will not show up in error monitoring — only in latency.&lt;/p&gt;
&lt;p&gt;Achieved RPS on the stress profiles:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (up to 800 VUs)&lt;/td&gt;
					&lt;td&gt;259&lt;/td&gt;
					&lt;td&gt;257&lt;/td&gt;
					&lt;td&gt;210&lt;/td&gt;
					&lt;td&gt;82&lt;/td&gt;
					&lt;td&gt;68&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;716&lt;/td&gt;
					&lt;td&gt;615&lt;/td&gt;
					&lt;td&gt;341&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
					&lt;td&gt;73&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;764&lt;/td&gt;
					&lt;td&gt;699&lt;/td&gt;
					&lt;td&gt;349&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
					&lt;td&gt;74&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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&amp;rsquo;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.&lt;/p&gt;
&lt;h3 id="resources-and-bottlenecks"&gt;Resources and bottlenecks &lt;a class="hanchor" href="#resources-and-bottlenecks" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Peak CPU of the panel VM, %:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;0.4&lt;/td&gt;
					&lt;td&gt;0.6&lt;/td&gt;
					&lt;td&gt;3.2&lt;/td&gt;
					&lt;td&gt;2.3&lt;/td&gt;
					&lt;td&gt;2.9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline (10 VUs)&lt;/td&gt;
					&lt;td&gt;0.7&lt;/td&gt;
					&lt;td&gt;0.9&lt;/td&gt;
					&lt;td&gt;4.8&lt;/td&gt;
					&lt;td&gt;8.9&lt;/td&gt;
					&lt;td&gt;10.9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load (up to 100 VUs)&lt;/td&gt;
					&lt;td&gt;2.4&lt;/td&gt;
					&lt;td&gt;3.8&lt;/td&gt;
					&lt;td&gt;25.7&lt;/td&gt;
					&lt;td&gt;81.2&lt;/td&gt;
					&lt;td&gt;96.8&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (up to 800 VUs)&lt;/td&gt;
					&lt;td&gt;20.4&lt;/td&gt;
					&lt;td&gt;65.3&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;40.0&lt;/td&gt;
					&lt;td&gt;95.6&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;87.7&lt;/td&gt;
					&lt;td&gt;96.1&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;max-throughput&lt;/td&gt;
					&lt;td&gt;96.1&lt;/td&gt;
					&lt;td&gt;95.3&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The key row is load: the &amp;ldquo;working&amp;rdquo; 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.&lt;/p&gt;
&lt;p&gt;Where the CPU goes under overload — processes on the stress profile (800 VUs), peak over the window, mean of runs 2–3:&lt;/p&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 330" role="img" aria-label="Per-process CPU at the stress profile (800 VUs). On Pterodactyl and Pelican, MySQL takes about one full core" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Per-process CPU at the stress profile (800 VUs)&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;lower is better&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;peak over the profile window · 400% = 4 vCPU · mean of runs 2–3&lt;/text&gt;
&lt;line x1="118.0" y1="48" x2="118.0" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="118.0" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;0%&lt;/text&gt;
&lt;line x1="248.5" y1="48" x2="248.5" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="248.5" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;100%&lt;/text&gt;
&lt;line x1="379.0" y1="48" x2="379.0" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="379.0" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;200%&lt;/text&gt;
&lt;line x1="509.5" y1="48" x2="509.5" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="509.5" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;300%&lt;/text&gt;
&lt;line x1="640.0" y1="48" x2="640.0" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="640.0" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;400%&lt;/text&gt;
&lt;text x="110.0" y="73.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 4.x&lt;/text&gt;
&lt;path d="M118.0,56.5 h38.0 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-38.0 z" fill="var(--c-app)"&gt;&lt;title&gt;GameAP 4.x — gameap: 31.5% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="165.0" y="68.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;31.5&lt;/text&gt;
&lt;path d="M118.0,74.5 h1.9 a1.8744545454545474,1.8744545454545474 0 0 1 1.8744545454545474,1.8744545454545474 v11.3 a1.8744545454545474,1.8744545454545474 0 0 1 -1.8744545454545474,1.8744545454545474 h-1.9 z" fill="var(--c-db)"&gt;&lt;title&gt;GameAP 4.x — postgresql: 2.9% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="127.7" y="86.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;2.9&lt;/text&gt;
&lt;text x="110.0" y="115.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;PufferPanel&lt;/text&gt;
&lt;path d="M118.0,98.5 h92.4 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-92.4 z" fill="var(--c-app)"&gt;&lt;title&gt;PufferPanel — pufferpanel: 73.1% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="219.4" y="110.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;73.1&lt;/text&gt;
&lt;path d="M118.0,116.5 h9.4 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-9.4 z" fill="var(--c-db)"&gt;&lt;title&gt;PufferPanel — postgresql: 9.5% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="136.4" y="128.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;9.5&lt;/text&gt;
&lt;text x="110.0" y="157.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 3.x&lt;/text&gt;
&lt;path d="M118.0,140.5 h412.1 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-412.1 z" fill="var(--c-app)"&gt;&lt;title&gt;GameAP 3.x — php-fpm: 318% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="539.1" y="152.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;318&lt;/text&gt;
&lt;path d="M118.0,158.5 h38.0 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-38.0 z" fill="var(--c-db)"&gt;&lt;title&gt;GameAP 3.x — mysqld: 31.4% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="165.0" y="170.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;31.4&lt;/text&gt;
&lt;text x="110.0" y="199.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pterodactyl&lt;/text&gt;
&lt;path d="M118.0,182.5 h313.7 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-313.7 z" fill="var(--c-app)"&gt;&lt;title&gt;Pterodactyl — php-fpm: 243% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="440.7" y="194.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;243&lt;/text&gt;
&lt;path d="M118.0,200.5 h138.8 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-138.8 z" fill="var(--c-db)"&gt;&lt;title&gt;Pterodactyl — mysqld: 109% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="265.8" y="212.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;109&lt;/text&gt;
&lt;text x="110.0" y="241.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pelican&lt;/text&gt;
&lt;path d="M118.0,224.5 h336.2 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-336.2 z" fill="var(--c-app)"&gt;&lt;title&gt;Pelican — php-fpm: 260% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="463.2" y="236.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;260&lt;/text&gt;
&lt;path d="M118.0,242.5 h120.0 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-120.0 z" fill="var(--c-db)"&gt;&lt;title&gt;Pelican — mysqld: 94.3% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="247.0" y="254.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;94.3&lt;/text&gt;
&lt;rect x="118" y="285" width="9" height="9" rx="2" fill="var(--c-app)"/&gt;
&lt;text x="132.0" y="294.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;app (Go / php-fpm)&lt;/text&gt;
&lt;rect x="358" y="285" width="9" height="9" rx="2" fill="var(--c-db)"/&gt;
&lt;text x="372.0" y="294.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;database (PostgreSQL / MySQL)&lt;/text&gt;
&lt;/svg&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Process&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;App (Go / php-fpm)&lt;/td&gt;
					&lt;td&gt;31.5&lt;/td&gt;
					&lt;td&gt;73.1&lt;/td&gt;
					&lt;td&gt;318&lt;/td&gt;
					&lt;td&gt;243&lt;/td&gt;
					&lt;td&gt;260&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Database (PostgreSQL / MySQL)&lt;/td&gt;
					&lt;td&gt;2.9&lt;/td&gt;
					&lt;td&gt;9.5&lt;/td&gt;
					&lt;td&gt;31.4&lt;/td&gt;
					&lt;td&gt;109&lt;/td&gt;
					&lt;td&gt;94.3&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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).&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;EXPLAIN&lt;/code&gt; pass over the list endpoints&amp;rsquo; queries suggests itself.&lt;/p&gt;
&lt;p&gt;Peak RAM of the VM, MB:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Profile&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;449&lt;/td&gt;
					&lt;td&gt;462&lt;/td&gt;
					&lt;td&gt;1 049&lt;/td&gt;
					&lt;td&gt;1 096&lt;/td&gt;
					&lt;td&gt;1 177&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (up to 800 VUs)&lt;/td&gt;
					&lt;td&gt;480&lt;/td&gt;
					&lt;td&gt;539&lt;/td&gt;
					&lt;td&gt;1 288&lt;/td&gt;
					&lt;td&gt;1 463&lt;/td&gt;
					&lt;td&gt;1 606&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;701&lt;/td&gt;
					&lt;td&gt;744&lt;/td&gt;
					&lt;td&gt;1 354&lt;/td&gt;
					&lt;td&gt;1 469&lt;/td&gt;
					&lt;td&gt;1 610&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;max-throughput&lt;/td&gt;
					&lt;td&gt;641&lt;/td&gt;
					&lt;td&gt;672&lt;/td&gt;
					&lt;td&gt;1 342&lt;/td&gt;
					&lt;td&gt;1 445&lt;/td&gt;
					&lt;td&gt;1 556&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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&amp;rsquo;s PostgreSQL RSS varied noticeably between runs (721 MB in the first, ~440 MB in the second and third).&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3 id="what-the-individual-endpoints-show"&gt;What the individual endpoints show &lt;a class="hanchor" href="#what-the-individual-endpoints-show" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Median latency per endpoint on the load profile, ms:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Endpoint&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;list_servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;0.87&lt;/td&gt;
					&lt;td&gt;1.41&lt;/td&gt;
					&lt;td&gt;11.1&lt;/td&gt;
					&lt;td&gt;87.3&lt;/td&gt;
					&lt;td&gt;141&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;server_details&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;0.58&lt;/td&gt;
					&lt;td&gt;0.74&lt;/td&gt;
					&lt;td&gt;8.34&lt;/td&gt;
					&lt;td&gt;12.7&lt;/td&gt;
					&lt;td&gt;25.6&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;server_status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;0.38&lt;/td&gt;
					&lt;td&gt;1.49&lt;/td&gt;
					&lt;td&gt;8.34&lt;/td&gt;
					&lt;td&gt;11.5&lt;/td&gt;
					&lt;td&gt;20.9&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;list_servers&lt;/code&gt; row &lt;strong&gt;across&lt;/strong&gt; 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.&lt;/p&gt;
&lt;h3 id="the-three-head-to-head-comparisons"&gt;The three head-to-head comparisons &lt;a class="hanchor" href="#the-three-head-to-head-comparisons" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GameAP 3 → GameAP 4.&lt;/strong&gt; 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%.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GameAP 4 vs PufferPanel.&lt;/strong&gt; 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&amp;rsquo;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pterodactyl vs Pelican.&lt;/strong&gt; 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&amp;rsquo;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.&lt;/p&gt;
&lt;h2 id="limitations"&gt;Limitations &lt;a class="hanchor" href="#limitations" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A list of what limits the conclusions of this test.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Different databases between the groups.&lt;/strong&gt; The Go panels ran on PostgreSQL, the PHP panels on MySQL 8.0 (in both cases the configurations recommended by the panels&amp;rsquo; developers). Any &amp;ldquo;Go vs PHP&amp;rdquo; comparison here really means &amp;ldquo;Go+PostgreSQL vs PHP+MySQL&amp;rdquo; — this test does not separate the database&amp;rsquo;s contribution from the language&amp;rsquo;s and the architecture&amp;rsquo;s. Within each group the databases are identical.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Different &lt;code&gt;list_servers&lt;/code&gt; response sizes.&lt;/strong&gt; 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&amp;rsquo;s work lighter and GameAP&amp;rsquo;s heavier).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameAP 3 has no status endpoint&lt;/strong&gt;: its &lt;code&gt;server_status&lt;/code&gt; is a repeat of the details request, so a third of the scenario on GameAP 3 is not equivalent to the other panels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read-only, API-only.&lt;/strong&gt; 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&amp;rsquo; HTTP/API layer, not of the panels as a whole.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;k6&amp;rsquo;s closed-loop model&lt;/strong&gt; (&lt;a href="#the-closed-loop-load-model"&gt;see methodology&lt;/a&gt;): the degraded panels automatically received less load, so their stress numbers are an optimistic estimate. The k6 request timeout is 60 s.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Warm-up.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restart asymmetry.&lt;/strong&gt; 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 &amp;ldquo;colder&amp;rdquo;. The profiles&amp;rsquo; ramp phases partially compensate for this, but the asymmetry was not eliminated entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versions recorded at the major level&lt;/strong&gt; (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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modified limits.&lt;/strong&gt; On Pterodactyl and Pelican the rate limits were raised to 10,000 requests/min — a stock installation would have cut the load off earlier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring.&lt;/strong&gt; 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&amp;rsquo;s smoke statistics include the OAuth request from &lt;code&gt;setup()&lt;/code&gt;. One hardware configuration, one VM layout, 100 servers in the database — on different hardware and data volumes the absolute numbers will differ.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusions"&gt;Conclusions &lt;a class="hanchor" href="#conclusions" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back to the three questions from the goals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How different are the panels at normal load?&lt;/strong&gt; 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&amp;rsquo;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%.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where is the breaking point and how does a panel break?&lt;/strong&gt; 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&amp;rsquo;s overload is visible only in latency — there will be no error alerts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is the bottleneck?&lt;/strong&gt; For GameAP 4 — the application&amp;rsquo;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3 id="whats-next"&gt;What&amp;rsquo;s next &lt;a class="hanchor" href="#whats-next" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 2 — game server management&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scaling by data volume&lt;/strong&gt;: runs with 1 / 100 / 1000 servers in the database, to measure how the list endpoints&amp;rsquo; latency depends on the database size.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open questions&lt;/strong&gt; from this phase: PufferPanel&amp;rsquo;s disk writes, profiling of Pterodactyl&amp;rsquo;s/Pelican&amp;rsquo;s MySQL queries, the difference between Pelican and Pterodactyl.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The scripts, configurations and raw data of all runs are in the &lt;a href="https://github.com/et-nik/game-panels-benchmark" target="_blank" rel="noopener"&gt;game-panels-benchmark&lt;/a&gt; repository. If you find a flaw in the methodology or the interpretation — open an issue: it will be re-checked and a correction published.&lt;/p&gt;</content:encoded></item><item><title>The GameAP blog is back</title><link>https://blog.gameap.com/news/blog-relaunch/</link><pubDate>Sun, 02 Aug 2026 12:00:00 +0300</pubDate><guid>https://blog.gameap.com/news/blog-relaunch/</guid><description>The GameAP blog has been relaunched: project news, release announcements and game server guides.</description><content:encoded>&lt;p&gt;The GameAP blog is back. The old version had been sitting untouched for years and had aged badly, so instead of reviving it we rebuilt the blog from scratch — on a new platform and at new addresses.&lt;/p&gt;
&lt;h2 id="new-addresses"&gt;New addresses &lt;a class="hanchor" href="#new-addresses" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The blog used to live on a single domain, &lt;code&gt;gameap.blog&lt;/code&gt;, where the languages were told apart by a path prefix: &lt;code&gt;gameap.blog/en/&lt;/code&gt; and &lt;code&gt;gameap.blog/ru/&lt;/code&gt;. Each language now has a domain of its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.gameap.com/"&gt;blog.gameap.com&lt;/a&gt; — English version;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.gameap.ru/" target="_blank" rel="noopener"&gt;blog.gameap.ru&lt;/a&gt; — Russian version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This lines the blog up with the rest of the project: the English-language resources live on &lt;code&gt;gameap.com&lt;/code&gt; (&lt;a href="https://gameap.com/" target="_blank" rel="noopener"&gt;website&lt;/a&gt;, &lt;a href="https://docs.gameap.com/" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt;), the Russian ones on &lt;code&gt;gameap.ru&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Links to the old &lt;code&gt;gameap.blog&lt;/code&gt; domain keep working — they take you to the new addresses.&lt;/p&gt;
&lt;p&gt;Every article is published in both languages. The switcher in the site header takes you to the same article on the other domain rather than to the home page.&lt;/p&gt;
&lt;h2 id="what-to-expect"&gt;What to expect &lt;a class="hanchor" href="#what-to-expect" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project news&lt;/strong&gt; — GameAP release announcements, changes in the panel and the daemon, plugin ecosystem updates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guides&lt;/strong&gt; — hands-on articles about deploying GameAP and running game servers: from installing the panel to fine-tuning.&lt;/li&gt;
&lt;li&gt;Comparisons, benchmarks and deep dives — the things that don&amp;rsquo;t fit into the documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="useful-links"&gt;Useful links &lt;a class="hanchor" href="#useful-links" aria-label="Link to this heading"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gameap.com/" target="_blank" rel="noopener"&gt;GameAP website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gameap.com/" target="_blank" rel="noopener"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gameap" target="_blank" rel="noopener"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first big article is already in the works — load testing of popular game server control panels.&lt;/p&gt;</content:encoded></item><item><title>Welcome</title><link>https://blog.gameap.com/news/welcome/</link><pubDate>Fri, 24 Apr 2020 00:00:00 +0300</pubDate><guid>https://blog.gameap.com/news/welcome/</guid><description>Welcome to the GameAP blog. Here we publish information on GameAP releases, guides, information about modules and more.</description><content:encoded>&lt;p&gt;Welcome to the GameAP Blog. Here will be published information on GameAP releases, guides, information about modules etc.&lt;/p&gt;</content:encoded></item></channel></rss>