Quick Arm Webserver

qaws

Static files. Full signal.

A zero-dependency Zig static file server built for high-throughput delivery across ARM, Linux, macOS, Termux, and more.

Install the latest stable release

curl -fsSL https://qaws.dev/install.sh | sh

Linux · macOS · FreeBSD · Termux

Measured throughput

322,200req/s

cached 268 B · loopback · access logs off
Trace the request

Event trace

A short path from accept to response.

Event workers own the connection, cached hits stay in memory, and uncached files move through the platform transfer path.

Request path
  1. Acceptepoll · kqueue
  2. Parsestrict HTTP/1.1
  3. Cache V2worker-local view
  4. Transfermemory · sendfile
  5. Respondordered output
Response

Complete static path

Small surface. Serious internals.

qaws keeps its public job narrow, then makes that path efficient, observable, and defensive from socket accept to final byte.

01

Static delivery

The directory is the product.

qaws serves finished assets with deliberately narrow routing semantics.

  • GET and HEAD with body-free HEAD responses, including errors and redirects
  • Directory index resolution and optional 308 trailing-slash redirects
  • Strict 404 behavior with no directory listing and no SPA fallback
  • Common MIME detection plus X-Content-Type-Options: nosniff
  • Validated custom headers without overriding runtime-owned framing headers

02

Modern static HTTP

The protocol work is already done.

Persistent connections, conditional requests, partial transfers, and prebuilt assets stay on the short path.

  • HTTP/1.1 keep-alive by default, with HTTP/1.0 opt-in behavior
  • Ordered HTTP/1.1 pipelining with bounded requests per connection
  • Representation ETags, If-None-Match precedence, Last-Modified, and 304
  • Single normal, open-ended, or suffix byte ranges with 206, 416, and If-Range
  • Brotli and gzip sidecar negotiation with q-values and Vary: Accept-Encoding

03

Event engine

Idle sockets do not own threads.

The runtime selects the native event backend internally and keeps response writes resumable.

  • epoll on Linux and Android, kqueue on macOS and FreeBSD
  • Bounded blocking worker fallback on unsupported targets
  • CPU-topology-aware automatic worker selection with an explicit override
  • Per-worker SO_REUSEPORT listeners with automatic dispatcher fallback
  • Nonblocking memory, buffered-file, and sendfile output resumed after EAGAIN

04

Cache V2

Small files stay close to the worker.

Immutable generations remove shared cached-hit contention while bounded accounting keeps memory explicit.

  • Worker-local cache views over shared immutable, reference-counted generations
  • Per-path refresh locking so unrelated files continue without waiting
  • Nanosecond mtime and size revalidation before rebuilding changed entries
  • Accounting for bodies, paths, headers, response storage, and live generations
  • Serve-uncached behavior at the memory cap, with no hidden eviction policy

05

Defensive defaults

Small does not mean permissive.

Request parsing and filesystem access reject ambiguous input before it reaches a file.

  • Normalized paths that block traversal, unsafe escapes, and backslashes
  • Dotfile segments denied by default except the ACME-friendly .well-known path
  • Required nonempty Host for HTTP/1.1 and strict request framing
  • Conflicting Content-Length, positive request bodies, and Transfer-Encoding rejected
  • Malformed or oversized requests close defensively after an error response

06

Operations

Observable when needed. Quiet when not.

Configuration, logs, and daemon lifecycle remain explicit and scriptable.

  • Strict opt-in JSON configuration with unknown fields and invalid types rejected
  • CLI-over-config precedence plus qaws check for validation without serving
  • Plain or JSONL event logs and nonblocking access logs with drop accounting
  • PID-file locking with daemon start, status, restart, stop, and forced stop
  • Host, port, serve root, connection limits, workers, cache, and sendfile controls

Measured throughput

Fast is a result, not a tagline.

Five-run loopback medians on the same 268-byte cached fixture. Access logs were disabled, connection limits were raised, and every comparison run reported zero socket errors.

Peak observed

322,200req/s

Linux x86_64 · c1000 · cached loopback

Cached small-file requests per second at 1,000 concurrent connections
HostqawsnginxCaddy
macOS ARM64macOS · 4 kqueue workers181.0k79.6k41.1k
mobile Linux ARM64postmarketOS · 2 epoll workers122.1k70.9k22.8k
Linux x86_64Debian · 6 epoll workers322.2k127.9k57.4k

64 MiB sendfile check

Large files stay on the platform transfer path.

macOS ARM64
7.24 GiB/s
mobile Linux ARM64
10.20 GiB/s
Linux x86_64
10.10 GiB/s
Full methodology

Loopback microbenchmarks describe these fixtures and machines. They are engineering observations, not universal production guarantees.

Practical releases

From an old phone to a regular server.

Twelve named binaries cover the systems qaws is built to serve, including Android Bionic for Termux and static musl for small Linux installations.

Linux

x86_64 · ARM64 · ARM hard-float · RISC-V

GNU and static musl

Termux

Android ARM64

Bionic static PIE

macOS

ARM64 · x86_64

kqueue

Windows

x86_64 · ARM64

bounded worker fallback

FreeBSD

x86_64

kqueue

Docker

linux/amd64 · linux/arm64

scratch image

Narrow on purpose

The edge remains your decision.

Put qaws behind Cloudflare Tunnel, Caddy, nginx, Traefik, or another public edge when you need certificates, identity, routing, or abuse protection. qaws stays focused on the static origin.

qaws handlesStatic files, HTTP semantics, connection concurrency, cache, and file transfer.

Intentionally outside qaws

  • TLS termination
  • Authentication or authorization
  • Reverse proxying
  • Runtime compression
  • Directory listings
  • SPA routing fallback
  • Dynamic application serving
  • Multipart byte ranges

Install qaws

One line. The right binary.

The installer detects your platform, resolves the latest stable release, verifies it, and puts qaws on a practical user-writable path.

qaws installerPOSIX sh
curl -fsSL https://qaws.dev/install.sh | sh
  1. 01DetectOS, CPU, libc, and Termux
  2. 02ResolveLatest stable GitHub release
  3. 03VerifyPublished SHA-256 checksum
  4. 04InstallAtomic move, no automatic sudo

Pin with QAWS_VERSION or choose a directory with QAWS_INSTALL_DIR.

Checksum verified

The matching SHA256SUMS entry is required before installation.

No automatic sudo

Writable system paths are used when available, otherwise ~/.local/bin.

No installer telemetry

The script downloads from GitHub, installs qaws, verifies the version, and exits.