nz.

Push a directory of HTML, get a url back. A small CLI for macOS, Linux and Windows, one Cloudflare Worker, and one KV entry per site.

$ nz push ./report --ttl 7d --password meow
expires 2026-11-10T19:40:02.000Z, password protected
https://k3x9q.example.com/

$ nz push ./report                # same path, same url, new content
$ nz update k3x9q --no-password  # policy only, content kept

The files

filewhatlines
worker.jsCloudflare Worker: plain JS, no dependencies, no build170
nzthe CLI: POSIX sh, using only tar and curl95
nz.ps1the same CLI for PowerShell, using the tar.exe and curl.exe built into Windows146
test.shend-to-end checks against wrangler dev61

Architecture

nz tars a directory and sends it to the Worker on the apex host, which stores it in KV. Browsers request id.domain, and the Worker reads the KV entry and serves one file out of the tar. nz push ./dir find | tar ustar curl --data-binary browser GET k3x9q.domain/ POST password form worker.js apex host: api POST / new PUT /<id> update bearer TOKEN <id>.host: sites untar, find path password gate signed cookie rate-limited unlock KV SITES key id value tar meta exp, pw expiration tar put get
A site is a single KV entry. Pushing writes it once, and serving a page reads it once. KV's built-in expiration deletes it when the TTL runs out.

One entry holds everything about a site:

key        k3x9q                         5 chars, a-z0-9
value      <ustar bytes>                 the whole site, 25 MiB max
metadata   { exp: 1795895602,            unix seconds, or null for never
             pw:  "<salt>.<pbkdf2>" }    absent when public
expiration exp                           KV deletes the entry for us

Push

Serve

Password

API

The CLI is a thin wrapper around this, so curl works just as well:

tar --format=ustar -cf - -C site . |
  curl -H "authorization: Bearer $TOKEN" \
       -H 'nz-ttl: 7d' -H 'nz-password: meow' \
       --data-binary @- https://example.com/       # 201, new site

curl -X PUT ... https://example.com/k3x9q          # 200, update
curl -X PUT -H 'nz-password;' ...                  # empty: no password

The response is the url on line one and a details line on line two. TTL takes 30m, 12h, 14d, 2w or never, and the default is DEFAULT_TTL (14d).

Install

wrangler kv namespace create SITES   # id into wrangler.jsonc
# set DOMAIN and routes; add a proxied DNS record: * AAAA 100::
wrangler secret put TOKEN
wrangler deploy

ln -s "$PWD/nz" ~/bin/nz
printf 'NZ_SERVER=https://example.com\nNZ_TOKEN=...\n' > ~/.config/nz/config

On Windows 10 or later, put nz.ps1 on your PATH and write the same config:

New-Item -ItemType Directory -Force ~\.config\nz | Out-Null
"NZ_SERVER=https://example.com`nNZ_TOKEN=..." | Set-Content -Encoding ascii ~\.config\nz\config
nz.ps1 push .\site --ttl 7d --password meow