pro/web server/version 1.0, source not yet published

ZERVO+ One binary where a stack used to be

Four packages, four configurations, four update paths, or one binary holding the runtime, the database and the template engine in a single process. What follows is one argument in seven steps: each proves the step before it and asks the one after, and the claims I cannot yet measure say so.

  • Rust
  • Lua
  • SQLite
  • Apache-2.0
Version
1.0
Kind
Single-binary web server
Guest
Lua 5.4, sandboxed, under a memory and time budget
Host
Rust
Storage
SQLite, statically linked, WAL
Templating
CAST, embedded, pure Lua
Protocols
HTTP/1.1, 2 and 3 (QUIC); WebSocket on 1.1 and 2
TLS
rustls · ACME on TCP binds
Bind
TCP or Unix socket
Backup
One .zap archive, optionally encrypted
Binary
11.95 MB stripped, against a 20 MB ceiling
Deploy
One binary and its configuration, the same locally and on the server
Licence
Apache-2.0

Startbefore the argument

Before the argument, the facts

1.0 rather than 2.0, because there has never been a public release and a higher number would assert a history that does not exist. The claim it carries is complete, not mature: the three transports, the register below and the audit are all in the first release. It runs in production on my own low-traffic sites, including Voss'scher Verlag. The source is not yet published, so every figure here rests on trust until it is, and the measurements will be reproducible when it ships. Apache-2.0 is the licence the source carries; publishing it is a separate decision, and not yet taken.

The argument, in seven steps

Then, for reference

01everything in one file

A modern website usually arrives as a committee

A web server, a runtime, a database, a process manager, each with its own configuration and its own update calendar, and none of them quite on speaking terms. ZERVO+ is the shorter proposal, and the shortest way to make the point is to count the moving parts.

Every box lists its own parts, and the number on it is how many.

Counted, not measured

The conventional way

16 Moving parts across four packages
nginx 4 parts
  • A server daemon and its workersStarted, watched and restarted by something else again.
  • Nginx.conf, and a server block per siteIts own configuration language, with its own reload semantics.
  • A certificate path per hostWritten by one program and read by another.
  • Its own release and security channelOn its own calendar, not yours.
PHP-FPM 4 parts
  • A master process and a pool of childrenSized by hand, per site, and wrong until it is not.
  • Php.ini and a pool configurationTwo files that disagree in ways nothing reports.
  • A socket between it and the proxyA hop on every render, with permissions of its own.
  • Its own release and security channelOn its own calendar, not yours.
MySQL 4 parts
  • A server daemonAnother long-running process with its own memory baseline.
  • My.cnf, users and grantsA second access model beside the filesystem's.
  • A backup and restore story of its ownNot the same one as the file tree it serves.
  • Its own release and security channelOn its own calendar, not yours.
certbot 4 parts
  • A scheduled job that actually has to runSilent when it does, and often silent when it does not.
  • A renewal configuration per certificateOne more file set that drifts from the proxy's.
  • A challenge path the proxy must exposeA hole in one program's config for another's benefit.
  • Its own release and security channelOn its own schedule, and easy to forget.

Four configurations · four update paths

ZERVO+

6 Moving parts in one package
ZERVO+ · one binary 6 parts
  • A supervisor and a worker pool per siteIt starts the pools, restarts a worker that dies, and stops after three crashes in sixty seconds rather than looping.
  • One configuration directoryOne file per site. The supervisor reads them all at start, and nothing else needs telling.
  • The Lua sandbox boundaryNo filesystem, no operating system, no module loading. The host does the I/O and the guest asks.
  • SQLite, in the same processStatically linked, WAL mode, one connection per worker. A page render is not a network hop.
  • TLS through rustlsNo libssl or libcrypto linked. ACME issues and renews on TCP binds.
  • Its own release and security channelOne thing to follow, and one thing to update, instead of four on four calendars.
What the count is not

Not a benchmark, and not a claim about throughput. A part is a thing that can be misconfigured, which is the only property the number measures.

One configuration · one update path

01.1and it compounds

Each further site is one entry, not four

Sixteen against six is the bill before a single site exists. This is what each further site adds: a server block, an application pool, a database with its user, and a certificate entry, against one entry in a configuration directory. These are counted, not measured, and the count ignores everything you set up once, which if anything flatters the stack.

Conventional stack, four per site ZERVO+, one per site
Things you configure, against sites hosted Two straight lines from the origin. The conventional stack rises by four configured items per site, reaching forty-eight at twelve sites. ZERVO+ rises by one per site, reaching twelve. The gap at twelve sites is thirty-six. things you configure 0 12 24 36 48 0 3 6 9 12 48 conventional stack 12 ZERVO+ sites hosted
Counted, not measured. Four entries per site against one, from a common baseline of zero sites.

The same figures as a table

Sites hostedConventional stackZERVO+
312 entries3 entries
624 entries6 entries
936 entries9 entries
1248 entries12 entries

Four entries per site against one. The stack's four are a server block, an application pool, a database with its user, and a certificate entry.

It deploys as plainly as it counts. You drop the one binary and its configuration into the project and start it, on your laptop or on the server, with no interpreter, no database service and no proxy to install for it. What you run in development is what you run in production, and what you carry away is one archive holding the same three things.

One file means less book-keeping. The fair next question is what it costs to run, so here is the machine's side of the ledger.

02light on the machine

Roughly the memory, and how it fills the cores

Three kinds of mark, and each bar says which it is beside its name. A solid green bar was measured. A solid accent bar is a fact about the design rather than a reading. A dashed grey outline is an estimate, and never a measurement.

Resident memory, idle

Resident memory at idle, conventional stack against ZERVO+ A stack of nginx and PHP-FPM backed by SQLite, so with no database daemon, is estimated at about one hundred and sixty-five megabytes resident when idle. ZERVO+ was measured at twenty-six, of which seven is the supervisor and nineteen one worker. 0 50 100 150 200 megabytes resident, idle PHP stack, SQLite estimated ~165 MB ZERVO+ measured 26 MB
This is deliberately not the heavy comparison. The tally further up counts a conventional stack with MySQL, because that is what people conventionally run; the bar above drops the database daemon altogether and puts the opponent on SQLite too, which is the like-for-like fight. nginx around fifteen megabytes, PHP-FPM around a hundred and fifty, and a database that costs nothing because it is a library rather than a server. What remains is a proxy and a runtime, two services to install, configure and update, each with a pool of its own, where this is one binary and one update path.

Cores, and how you fill them

Workers needed to use every core of an eight-core host ZERVO+ runs one multithreaded process that spreads across every core. A single-threaded runtime uses one worker per core, so an eight-core host takes eight of them. This is architectural, not a measurement. 0 2 4 6 8 workers to fill an eight-core host Single-threaded runtime illustrative 8 ZERVO+ by design 1
One multithreaded process spreads across every core. A single-threaded runtime uses one worker per core, so an eight-core host takes eight, each with its own memory. Architectural, not measured.
measured26 MBTotal, one worker
measured7 MBSupervisor
measured19 MBOne worker
measured0 %CPU when idle

Supervisor and one worker, serving a site, idle: that is the configuration these figures are taken in. A site that runs a pool multiplies the per-worker nineteen megabytes, which the trade-offs below concede. The Lua VM footprint is stable, handlers run without heap growth, and with little resident state in the request path there is less to traverse on the way to an answer. Whether that also makes it quick is the next question.

03fast because little stands in the way

The quickest hop is the one you do not take

There is no benchmark on this page, because a latency figure has not yet been taken. So the argument here is architectural, not a number, and it will earn its number later.

A conventional request crosses borders: the proxy hands to a runtime over a socket, the runtime opens a connection to a database on another. Each hop is small and each hop is real. In ZERVO+ the template engine renders from the same process that holds the database, so a page render reads its data in memory rather than across a wire, and there are fewer wires to cross.

That is speed argued from where the work happens; when the latency figures are measured they will appear here in green, or not at all. It has a quieter consequence too: fewer hops and less work for each request is less processor time spent on each request, and processor time is where the next claim is spent.

    $ zervo --config /etc/zervo
    # one process. the render and the query share its memory,
    # so the database is not a network away.
        

04frugal with power

I have not held it to a wattmeter

And I will not pretend otherwise, so treat this as a derivation, not a measurement. I have counted what can be counted: less processor time for each request, from the step before; one binary where the tally counts four packages; one worker filling the cores where a single-threaded runtime wants eight; and twenty-six megabytes measured against the hundred and sixty-five estimated for the lighter opponent, nginx and PHP-FPM on SQLite with no database daemon at all.

Power follows work, and on every axis there is less of it: fewer processes to schedule, less memory to keep warm, idle cores left genuinely idle rather than polled by a spare worker. The arithmetic only points one way. It does not, on its own, tell you the size of the saving, and until a meter does, the figure stays a dashed line rather than a number.

The comparison that sharpens it is the modern worst case: a runtime rendering a framework on the server, then shipping a second copy of that framework to the browser to run the same render again. Against that, ZERVO+ ships no framework to the browser and does no render twice; a page is built once, in a native process, and sent. Less work for each request is less processor time. What that is worth in watts, I have not measured.

Doing less is cheap. The proper worry is whether doing less has quietly cost you safety, which is the next step.

05safe by construction

Bounded at every level

The isolation model is borrowed from operating systems rather than from the web. A supervisor loads the configuration and spawns a pool of worker processes for each site. Every worker is its own operating-system process, with its own tokio runtime, its own Lua virtual machine and its own SQLite connection, so one site's fault cannot reach another's. A single worker is already a multi-threaded runtime that fills the cores it is given, so the pool, one worker by default and configurable per site, is for isolation and rolling restart, not for parallelism.

Inside each worker, the site's own Lua and CAST run as a guest that cannot open a file, call the operating system or load code. Every side effect is performed by the host, at the guest's request and never on its own authority. Its TLS is rustls, with no libssl or libcrypto linked, which remains the most dependable way to survive OpenSSL's next advisory.

Capability isolation is only half of it, because a guest that cannot open a file can still allocate without bound or spin forever. So every request also runs under a budget: a memory ceiling and a wall-clock deadline, sixty-four megabytes and five seconds by default, both configurable per site. A handler that breaches either fails that one request while the worker carries on serving the next. The deadline is wall clock rather than processor time, and the guest is interrupted between virtual-machine instructions rather than at an exact moment, so this is a backstop against runaway code and not a real-time guarantee.

The database is bounded the same way, and it was not always. ZERVO+ guarded that boundary by searching submitted SQL for forbidden table names, which is what most application servers do, and a pre-release security audit defeated it in one line: ATTACH names no forbidden table, because it brings its own database with it. The escape crossed between sites on a shared host, which is the boundary that mattered. It never reached a public release, there not having been one, and it is closed. The guard is now SQLite's own authorizer, which sits inside the parser and judges resolved names, so quoting, comments and whitespace have nothing to work with: ATTACH, PRAGMA and ZERVO+'s own internal tables are refused, while a site's ordinary queries are untouched.

The same audit closed two remote denial-of-service advisories in the QUIC stack, which mattered here more than most: a QUIC endpoint comes up on every TCP bind and there is no switch to turn HTTP/3 off. One advisory stays open with no fix available, a timing side channel in rsa, reached only when an RSA SSH key decrypts a backup archive. That is a single local interactive operation rather than the repeated decryptions the attack needs, and an ed25519 key avoids it altogether.

Audited before release, then, with dependencies checked against the RustSec advisory database. Not fuzzed, not penetration tested, and the libraries it trusts were not audited themselves.

When a worker dies, the supervisor starts a new one. When it dies three times in sixty seconds, the supervisor stops trying and leaves it down, because a restart loop that hides a fault is worse than an outage that shows one. Safe, then, by construction; whether the language behind that sandbox is a pleasure to write or a chore is the next question.

Supervisor One per machine It reads the configuration and owns the pools

It starts a pool of workers for every site, and replaces any worker that dies.

Worker, a Rust host A pool per site Its own process, runtime and database connection

Its own tokio runtime and its own SQLite connection. It serves over rustls, with no libssl or libcrypto linked.

Your site, in Lua and CAST

No filesystem · no operating system · no module loading

Every side effect is performed by the host, at the guest's request. Everything the guest needs arrives as a primitive the host provides and controls.

Then it stops

The supervisor leaves the site down rather than looping.

06an afternoon to learn

Small enough to hold in your head

The site code is Lua, a language whose entire reference manual is shorter than a framework's getting-started guide, and whose reputation for being learnable in an afternoon is well earned. CAST, the template engine, is pure Lua in the same process, so there is one language and no build step between writing a page and serving it.

The bargain is deliberate. Because the guest is small and sandboxed, reaching a new capability means writing it into the host, not installing a package: a narrow gate, and the design working as intended rather than an oversight. Small and quick to learn invites one fair suspicion, that this is a toy, and the last step answers it.

    -- a route is a Lua function that returns a rendered page
    route("/", function(req)
      return render("index.cast", { title = "Home" })
    end)
        

07not a toy, a current one

Small, and made of current parts

Single-file the way a multi-tool is one object, not the way a brick is, and not a toy for being small. Around thirty direct dependencies are compiled in, current ones: building blocks in the binary, not a runtime stack you install and reconcile beside it, which is what lets the datasheet say one binary and mean it.

Single-file is not itself new. Caddy has shipped one binary with automatic HTTPS for years, and PocketBase pairs one binary with an embedded SQLite backend and a sandboxed guest language. What ZERVO+ adds is the isolation model: a pool of operating-system processes per site, so a fault, a leak or a runaway handler is bounded by the process that caused it and cannot reach the site next door. The sandboxed guest rendering in the same address space as the database is what lives inside that isolation, not what makes it unusual. The file being one is the easy part; the boundaries drawn inside it are the point.

Current is also a claim that can be checked, so the specifications are listed by number, the ones it answers and the ones it does not.

Libraries

tokio
the async runtime each worker runs on
hyper
HTTP/1.1 and HTTP/2
quinn · h3 · h3-quinn
HTTP/3 over QUIC
rustls · ring
TLS and the cryptography under it; no libssl or libcrypto linked
mlua
the sandboxed Lua 5.4 guest
rusqlite
SQLite, statically linked, in WAL mode
instant-acme · rcgen
ACME issuance and certificate generation
lettre
the RFC-conformant mail send
tar · zstd · age
the archive container, its compression and its encryption
rmp-serde
MessagePack for the supervisor-to-worker IPC

Seven steps, and the first is unchanged by the other six: one file, and no capability traded away for it. What that did cost is set out below, and what it is not comes next.

Edgeswhere its edges are

On its own, or behind a proxy

ZERVO+ carries its own TLS and stands on a TCP bind alone, issuing and renewing its certificates over ACME, which is how it runs in local development, with nothing in front of it. Behind an edge proxy it binds a Unix socket instead and lets the proxy terminate TLS, which costs the built-in ACME renewal but suits a host that already owns port 443. Neither is the default the other falls back to; both are first-class.

The three transports are not three equal protocols. Every protection layer runs on all of them, in the same order and before a route is resolved; static files, Lua routes, CAST templates and Server-Sent Events run on all three as well. WebSocket does not: over HTTP/3 it answers 501, because it needs an extension the underlying crate does not yet carry, and HTTP/1.1 and HTTP/2 stay available for it at all times, advertised alongside HTTP/3 through Alt-Svc. The distinction worth keeping is that an absent feature announces itself with a 501, while an absent security check announces nothing at all. The second kind is what the security audit was for.

The bind, either way
On its own

tcp:0.0.0.0:443 · rustls, ACME issuance and renewal built in

Behind an edge proxy

unix:/var/run/zervo/site.sock · the proxy owns 443 and the certificate

  • Not a framework. It does not tell you how to structure an application.
  • Not an application platform. There is no scheduler, no queue, no service mesh.
  • Not a container. It runs as a process, next to your other processes.
  • Not a cluster. It scales up on one host: reads spread across the cores it is given, writes serialise on SQLite's single writer. It does not scale out, because a second host is a second database, not a shared one.

Costswhat it spends

Three decisions, and what each one costs

Every engineering decision buys something and spends something. The seven steps above are the buying; here is the spending, for the three decisions that shape ZERVO+ most, with both sides at equal weight.

One binary instead of a stack

The runtime, the database engine and the template engine are the same process. There is one thing to install, one configuration directory, one thing to update, and no version matrix between four projects that each release on their own calendar.

What it wins

  • One installation, one configuration, one update path.
  • No seams: rendering a page reads the database in-process, not across a socket.
  • Nothing to reconcile when one of four projects changes its defaults.

What it costs

  • You cannot swap a part. Another database engine or another template language is not a configuration change.
  • You depend on one project rather than four, each with its own maintainers and its own security response.
  • Everything scales together, because it is one process tree.

A worker pool per site

Each site gets its own pool of operating-system processes. A worker holds its own tokio runtime, its own Lua virtual machine and its own SQLite connection, so a fault is bounded by the process that caused it. The supervisor restarts a worker that dies, and stops after three deaths in sixty seconds, because a restart loop hides the fault it is papering over.

What it wins

  • One site's fault cannot reach another site's memory, VM or database handle.
  • A crashed worker is replaced without an operator; a crash loop is stopped and shown.
  • Per-worker state means a leak is bounded and reclaimed by a restart.

What it costs

  • Every worker carries its own baseline, measured at roughly 19 MB against the supervisor's 7 MB.
  • Nothing is shared in-process across workers, so a cache is per worker, not per machine.
  • The guest budget bounds a runaway handler, but it counts wall-clock time: a request waiting on a slow database or an outbound call spends its allowance waiting, so those calls carry their own timeouts and the budget is only the backstop. The two defaults meet at five seconds, so a write-heavy site wants the database busy timeout lowered beneath the guest deadline, or a request blocked on the WAL lock races its own budget and the failure names neither.
  • More processes to watch, and a config change means restarting them rather than reloading in place.

A Lua guest inside a Rust host

Site code is Lua, and it runs as a guest with no filesystem, no operating system and no module loading. Everything it needs from the outside world arrives as a primitive the host provides and controls: hashing, constant-time comparison, an outbound fetch gated by a whitelist, and an RFC-conformant mail send.

What it wins

  • A compromised template cannot read a file, spawn a process or reach the network at will.
  • The attack surface is a list of primitives that can be read in one sitting.
  • An outbound call is whitelisted by the host, not by the discipline of whoever wrote the template.

What it costs

  • No arbitrary C modules. What the guest needs must exist as a host primitive first.
  • New capability means new Rust, not a package install. A deliberate bottleneck, and a real one.
  • Code written for stock Lua does not simply drop in.

Tableeverything in the binary

Everything in the binary

Everything in the binary at 1.0. What is not built says so.

ZERVO+ capabilities and their status
CapabilityWhat it isStatus
HTTP/1.1 and HTTP/2hyper, ALPN-negotiated; CAST renders on every pathbuilt
HTTP/3 over QUICquinn and h3, advertised through Alt-Svc; static files, Lua routes, CAST and every protection layer, in the same order as on HTTP/2built
HTTP QUERYRFC 10008, published June 2026; answered on all three transportsbuilt
Server-Sent Eventscoroutine driver, on all three transportsbuilt
WebSocketRFC 6455, handwritten rather than a crate; over HTTP/1.1 and HTTP/2built
WebSocket over HTTP/3answers 501; it needs Extended CONNECT (RFC 9220), which is blocked in the h3 crate rather than in zervonot built
TLSrustls; no libssl or libcrypto linkedbuilt
Auto-TLSACME issuance and renewal, on TCP bindsbuilt
Unix-socket bindfor deployment behind a local TLS-terminating proxybuilt
Lua 5.4sandboxed routes and configuration; per-site env with nested tablesbuilt
Guest resource budgeta memory ceiling and a wall-clock deadline for each request, 64 MB and 5 s by default, configurable per sitebuilt
Guest SQL boundarySQLite's own authorizer, inside the parser; ATTACH, PRAGMA and kernel tables refusedbuilt
CAST templatespure-Lua templating engine, embeddedbuilt
SQLitestatically linked, WAL mode, one connection per worker; concurrent writes serialise on the WAL lock, waiting up to a five-second busy timeoutbuilt
Backup and restoreone .zap archive holding the site's files, its configuration and a consistent SQLite snapshot; any one of the three can be exported or restored alonebuilt
Archive encryptionoptional, off unless a recipient key is named; the server holds only the public halfbuilt
Process isolationsupervisor plus a worker pool per site, each with its own VM and databasebuilt
Crash recoveryrespawn on exit; disable after three crashes in sixty secondsbuilt
Protection layerspasslock, brute-force ban and developer PIN, enforced on every transport before a route is resolvedbuilt
Client detectionoptional per instance; with it switched off, no detection cookie is ever setbuilt
Server-Timingthe W3C header carrying measured process metrics; off by defaultbuilt
Loggingfour levels across thirteen scopes to file or stderr, with rotation and retention, and zervo log to read them backbuilt
Kernel primitivescrypto.hmac_sha256, constant_time_compare, http.fetch (whitelist-gated), smtp.send (RFC 5321/5322)built
Sessionsconfigurable cookie, SameSite per sitebuilt
Single-instance lockflock PID lock with --takeover for clean restartsbuilt
Management CLIstatus, restart and stop over a Unix domain socketbuilt

Backupkeeping it

One file to run, one file to keep

The tally further up charges the conventional stack a moving part for MySQL's backup and restore story, which would be a cheap point to score if this server had none of its own. It has. A backup is one .zap archive holding the three things a site actually is: the files under its root, its configuration, and its database as a consistent snapshot. Any one of the three travels alone if that is what you asked for.

    $ zervo backup  [--config PATH] [--out FILE] [--only project,config,sql] [HOSTNAME...]
    $ zervo restore [--config PATH] [--yes[=BLOCK,...]] ARCHIVE [HOSTNAME...]
        

Neither command needs a running server, and neither shells out to anything: no external tar, xz, zstd or sqlite3 is involved at any point. The database block is taken with VACUUM INTO over a read-only connection, never a filesystem copy of a live WAL database. That is why this is a feature and not a runbook line telling you to copy a directory. A WAL database copied while it is being written is not a backup; it is a file that resembles one.

What one archive holds
project

the file tree under the site's root: CAST, Lua, styles, assets

config

server.lua, and the site's own config.lua

sql

the SQLite database, snapshotted through VACUUM INTO

Restoring into an empty machine asks nothing, because there is nothing to overwrite: the site is rebuilt from the configuration block, the files land at the root it recorded and the snapshot at its data directory. Restoring over a site that already exists asks once, and asks again per block only if that first answer was no.

    Site example.com already exists.
    Restore everything from this backup? [Y/n] n
    Restore project? [Y/n] n
    Restore config? [Y/n] y
    Restore sql? [Y/n] n
        

A partial archive needs no flag on the way back. The manifest is the first entry and is read before a single byte is unpacked, and it records which of the three blocks are present and where each belongs. It is written in GBLN, the typed-record format these tools share, and zervo carries its own reader for it, so an archive does not need JSON to describe itself.

    format<s16>(zervo-backup)
    format_version<u8>(1)
    created_at<s32>(2026-07-25T16:05:28Z)
    sites[
        { hostname<s256>(example.com) has_project<u8>(1) has_config<u8>(1) has_sql<u8>(1) … }
    ]
        
  • Restore writes files, it does not reload a running server. Restarting the site's workers stays the operator's step.
  • The project block is additive, not a mirror. Archived files overwrite their counterparts and files added since the backup survive. There is no mode that makes the tree match the archive exactly.
  • Only the database is snapshotted. The file tree is copied as it is found, so a site taking uploads during a backup can produce a tree the database disagrees with. Quiet hours, or a brief pause, remain the operator's call.
  • A restore that would need a question refuses rather than assumes. With no terminal attached it aborts and names --yes, so a scheduled job cannot silently overwrite a live site.
  • File ownership is not preserved. Restored files belong to whoever ran the command; permissions come from the archive.

Backupencryption

Encryption, for the archive that leaves the machine

Off unless a recipient is named. Name one and the whole archive is wrapped in an age stream, the manifest inside it, so the hostnames and paths are not readable from the file at all. An SSH public key works as the recipient, so there is no new key management to invent.

A recipient is a public key, and the public half is all a server ever needs. There is deliberately no configuration key for a private one, not even as a path: a configuration that names an identity is refused with an explanation rather than obeyed. So a ZERVO+ server can write its own backups and cannot read them, which is the property worth having on the day the machine holding them is the machine you have stopped trusting.

On the way back the private key never appears on a command line, where ps would show it to every local user and shell history would keep it afterwards. Instead zervo names a file itself, at a path only this run could know, verifies its owner and mode on the open descriptor so the check cannot be raced, reads it once and deletes it.

An unencrypted archive stays an ordinary zstd-compressed tar, which is deliberate rather than a leftover: tar --zstd -tf backup.zap opens it on any machine. The moment you need a backup is the moment zervo may be missing, broken or the wrong version, and an archive only its own author can open would be a liability exactly then.

  • An encrypted archive needs a terminal to restore. Writing one is fully unattended, since that needs only the public key. Reading one is deliberately not.
  • Lose the key and the data is gone. An encrypted archive without its identity is indistinguishable from deleted data.
  • One archive, one recipient. Exporting two sites configured with different keys is refused rather than encrypted to both, which would let each operator read the other's data.

Standardschapter and verse

The standards it answers, and the ones it does not

Three transports on a product page is a claim rather than a description. The transports worked and carried every feature, and nobody had held them against the specifications themselves. I have now, reading the requirements a server is able to violate on its own, and the survey found two of them rather than none, which is the argument for printing the register at all.

HTTP/3 responses carried no Date field, which RFC 9110 requires on every 2xx, 3xx and 4xx. It had gone unnoticed in the way these things do: hyper adds it on HTTP/1.1 and HTTP/2 without being asked, so two transports were always right and the third never was. Separately, HTTP/3 accepted connection-specific header fields, which RFC 9114 requires to be treated as malformed. That one is more than a conformance point, because those fields describe an HTTP/1.1 connection and carrying them across a protocol boundary is the textbook set-up for request smuggling. Both are closed, the first at the single point every HTTP/3 response now passes through rather than at the twenty-one places that send one.

Implemented by ZERVO+

Standards implemented by ZERVO+ itself
StandardWhat it does
RFC 9110HTTP semantics: status codes, header fields, methods, conditional and range requests, Date, content negotiation
RFC 10008The HTTP QUERY method, full server side, on all three transports
RFC 6455WebSocket: handshake, framing, ping and pong, close, handwritten rather than taken from a crate
RFC 6265Cookies: session and passlock cookies, HttpOnly, Secure, SameSite
RFC 8555ACME: account, order, HTTP-01 challenge, issuance and renewal
RFC 8305Happy Eyeballs v2: dual-stack connection racing for outbound mail
RFC 2104HMAC, as crypto.hmac_sha256, for verifying signed webhooks
RFC 9114 §4.2HTTP/3 field rules: connection-specific fields refused
WHATWG HTMLServer-Sent Events: formatting, keepalives, the coroutine driver
W3C Server-TimingMeasured process metrics carried on the response
W3C Fetch MetadataSec-Fetch-*, for the browser detection gate
RFC 7239 §5.2The Forwarded field's for= parameter, and the older X-Forwarded-For beside it, read on the Unix-socket bind so that behind a proxy the client is the client and not the proxy. Read, never written
Global Privacy ControlSec-GPC surfaced to handlers

One line there is newer than the rest. QUERY was published in June 2026, weeks before this page was written, and ZERVO+ answers it on all three transports. It is safe and idempotent in the way GET is, but it carries a request body, so a search no longer has to be stuffed into a URL to be cacheable. Very few servers answer it yet.

Standardsthrough libraries

Provided by libraries

Attributed, never claimed. The second column names the library that carries each one.

Standards provided by libraries ZERVO+ links
StandardThrough
RFC 9112, 9113, 7541HTTP/1.1, HTTP/2 and HPACK, through hyper
RFC 9114, 9204HTTP/3 framing and QPACK, through h3
RFC 9000, 9001, 9002QUIC transport, its TLS binding and loss recovery, through quinn
RFC 8446TLS 1.3, through rustls
RFC 7932, 1952Brotli and gzip, for pre-compressed static files
RFC 5321, 3207, 4954SMTP, STARTTLS and SMTP AUTH, through lettre
RFC 9106, 7748, 8439Argon2, X25519 and ChaCha20-Poly1305, through age, for archive encryption

Standardsnot implemented

Not implemented

Standards ZERVO+ does not implement, and why
StandardState
RFC 9220Extended CONNECT, which WebSocket over HTTP/3 needs. Blocked in the h3 crate rather than in ZERVO+
RFC 6797HSTS is not implemented; a deployment that wants it sets it at the proxy in front
RFC 9218Extensible Priorities, an optional extension, not implemented
OPTIONSAnswers 405, which is conformant, but it leaves cross-origin QUERY without a preflight
RFC 9114 §5.2GOAWAY on shutdown is a SHOULD; connections close with QUIC instead

What the survey is not: no formal conformance suite was run against this server. It covers the requirements a server can breach on its own, read from the specifications and checked against the code. That makes it a survey, not a certification.