L4/L7 edge proxy · zig 0.17 · io_uring
A zero-allocation L4/L7 edge proxy with experimental HTTP/3, written in Zig. Every pool reserved at startup — the serving path allocates nothing, and exhaustion sheds load at a defined rung of the ladder.
$ git clone https://github.com/chy3xyz/zgix
$ cd zgix && zig build -Doptimize=ReleaseSafe
$ ./zig-out/bin/zgix --check config.json
config.json: valid, and this box can start it
$ ./zig-out/bin/zgix config.json
# 1 listener(s), 1 cluster(s) — budget printed, fds asserted
# one-line install ships with the first release:
# curl -fsSL https://zgix.click/install.sh | sh
All memory is reserved at startup; the request path allocates nothing. Pool sizes are closed-form functions of compile-time constants, printed in the banner and asserted against RLIMIT_NOFILE.
Experimental h3_listeners terminate QUIC-TLS and proxy each request over a pooled plaintext-or-TLS HTTP/1.1 leg — observed like the TCP path, verdict counters and access log included.
Terminate TLS 1.3 with up to 16 certificate pairs per listener — or relay blind by SNI, so one :443 fronts many TLS services without zgix ever holding key material.
An opt-in upgrade relay with its own bounded tunnel pool. Long-lived sessions pin their own buffers and can never starve HTTP of relay slots or upstream leases.
One file, unknown fields refused, --check prices memory and fds before a port is bound. No config reload: replace the process behind SO_REUSEPORT instead.
zgix_* counter families, per-endpoint health gauges, RED histograms, a JSON access log, a bounded log tail on the scrape — and a bundled control panel that reads it all.
observability
The admin endpoint serves one Prometheus scrape at a time from a slot reserved outside the data-path pools. Counters, pool gauges, per-endpoint verdicts and a 128-line access-log tail — the bundled panel turns the scrape into 概览 / 站点 / 应用 / 关于 views.
$ curl -s 127.0.0.1:9100/metrics | head
zgix_info{version="0.9.0"} 1
zgix_conn_slots_in_use 42
zgix_cluster_responses{cluster="api",class="2xx"} 4182
zgix is built on the TigerBeetle I/O model — completion-based, with caller-owned completions — and held to TigerStyle: all memory is reserved at startup, and the request-serving path allocates nothing. One thread owns one event loop and every pool; scale-out is N independent processes behind SO_REUSEPORT, never threads sharing memory.
Every limit is a compile-time constant, and the memory, file-descriptor and ring-depth budgets are closed-form functions of them — printed at startup and asserted against RLIMIT_NOFILE before a port is bound. When a resource runs out, zgix sheds the newest work with a defined answer: a static 503 from constant memory, never a crash, never an unbounded queue.
Correctness rests on a deterministic simulator: the serving path runs against virtual sockets and a virtual clock under a seeded adversarial backend — partial reads down to one byte, resets at every point, delayed and black-holed connects. A live gate then runs the real binary against a real origin on every change and asserts that N requests produce exactly N access-log lines, that counters reconcile, that memory does not move, and that SIGTERM drains cleanly.