The aaPanel benchmark, in full
The aaPanel benchmark, in full
Three servers were rented together, set up identically, and given the same shop's data. The only difference between them was the panel running the machine. This is the evidence page: the conditions, the two bugs in our own benchmark that had to be fixed before any figure was trusted, the endpoint-by-endpoint table, and the attribution — including the third of the gap we cannot explain.
The result, so it is on the first screen: 1.76× faster at one request and 1.82× with four arriving at once, against an aaPanel box that was tuned first, across the endpoints that reach PHP on both machines. The comparison page is where that turns into a recommendation. This page is why you should believe the number — and where it stops being ours to claim.
| What was held constant | On all three machines |
|---|---|
| Hardware | AMD EPYC 7713, 2 vCPU, 4 GB — same class, identical processor model and enabled mitigations |
| Operating system | Ubuntu 24.04 on all three |
| Application | the same 6amMart code, verified byte for byte — identical dependency lockfile, identical hash across application, routes, modules and configuration |
| Data | one real shop, row for row — 66,701 orders, 3,865 items, 85 stores, 17,534 customers; every table within about 1 % across boxes |
| Method | each machine measures itself over loopback with its real hostname and certificate |
| Samples | 40 per cell, after a discarded warm-up |
| Reported | medians of alternating paired passes — never a single run |
Two bugs in our own benchmark, found before the numbers were used
Neither of these was in the software under test. Both were in the measurement, and both would have produced a more flattering result than the truth.
The application rate-limits at 600 requests a minute, on top of the web server's own limit. A blocked request is answered in microseconds, so a run that trips the throttle reports a better median than the server can actually serve. Every reported row now mints a distinct token per request and records zero blocked responses.
Cold reads were passing as warm ones. One admin report read 6,260 ms cold against 1,201 ms warm — a five-fold error, in the direction that invents a crisis. Every sweep now discards a warm-up.
A third defect was in the harness rather than the numbers, and is recorded because it was ours: the benchmark signed in as a real customer on every run and never signed out, leaving 100 live access tokens across the two boxes and one written into a temporary file. They were revoked, and token minting now refuses to run when it cannot revoke.
Apply the first one to anyone else's benchmark too
Two rate limiters sit in front of a 6amMart install: the web server's and the application's own 600-per-minute throttle. A load test that does not report zero blocked responses may be measuring the throttle and reporting the refusal as a speedup. If a comparison does not say how it handled that, it has not told you what it measured.
Single runs lie on machines this size
Run-to-run spread reaches 15–30 % on a short row. One endpoint's apparent 17 % regression turned out to be a single-run outlier that a second run put at parity. Arm-to-arm spread on these boxes is 1–13 %.
That is the reason every figure here is a median of alternating paired passes, and the reason we say plainly that any panel comparison quoting one run of each — including one of ours — is unproven.
Every endpoint
Median milliseconds, one request / four at once. Lower is better. The tuned column is the same aaPanel box after the pass described below.
| Endpoint | SixPanel | SixPanel Docker | aaPanel, stock | aaPanel, tuned |
|---|---|---|---|---|
/api/v1/categories | 29.1 / 46.9 | 34.5 / 70.9 | 52.0 / 103.6 | 56.1 / 99.9 |
/api/v1/stores/get-stores/all | 17.9 / 33.3 | 24.5 / 45.0 | 37.8 / 76.4 | 38.0 / 84.2 |
/api/v1/items/search | 18.0 / 35.9 | 25.1 / 43.2 | 44.3 / 68.3 | 40.9 / 71.1 |
/api/v1/items/popular | 41.6 / 77.2 | 50.7 / 94.6 | 58.4 / 112.1 | 65.3 / 122.9 |
/api/v1/customer/order/list | 102.1 / 191.8 | 123.3 / 242.3 | 139.6 / 245.8 | 152.7 / 288.9 |
/ admin entry | 59.6 / 102.7 | 63.5 / 156.3 | 76.7 / 144.5 | 84.8 / 171.2 |
| storefront home | 54.3 / 104.0 | 111.4 / 213.5 | ≈83 / — | — |
| websocket handshake | 2.3 / 5.8 | 4.2 / 11.6 | 2.6 / 6.3 | — |
Three rows are not a fair race, and they are shown rather than quietly dropped:
- On the admin entry, SixPanel redirects in 430 bytes where aaPanel renders 352 KB. Different work.
- On the storefront home, aaPanel's figure is a proxy-cache hit, not a page being rendered.
/api/v1/configis excluded entirely: SixPanel answers it from the web server in 0.6 ms and never sends it to PHP, which would flatter us for doing less.
The headline ratio is taken across the five endpoints that reach PHP on both boxes, which is what leaves those three out of it. /api/v1/categories is the cleanest of the five: it is forced to PHP on all three machines, with nothing answering it from a cache anywhere.
One row moves the other way and stays in the table for that reason. On a static asset aaPanel answers in 0.3 / 0.8 ms against 1.8 / 4.7 ms, because it serves it from its proxy cache.
The rival was tuned first, and the gap did not close
Beating a competitor on its defaults proves nothing. The aaPanel box was set up the way a competent administrator would set it up, and every number was taken again.
| Setting | Before | After |
|---|---|---|
| PHP compiled-code cache | 128 MB | 256 MB, timestamp validation off |
| Database buffer pool | 256 MB | 1152 MB |
| Database log file | 128 MB | 320 MB |
| Query cache | on | off |
| PHP workers | 50 (over-committed) | 14 (sized) |
| Path cache | default | 4× |
| Web-server buffers | default | 2× |
Its PHP 8.4 and its JIT compiler were deliberately left switched on. Those are its advantages, and removing them would have rigged the test.
Against the stock box the ratio was 1.65× / 1.80×. Against the tuned box it is 1.76× / 1.82× — it moved slightly the other way. The one row that shifted is fully accounted for by switching the query cache off, which is a correction rather than a handicap: MariaDB ships it off, it takes a global lock on every query, and with it on the box was reporting database timings faster than the connection floor, which is impossible.
The database battery did not move at all. A buffer pool four and a half times larger changed nothing, because these reports are processor-bound work over rows already in memory. The internals did improve — cache hit rate 99.856 % to 99.930 %, temporary tables spilling to disk 47.3 % down to 22.1 % — and the response times did not follow. That is worth keeping: a database metric improving is not the same event as a page getting faster.
Where the gap comes from
Measured on the five endpoints that reach PHP on both machines, one variable at a time.
| Configuration | One request | Four at once |
|---|---|---|
| As the two boxes ship | 1.99× | 1.91× |
| After switching off aaPanel's directory restriction | 1.32× | 1.29× |
| After also correcting its JIT compiler mode | ≈1.25× | ≈1.21× |
Which resolves to: a PHP directory restriction ≈ 60 % · the wrong JIT mode ≈ 8 % · the PHP version 0 % · about 32 % unexplained.
The directory restriction — 60 % of it, and it was nearly missed
aaPanel restricts which directories PHP may touch (open_basedir). That is a genuine security feature, so it earned a real measurement instead of being dismissed.
Three alternating pairs. The "off" arm writes an empty settings file so the per-directory scan happens in both arms; the static asset is the control, because it never reaches PHP at all.
| Endpoint | Restriction on | Restriction off | Change |
|---|---|---|---|
/api/v1/config | 37.7 | 20.3 | −46 % |
/api/v1/stores/get-stores/all | 39.8 | 23.5 | −41 % |
/api/v1/items/search | 40.6 | 24.1 | −41 % |
/api/v1/categories | 55.4 | 33.7 | −39 % |
/api/v1/items/popular | 70.7 | 51.5 | −27 % |
/ admin entry | 85.7 | 66.4 | −23 % |
/api/v1/customer/order/list | 154.9 | 128.9 | −17 % |
| static file — never reaches PHP | 0.4 | 0.4 | 0 % |
Arm-to-arm spread on these machines is 1–13 %; these effects are 17–46 %; and the control moved by nothing. That last row is what makes it a result rather than a coincidence.
The mechanism was then counted rather than argued. 5,862 filesystem lookups per request with the restriction on, 214 with it off, 217 on SixPanel. And with it on, 400 path resolutions add zero entries to PHP's path cache; with it off they add 509. The cache is simply disabled — which is why aaPanel's own tuned path-cache setting bought it nothing.
Confirmed in the other direction on a second machine: switching the same restriction on for SixPanel reproduced 17 to 23 ms of extra time per request, with filesystem lookups up 19- to 33-fold.
This is the part worth keeping if you administer a server
The setting was in a per-directory file inside the site's own web root — public/.user.ini — which four other places did not show. Not the main PHP configuration, not the site's own configuration, not the worker pool, and not what the command line reports. It was found by reading the value back through the process that actually serves requests, and until that was done, 60 % of a measured gap was sitting in the "unexplained" column. The only reliable check is the serving process, never a configuration file.
The JIT mode — a four-digit number, not a switch
PHP's JIT setting takes a code, and the two named modes are different things: function is 1205, tracing is 1254. A tuning script that only asks "is JIT enabled?" will leave the wrong one in place and report the job done.
aaPanel ships 1205. SixPanel ships tracing. Three alternating pairs put tracing ahead on 9 of 9 endpoints at one request and 9 of 9 at four at once — geometric mean 5.1 % and 6.2 %. Any single one of those differences sits inside the machines' own noise; eighteen out of eighteen falling the same way does not.
For a Laravel application this is the expected direction. Function JIT targets tight numeric loops, which a web request does not run.
The PHP version — worth nothing, and measured rather than assumed
The aaPanel box had both 8.3 and 8.4 installed, so it could answer this on one machine. The 8.3 pool was first given the entire 8.4 tuning, because it had been left on defaults — which would have rigged the result — and every setting was verified from the running process rather than from a file.
Geometric mean of 8.3 against 8.4: 0.9994. 8.3 was ahead on 3 rows of 7, and every difference was smaller than at least one arm's own run-to-run spread. The slow admin report agreed.
That is why the version follows the operating-system release rather than being pinned: it is a free choice. PHP versions has the rest of that, including the separate question of whether 8.5 runs the code at all.
What was ruled out
Each of these is a plausible explanation somebody could offer. Each was measured, and is not the cause.
| Candidate | What was measured |
|---|---|
| The web server | each endpoint timed over the wire and again inside a single PHP process, on both boxes. Wire minus in-process: 0–7 ms on aaPanel, 3.5–5 ms on SixPanel, mostly the encryption handshake. aaPanel's is no larger despite writing an access-log line per request |
| The database | query times effectively identical across all three configurations; datasets within about 1 % on every table |
| How PHP reaches the database | both on the local socket, verified from the running process |
| The cache layer | counted rather than read from a file — 17.1 cache commands per request on aaPanel, 18.1 on SixPanel. Neither silently falling back to disk |
| PHP's compiled-code cache | zero out-of-memory events, zero hash collisions, zero manual restarts, on both; 99.86 % hit rate with 0 % wasted memory on SixPanel |
| The hardware | identical down to the processor model and the set of enabled security mitigations |
One reading that did not come out clean, and is recorded rather than dropped: on the SixPanel box 59.8 % of 2,606 temporary tables spilled to disk. That is a query-shape figure rather than a sizing one — the relevant memory limit was already 64 MB — and it is 6amMart's own query shapes, on the box that was winning the comparison.
About a third of the gap is unexplained, and stays that way
32 % is published as unattributed rather than credited to us. The leading untested candidate is that aaPanel compiles its own PHP while Ubuntu ships a package; the next is that the two allow PHP's compiled-code cache a different number of files — 80,000 against 40,000. Neither can be isolated on a single machine, so neither is claimed.
What the ratio means for a real shop
The tax is a fixed cost per request, so it dominates short work and vanishes on long work. The same 20.9-second admin report is only 1.17× apart between the two machines.
If your shop is mostly heavy back-office reports, the difference will be small. If it is mostly phone-app API calls — which is what a food-delivery shop mostly is — it is the whole difference.
The slowest thing on all three boxes is none of the three
All 297 6amMart admin pages were timed on the real dataset. 290 answer in under 300 ms. The worst, the day-wise transaction report, takes 20,860 ms, of which 12,430 ms is inside the database driver, with one statement running 122 times at 87.6 ms each.
It is present on every machine tested and moved by nothing a panel can do: the full aaPanel tuning pass took it from 20,925 ms to 20,470 ms. That is the application's own code. No control panel can fix it and none should claim to — how the 6amMart optimisation was measured is where it does get fixed, at 712 ms.
Related
SixPanel vs SixPanel Docker vs aaPanel for the decision · Security and isolation, because one panel is faster partly for not setting that restriction · What we withdrew · How we measure.