Zeroserve: A zero-config web server you can script with eBPF Zeroserve, a new zero-configuration HTTPS server, serves entire websites from a single tarball over HTTP/2 and TLS 1.3 while allowing users to script request handling with eBPF programs that run as sandboxed, JIT-compiled middleware in userspace. The server collapses traditional declarative configuration and separate scripting layers into one eBPF program that handles routing, authentication, rate limiting, and proxying for every request. Designed as an alternative to nginx and Caddy, zeroserve aims to simplify deployment and eliminate configuration complexity by treating the eBPF program itself as the entire configuration. Disclaimer: This article is co-authored with GPT-5.5 and Claude Opus 4.8. zeroserve https://github.com/losfair/zeroserve is a small, fast, zero-config HTTPS server. You hand it a tarball of a website and it serves it - over HTTP/2 and TLS 1.3, with hot reload and a tiny resident footprint. The twist is that you can drop eBPF programs into the tarball and they run on every request, in userspace, as sandboxed middleware - rewriting, authenticating, and rate-limiting requests, or reverse-proxying them to a backend when you want it to act as a gateway in front of your app. In short: Fast : on one core it beats nginx across most workloads - small and large static files, scripted middleware, and small-response proxying, all over HTTPS. Efficient eBPF scripting : scripts are JIT-compiled to native code and sandboxed in userspace, cheap enough to run on every request. Program-as-configuration : your eBPF program is the whole configuration, deciding what happens to each request.: every network and disk operation is submitted through io uring throughout io uring . Modern TLS in the box : TLS 1.3, HTTP/2, Encrypted Client Hello, SNI certificate selection, and JA4 fingerprinting. Simple to operate : serve a whole site from one tarball and hot-reload it and the TLS material with a SIGHUP . It's meant to be an alternative to nginx and Caddy, and the design bet is about configuration . Those servers give you a declarative config language - location blocks, rewrite rules, map directives, try files - and then, once the declarative language hits its limits, an optional scripting runtime bolted on the side Lua, or Caddy's plugins . Behavior ends up split across two layers: directives that quietly grow their own control flow, plus scripts that run somewhere in the request lifecycle you have to keep in your head. zeroserve collapses that into one thing. There is no config file. The eBPF program is the configuration - a single, ordinary, sandboxed program that sees every request and decides what happens: routing, headers, auth, rate limiting, proxying. I want the whole request path in one program I can read top to bottom. One tarball, served in place The whole site is a single tar file. zeroserve indexes it on load - building a path - byte-range map - and then serves files by issuing byte-range reads against the tarball itself. Nothing is ever unpacked to disk. The site lives entirely in that one file, so there's no document root for a stray location rule to expose, and a deploy is a single atomic file swap. To package a directory: zeroserve --pack ./public site.tar zeroserve --addr 0.0.0.0:8080 site.tar Deploying a new version is "replace the tarball and send SIGHUP ". The reload swaps the site, the scripts, and the TLS material atomically, in the same process, with no dropped connections: killall -SIGHUP zeroserve All network and disk I/O goes through io uring via the monoio https://github.com/bytedance/monoio runtime . Each instance is a single-threaded event loop. That sounds like a limitation, and per-process it is - but it's the right shape when your scaling unit is "more processes", and it's why many of them coexist happily on one box. Scripting with eBPF, in userspace This is the part I find most fun. Any .c file you put under .zeroserve/scripts/ gets compiled to an eBPF object at pack time with clang and llc and runs on every request. The eBPF runs entirely in userspace: zeroserve loads the bytecode into a runtime async-ebpf https://crates.io/crates/async-ebpf inside its own ordinary, unprivileged process, so the kernel's BPF subsystem and CAP BPF stay out of it. async-ebpf JIT-compiles the bytecode to native machine code it vendors uBPF https://github.com/iovisor/ubpf , so your "config" runs as native x86-64. A pointer cage does the job the kernel verifier normally would, keeping the program from reading or writing memory it shouldn't: every memory access in the JIT-compiled code is masked into the program's own arena, so a stray access stays confined to the script's own memory. The script runs directly on zeroserve's single event loop. To keep one slow script from stalling every other connection, the runtime is fully preemptible: a timer can interrupt JIT-compiled native code mid-execution and hand control back to the event loop. The programming model is a chain of scripts, run in sorted filename order, sharing a per-request metadata map. If a script calls zs respond or zs reverse proxy , the chain short-circuits. Here's a script that runs first and enriches every request: include