Performance

Arka against nginx, Apache and Caddy: the same machine, the same files, the same certificate, measured minutes apart in alternating rounds — and each server set up the way an administrator who cares about speed would set it up. Where Arka is faster this page says so, and where it is slower, too.

In short#

  • Static files: Arka was as fast as nginx with 27–42% less CPU, compressed files included; Apache and Caddy were 3 to 7 times slower on small files.
  • Reverse proxy over HTTPS: Arka was 3–10% faster than nginx, with up to 26% less CPU, and 2.2 to 8 times as fast as Apache and Caddy. Without HTTPS, nginx was 5% faster.
  • PHP: against nginx with kept connections to php-fpm, the same speed with 9% less CPU, and on pages compressed for browsers 15% faster with 62% less CPU. nginx's slowest answers took over half a second, and 101 of its requests timed out after 2 seconds; Arka's slowest took 12–32 ms, and none failed. (Arka 0.1.11 still needed 23% more CPU than this nginx on small scripts.) Against Apache and Caddy, Arka was 9% to 3.9 times as fast with 62–89% less CPU.
  • As Ubuntu ships them, nginx and Caddy compress every response again and Arka's leads are much larger — up to 157 times on a compressed script. Those are differences of setup more than of speed, and the main comparison removes them; they are shown separately.

What a visitor notices#

A page's load time is mostly the distance between the visitor and the server — typically 20 to 100 milliseconds — plus whatever the application (PHP, Django…) spends on it. The web server's own share is a fraction of a millisecond with any of these servers, so on a quiet server visitors rarely feel a difference.

Time for one visitor to get a file, one request after another, over HTTPS on the same machine (no network distance; medians):

One visitor, one request at a time Arka nginx Apache Caddy
Small HTML page 0.04 ms 0.04 ms 0.17 ms 0.09 ms
jquery.js (285 KB), compressed 0.14 ms 0.15 ms 17.98 ms 0.21 ms
PHP page (44 KB), compressed 1.53 ms 2.01 ms 2.21 ms 1.78 ms
  • Small files come back in a few hundredths of a millisecond from any of them; nobody can notice the difference.
  • The 285 KB script, compressed ahead of time, comes back from Arka, nginx and Caddy in 0.14–0.21 ms. Apache compressed it again for every visitor and took 18 ms — as nginx (16.5 ms) and Caddy (5 ms) do when nobody has made those copies.
  • The PHP page takes all four between 1.5 and 2.2 ms, almost all of it PHP's own work. Arka was the quickest: zstd, which it uses for pages made per request, is quicker to make than nginx's brotli or Apache's gzip, and smaller. Caddy sends zstd too, and took 1.78 ms.

What each server sends when a browser asks for compressed files (Accept-Encoding: gzip, deflate, br, zstd, as Chrome and Firefox send):

File, as a browser asks for it Arka nginx Apache Caddy
jquery.js (285 KB) 69.5 KB (br) 69.5 KB (br) 84.0 KB (gzip) 69.5 KB (br)
PHP page (44 KB) 2.6 KB (zstd) 2.8 KB (br) 3.7 KB (gzip) 2.6 KB (zstd)
Small HTML page (943 B) 0.2 KB (br) 0.2 KB (br) 0.3 KB (gzip) 0.2 KB (br)

The static files were compressed ahead of time (brotli at its highest level, also gzip and zstd), and Arka, nginx and Caddy all sent those copies; Apache, which needs rewrite rules for that, compressed them itself. Without such copies Arka compresses a file once with brotli and keeps it (0.2 KB and 77.4 KB here). For pages made per request — the PHP page — Arka uses zstd, as small as brotli and quicker to make (Safari, which has no zstd, gets brotli).

CPU per request, at a glance#

When a server is busy, what limits it is CPU: the less CPU each request costs, the more visitors one machine can serve before it slows down, and the more CPU is left for the applications running beside it. The table shows how much CPU Arka spent per request compared with each server — less means Arka could serve more on the same hardware.

Test vs nginx vs Apache vs Caddy
Small HTML page 32% less 84% less 82% less
Small page, compressed 27% less 92% less 85% less
jquery.js 285 KB, compressed 42% less < 1% of theirs 69% less
1 MB file 37% less 60% less 38% less
New HTTPS connection per request 43% less 66% less 46% less
Proxy, small answer 10% less 67% less 81% less
Proxy, 30 KB page 26% less 67% less 72% less
Proxy over HTTP/2 about the same 85% less 90% less
Proxy, plain HTTP (no TLS) 8% more 63% less 80% less
PHP, tiny script 9% less 83% less 89% less
PHP, 44 KB page 9% less 62% less 77% less
PHP, 44 KB page compressed 62% less 74% less 65% less

Against nginx#

nginx/1.24.0 (Ubuntu) — the most widely used web server — at its best: files compressed ahead of time served by gzip_static and brotli_static (the brotli module from Ubuntu's packages), brotli at level 4 for pages made per request, sendfile, open_file_cache, HTTP/2, kept connections to the application, and kept connections to php-fpm (fastcgi_keep_conn, 8 per worker: php-fpm's 32 workers in all).

Test Arka, requests/s nginx, requests/s Arka's speed Arka's CPU per request Slowest 1%: Arka / nginx
Small HTML page 202,642 198,573 the same 32% less 0.3 ms / 0.4 ms
Small page, compressed 207,200 205,263 the same 27% less 0.3 ms / 0.3 ms
jquery.js 285 KB, compressed 41,072 40,455 the same 42% less 1.6 ms / 4.3 ms
1 MB file 2,767 2,591 6% faster 37% less 25 ms / 31 ms
New HTTPS connection per request 5,701 5,972 5% slower 43% less 0.7 ms / 1.7 ms
Proxy, small answer 153,549 146,908 3% faster 10% less 0.6 ms / 0.8 ms
Proxy, 30 KB page 56,037 51,070 10% faster 26% less 2.0 ms / 2.4 ms
Proxy over HTTP/2 153,484 139,251 10% faster about the same 2.8 ms / 4.5 ms
Proxy, plain HTTP (no TLS) 180,392 191,030 5% slower 8% more 0.6 ms / 0.7 ms
PHP, tiny script 58,957 58,179 the same 9% less 12 ms / 525 ms
PHP, 44 KB page 4,227 4,310 the same 9% less 31 ms / 825 ms — 25 of nginx's timed out
PHP, 44 KB page compressed 4,134 3,578 15% faster 62% less 32 ms / 27 ms — 76 of nginx's timed out
  • Static files: the same speed — the load generator, not the servers, set the pace — with Arka using 32% less CPU for a small page, 27% less for the same page compressed, 42% less for the compressed script and 37% less for a 1 MB file (its slowest 1% of answers: 25 ms against 31 ms). Both sent the same copies compressed ahead of time.
  • New HTTPS connection for every request: nginx got about 5% more requests through, but here the load generator ran out of CPU first — Arka used 1.2 of its 4 cores, nginx 2.3. Arka needed 43% less CPU per connection, which is what counts when many new visitors arrive at once.
  • Proxy over HTTPS: 3% faster on small answers with 10% less CPU, 10% faster on 30 KB pages with 26% less. Over HTTP/2 Arka was 10% faster with about the same CPU per request.
  • Proxy without HTTPS: nginx was 5% faster, with 8% less CPU. This only matters when something in front of the server (a CDN or load balancer) handles HTTPS.
  • PHP: with connections to php-fpm kept open, nginx served as many requests a second as Arka of a tiny script and of a full page, with about 10% more CPU per request — but its slowest 1% of answers took 525 and 825 ms, against Arka's 12 and 31 ms, and 101 requests timed out after 2 seconds (the tables count them). php-fpm gives each open connection a worker of its own, so a connection an nginx worker keeps idle holds a php-fpm worker that a request in another nginx worker is waiting for. nginx's max_conns refuses requests over the limit rather than queueing them (a queue comes with the commercial nginx Plus); Arka keeps at most max_conns connections, queues the rest, and serves them in the order they came. Fewer kept connections per nginx worker would mean fewer stalls and less speed; this used the usual figure, one per php-fpm worker in all. On the page compressed for browsers, Arka was 15% faster with 62% less CPU — zstd against nginx's brotli at level 4.

Against Apache#

Apache/2.4.58 (Ubuntu) with the event MPM (4 processes × 64 threads, started up front), sendfile, mod_deflate at level 6, mod_http2, mod_proxy and PHP through proxy_fcgi — the way Ubuntu sets up php-fpm for Apache.

Test Arka, requests/s Apache, requests/s Arka's speed Arka's CPU per request Slowest 1%: Arka / Apache
Small HTML page 202,669 59,749 3.4× faster 84% less 0.3 ms / 5.1 ms
Small page, compressed 207,538 29,927 6.9× faster 92% less 0.3 ms / 10 ms
jquery.js 285 KB, compressed 38,564 216 178× faster < 1% of theirs 1.8 ms / 428 ms
1 MB file 2,735 1,928 42% faster 60% less 25 ms / 236 ms
New HTTPS connection per request 5,697 4,863 6% faster 66% less 0.7 ms / 1.9 ms
Proxy, small answer 154,119 54,902 2.8× faster 67% less 0.7 ms / 3.8 ms
Proxy, 30 KB page 55,600 25,495 2.2× faster 67% less 1.8 ms / 8.1 ms
Proxy over HTTP/2 147,282 27,941 5.2× faster 85% less 3.3 ms / 17 ms
Proxy, plain HTTP (no TLS) 179,373 66,064 2.7× faster 63% less 0.6 ms / 2.8 ms
PHP, tiny script 58,469 23,112 2.5× faster 83% less 12 ms / 11 ms
PHP, 44 KB page 4,215 3,841 9% faster 62% less 31 ms / 32 ms
PHP, 44 KB page compressed 4,111 3,286 25% faster 74% less 32 ms / 35 ms
  • Arka was faster in every test, and used less CPU in every test — usually by a wide margin: 3.4 times as many small pages, 6.9 times as many compressed ones, 2.2 to 5.2 times as many proxied requests, with about a third of Apache's CPU per request or less.
  • The compressed script: Apache was not given copies compressed ahead of time (it needs rewrite rules for them) and compressed it for every visitor — that, not Apache's speed, is the 178 times. nginx and Caddy do the same without such copies.
  • Large files: Arka 42% faster with 60% less CPU, and far steadier — its slowest 1% of answers took 25 ms, Apache's 236 ms.
  • New HTTPS connection for every request: Arka 6% faster, with 66% less CPU per connection.
  • PHP: Arka 2.5 times as fast on a tiny script with 83% less CPU; 9% faster on a full page (PHP is the limit) with 62% less; 25% faster on a compressed page with 74% less. On the tiny script Apache's slowest 1% of answers came a little sooner than Arka's (11 ms against 12 ms); on full pages Arka's did.

Against Caddy#

Caddy v2.11.7, the current release from Caddy's own downloads, with file_server serving files compressed ahead of time (precompressed br zstd gzip), encode zstd gzip for everything else, reverse_proxy and php_fastcgi.

Test Arka, requests/s Caddy, requests/s Arka's speed Arka's CPU per request Slowest 1%: Arka / Caddy
Small HTML page 203,138 66,345 3.1× faster 82% less 0.3 ms / 5.0 ms
Small page, compressed 207,642 54,421 3.8× faster 85% less 0.3 ms / 5.5 ms
jquery.js 285 KB, compressed 41,124 28,356 45% faster 69% less 1.6 ms / 9.1 ms
1 MB file 2,742 2,568 7% faster 38% less 24 ms / 214 ms
New HTTPS connection per request 5,697 6,366 11% slower 46% less 0.7 ms / 1.7 ms
Proxy, small answer 152,597 30,299 5.0× faster 81% less 0.9 ms / 7.9 ms
Proxy, 30 KB page 55,428 21,124 2.6× faster 72% less 2.0 ms / 10 ms
Proxy over HTTP/2 148,517 18,160 8.2× faster 90% less 3.1 ms / 18 ms
Proxy, plain HTTP (no TLS) 180,945 33,838 5.3× faster 80% less 0.6 ms / 8.3 ms
PHP, tiny script 59,638 15,615 3.9× faster 89% less 12 ms / 13 ms
PHP, 44 KB page 4,230 3,553 18% faster 77% less 31 ms / 33 ms
PHP, 44 KB page compressed 4,215 3,556 18% faster 65% less 30 ms / 33 ms
  • Arka was faster in every test but one: 3.1 to 3.8 times as many static pages, 2.6 to 8.2 times as many proxied requests, and 38–90% less CPU per request in every test.
  • The compressed script: with copies compressed ahead of time, Caddy closed most of the gap — Arka 45% faster with 69% less CPU, against 36 times as fast when Ubuntu's Caddy compressed it for every visitor.
  • New HTTPS connection for every request: Caddy got about 11% more requests through, with the load generator again the limit — Arka used 1.2 cores, Caddy 2.5; Arka needed 46% less CPU per connection.
  • Large files: 7% faster with 38% less CPU, and a slowest 1% of 24 ms against Caddy's 214 ms.
  • PHP: Arka 3.9 times as fast on a tiny script with 89% less CPU, and 18% faster on a full page and on a compressed one, with 77% and 65% less; both sent that page as zstd, at the same 2.6 KB. Arka's slowest answers came sooner in all three.
  • Its version: the current Caddy was no faster here than Ubuntu's 2.6.2 from 2022 — slightly slower on the proxy tests.

Where Arka is behind#

Plain-HTTP proxying. Without HTTPS, nginx's proxy was 5% faster than Arka's and used 8% less CPU per request. Over HTTPS — how websites are actually served — Arka is ahead, because its encryption costs less.

The slowest PHP answers under full load. With every php-fpm worker busy, Arka's slowest 1% of answers were not always the quickest: on a script that only prints a line, 12 ms against 10 ms for nginx as Ubuntu sets it up and 11 ms for Apache; on a full page, 31 ms against 30 ms for that nginx; on a compressed page, 32 ms against 27 ms for nginx with kept connections — although Arka served as many requests a second or more each time (30% more than that nginx, and two and a half times Apache's, on the tiny script). On the other PHP tests, nginx with kept connections stalled for half a second and more.

As Ubuntu ships them#

Most servers run with their package's settings. Against nginx and Caddy as Ubuntu 24.04 sets them up — nginx with gzip at level 6 and a new connection to php-fpm for every request, Ubuntu's Caddy 2.6.2 with a plain file_server — the same tests, run the same day, gave Arka much larger leads (Arka's speed, and its CPU per request):

Test vs nginx vs Caddy 2.6.2
Small HTML page the same; CPU: 29% less 2.8× faster; CPU: 81% less
Small page, compressed 41% faster; CPU: 60% less 3.3× faster; CPU: 83% less
jquery.js 285 KB, compressed 157× faster; CPU: < 1% of theirs 36× faster; CPU: 99% less
1 MB file the same; CPU: 36% less 6% faster; CPU: 37% less
New HTTPS connection per request 5% slower; CPU: 43% less 11% slower; CPU: 47% less
Proxy, small answer 6% faster; CPU: 7% less 4.6× faster; CPU: 79% less
Proxy, 30 KB page 9% faster; CPU: 25% less 2.5× faster; CPU: 69% less
Proxy over HTTP/2 12% faster; CPU: about the same 6.9× faster; CPU: 88% less
Proxy, plain HTTP (no TLS) 5% slower; CPU: 9% more 4.8× faster; CPU: 78% less
PHP, tiny script 30% faster; CPU: 37% less 3.5× faster; CPU: 87% less
PHP, 44 KB page the same; CPU: 19% less 16% faster; CPU: 74% less
PHP, 44 KB page compressed 15% faster; CPU: 67% less 19% faster; CPU: 62% less

The large leads on compressed files come from those servers compressing every response again, which copies compressed ahead of time avoid, as the main comparison shows. The PHP lead over this nginx comes from Arka keeping its connections to php-fpm open — without the stalls above — and from zstd. Apache's figures in the main comparison are already as Ubuntu sets it up.

How we measured#

Machine A laptop with an Intel Core i5-1240P (4 performance cores, 8 efficiency cores) and 16 GB of memory, Linux 6.17 (Ubuntu 24.04).
Layout The web server under test: 4 workers on the 4 performance cores. The load generator: 4 efficiency cores. The application behind the proxy (a fixed answer from nginx, and a 30 KB page) and php-fpm: the other 4 efficiency cores. Nothing else busy.
Arka 0.1.12 — the binary from the official package — measured on 30 October 2026.
Others nginx/1.24.0 (Ubuntu) (with Ubuntu's brotli module) and Apache/2.4.58 (Ubuntu) from Ubuntu 24.04's packages; Caddy v2.11.7 from Caddy's releases, and Ubuntu's Caddy 2.6.2 for the second view. PHP 8.3 with OPcache, a separate pool of 32 workers for each server.
Encryption HTTPS everywhere except the "plain HTTP" proxy test: TLS 1.3, X25519, AES-256-GCM, the same ECDSA certificate for every server.
Load wrk with 4 threads and 64 kept-alive connections (16 connections, each closed after one request, for the new-connection test); oha for HTTP/2 (16 connections × 8 streams) and for the one-visitor times.
Method For each test, Arka and the other server ran alternately for 3 seconds at a time, 10 rounds, swapping which went first. The figures are medians. CPU time is read from the kernel for the server's own processes only. Running the two in alternation cancels out the laptop's changing clock speed, which would otherwise swing results by 30%. Arka ran in every comparison, and its own figures differ between them for the same reason: compare the two servers within one table.
Every answer checked Before the runs, each server's answers were fetched and compared: the same status, the same bytes. During the runs the load generators' counts of failed answers and time-outs were kept, and the tables show every one: apart from nginx's PHP time-outs there were none. The runs of the second view were checked the same way in a separate round, with no failures. (oha cancels the HTTP/2 requests still in flight when its time is up — every server alike — and those are not counted.)
Arka's build The official binary is built with profile-guided optimisation: the compiler arranges the code after a training run of static files, compression, the reverse proxy, PHP and HTTPS — with the same load generator and connection count as these tests (PHP answered by a stand-in for php-fpm). That saves about 20% CPU per request, and ordinary traffic runs the same code, but it makes these tests close to Arka's best case.

What CPU per request means. Requests per second only show who is faster when the server is the bottleneck; on this laptop the load generator often was, so both servers reached similar numbers. CPU per request measures how much work each server did for the same result, which is what decides how many visitors a real server can take.

Limits of this comparison. It is one machine, over loopback (no real network), with a handful of request types at 64 connections — not thousands of connections, slow mobile clients, HTTP/3 or long runs — and it was run by the people who make Arka. Your applications, files and hardware will give different numbers. That is why everything needed to repeat it is here: the configurations below, and the script and every result file (with the load generators' own output).

The Arka host used
default = true
server_names = ["bench.local"]
listen = ["127.0.0.1:18892"]
request_id = false                  # nginx adds no request id by default either

[tls]
listen = ["127.0.0.1:18445"]
cert = "/srv/bench/tls/cert.pem"
key = "/srv/bench/tls/key.pem"

[log]
enabled = false                     # access logs off for every server

[compression]
enabled = true
min_size = "512B"

[[routes]]
path = "/proxy/"
handler = { type = "proxy", upstreams = ["127.0.0.1:18893"], strip_prefix = true, forward_headers = false }

[[routes]]
path = "/app/"
handler = { type = "fastcgi", address = "unix:/run/bench/php-a.sock", root = "/srv/bench/www", max_conns = 32, queue = { size = 1000, timeout = "30s" } }

[[routes]]
path = "/"
handler = { type = "static", root = "/srv/bench/www" }

With 4 workers (workers = 4). forward_headers = false matches nginx's default of sending no X-Forwarded-* headers; Apache and Caddy add them by default, so on the proxy tests they did slightly more work than Arka and nginx. max_conns = 32 matches the size of the php-fpm pool, as the documentation recommends: Arka keeps up to that many connections to php-fpm open and queues the requests beyond them.

nginx, at its best
load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so;
load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so;
worker_processes 4;
events { worker_connections 8192; use epoll; multi_accept on; }
http {
  include /etc/nginx/mime.types;
  access_log off;
  sendfile on; tcp_nopush on; tcp_nodelay on;
  keepalive_timeout 65; keepalive_requests 1000000;
  open_file_cache max=10000 inactive=60s; open_file_cache_valid 60s;
  open_file_cache_min_uses 1; open_file_cache_errors on;
  gzip_static on; brotli_static on;
  gzip on; gzip_comp_level 5; gzip_min_length 512; gzip_vary on;
  gzip_types text/css application/javascript text/javascript application/json image/svg+xml;
  brotli on; brotli_comp_level 4; brotli_min_length 512;
  brotli_types text/css application/javascript text/javascript application/json image/svg+xml;
  ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m;
  upstream backend { server 127.0.0.1:18893; keepalive 128; }
  # 4 workers x 8 kept connections = php-fpm's 32 workers
  upstream php { server unix:/run/bench/php-b.sock; keepalive 8; }
  server {
    listen 127.0.0.1:18894 reuseport;
    listen 127.0.0.1:18446 ssl http2 reuseport;
    ssl_certificate /srv/bench/tls/cert.pem; ssl_certificate_key /srv/bench/tls/key.pem;
    root /srv/bench/www;
    location /proxy/ { proxy_pass http://backend/; proxy_http_version 1.1; proxy_set_header Connection ""; }
    location ~ ^/app/.+\.php$ {
      try_files $fastcgi_script_name =404;
      include /etc/nginx/fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_pass php;
      fastcgi_keep_conn on;
    }
    location / { try_files $uri =404; }
  }
}

nginx as Ubuntu sets it up (the second view) is the same without the two modules, gzip_static, brotli and the php upstream: gzip at level 6, and fastcgi_pass unix:/run/bench/php-b.sock — a new connection to php-fpm for each request.

Apache
# modules: mpm_event, authz_core, mime, dir, filter, deflate, socache_shmcb,
#          ssl, http2, proxy, proxy_http, proxy_fcgi
ServerLimit 4
StartServers 4
ThreadsPerChild 64
MaxRequestWorkers 256
MinSpareThreads 64
MaxSpareThreads 256
MaxConnectionsPerChild 0
KeepAlive On
MaxKeepAliveRequests 0
KeepAliveTimeout 65
EnableSendfile On
Protocols h2 http/1.1
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLSessionCache shmcb:/run/bench/ssl_scache(512000)
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript text/javascript application/json image/svg+xml
DeflateCompressionLevel 6
Listen 127.0.0.1:18894
Listen 127.0.0.1:18446 https
DocumentRoot "/srv/bench/www"
<Directory "/srv/bench/www">
  Require all granted
  AllowOverride None
</Directory>
ProxyPass /proxy/ http://127.0.0.1:18893/
ProxyPassReverse /proxy/ http://127.0.0.1:18893/
<FilesMatch "\.php$">
  SetHandler "proxy:unix:/run/bench/php-b.sock|fcgi://localhost"
</FilesMatch>
<VirtualHost 127.0.0.1:18446>
  SSLEngine on
  SSLCertificateFile /srv/bench/tls/cert.pem
  SSLCertificateKeyFile /srv/bench/tls/key.pem
</VirtualHost>
Caddy
{
  admin off
  auto_https off
  default_sni 127.0.0.1
  servers {
    protocols h1 h2
  }
}
https://127.0.0.1:18446 {
  tls /srv/bench/tls/cert.pem /srv/bench/tls/key.pem
  encode zstd gzip
  handle_path /proxy/* {
    reverse_proxy 127.0.0.1:18893
  }
  root * /srv/bench/www
  php_fastcgi /app/* unix//run/bench/php-b.sock
  file_server {
    precompressed br zstd gzip
  }
}
http://127.0.0.1:18894 {
  handle_path /proxy/* {
    reverse_proxy 127.0.0.1:18893
  }
}

Ubuntu's Caddy (the second view) ran the same file with a plain file_server.