Stop babysitting a Nix cache server. Deploy this Worker, point
nix.confat it, and substitutes come from Cloudflare's edge.
A Nix binary cache that runs on Cloudflare Workers and R2. Written in Rust.
Nix supports S3-compatible binary caches natively via the
s3://
store type, and R2 speaks the S3 API. You can also put a public R2 bucket
behind a custom domain and use Nix's
HTTP Binary Cache Store
for anonymous reads. Both are simpler than running this Worker.
What you get on top of s3://:
- Clients authenticate with a shared HTTP Basic token, not AWS access keys. The
s3://store uses the AWS default credential provider chain, so every uploader needs a key pair. - Server-side narinfo signing. The Worker holds the Nix signing key and signs uploads itself; the key never has to live on a CI runner's
secret-key-files. - Upload validation. The Worker parses each narinfo, checks the format, binds the StorePath to the request route, and rejects anything unsigned.
s3://is opaque blob storage. POST /mass-query in one request. The HTTP Binary Cache protocol supports it;s3://falls back to one HEAD per path.
If none of that matters to you, s3:// to R2 is less code to maintain.
Also: this is a hobby project. I wanted an excuse to spend more time with Cloudflare Workers and Rust, and a Nix cache made a good target.
- Why a Worker, not direct R2/S3?
- Features
- How it works
- Quick start
- HTTP API
- Configuration
- Deployment
- Development
- Dependencies
- License
- Speaks the same protocol as
cache.nixos.org. - Reads come from Cloudflare's edge.
- Storage lives in R2 (no egress to Workers).
- HTTP Basic auth on uploads.
- Optional Ed25519 signing of narinfo on the server.
- One Worker bundle:
index.jsplusindex_bg.wasm.
┌──────────┐ GET /<hash>.narinfo ┌──────────────┐ R2 GET ┌────────┐
│ nix │ ────────────────────────► │ Worker (CF) │ ───────────► │ R2 │
│ client │ ◄──────────────────────── │ │ ◄─────────── │ bucket │
└──────────┘ narinfo + .nar └──────────────┘ object └────────┘
▲
│ PUT (Basic auth)
┌──────────────┐
│ uploader │
│ (nix copy …) │
└──────────────┘
Reads are public. Uploads need HTTP Basic auth. Everything lives in one R2 bucket.
Add the deployed Worker URL to your nix.conf:
substituters = https://<your-worker>.workers.dev https://cache.nixos.org
trusted-public-keys = <your-key-name>:<base64-public-key> cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=export NIX_TOKEN=<token>
nix copy \
--to "https://x-auth-token:${NIX_TOKEN}@<your-worker>.workers.dev" \
/nix/store/<hash>-<name>nix build nixpkgs#hello # served from the Worker if cachedFor anything beyond a one-shot push, put the token in a netrc file so it stays out of shell history, process lists, and CI logs:
machine <your-worker>.workers.dev
login x-auth-token
password <NIX_TOKEN>
Point Nix at it from nix.conf:
netrc-file = /home/you/.netrcThen nix copy --to https://<your-worker>.workers.dev /nix/store/... works without embedded credentials.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/nix-cache-info |
public | Cache metadata (priority, etc.). |
GET |
/<hash>.narinfo |
public | Narinfo for a store path. |
HEAD |
/<hash>.narinfo |
public | Existence check for a narinfo (200 / 404). |
PUT |
/<hash>.narinfo |
basic | Upload a narinfo. |
GET |
/nar/<hash>.nar |
public | NAR archive bytes. |
HEAD |
/nar/<hash>.nar |
public | Existence check for a NAR (200 / 404). |
PUT |
/nar/<hash>.nar |
basic | Upload a NAR archive. |
Auth: HTTP Basic, username x-auth-token, password ${NIX_TOKEN}.
Signing: every stored narinfo carries a Sig:. If the uploader didn't sign and NIX_SECRET is set, the Worker signs the upload itself. Otherwise the PUT returns 400.
| Variable | Required | Description |
|---|---|---|
NIX_TOKEN |
for uploads | Password for HTTP Basic auth on PUT requests (username is x-auth-token). |
NIX_SECRET |
conditional | <key-name>:<base64> — base64 decodes to 64 Ed25519 secret-key bytes (as emitted by nix key generate-secret). Required unless every uploader sends pre-signed narinfo. |
| Binding | Type | Description |
|---|---|---|
NIX_BUCKET |
R2 bucket | Stores .narinfo and .nar objects. |
Use Terraform with the Cloudflare provider. The examples/terraform/ directory has a full example that pulls the Worker bundle from this repo's GitHub Releases and deploys it to Workers + R2.
CI publishes two files to GitHub Releases:
build/index.jsbuild/index_bg.wasm
Both are required, because index.js imports ./index_bg.wasm at runtime.
wrangler.tomlis for local testing, not production.
The Nix flake gives you a dev shell with the tooling already pinned:
nix develop -c cargo test # run the test suite
nix develop -c worker-build --dev # build the Worker bundle into ./build
nix develop -c wrangler dev # serve locally via wranglerRuntime crates:
workerandworker-macros, the Cloudflare Workers Rust SDKnarinfofor parsing and serializing.narinfohttp-auth-basicfor the auth headered25519-dalek,sha2, andbase64for signing and validation
Tooling:
- Nix, for the dev shell
worker-build(from Cloudflare'sworkers-rs) for the JS + WASM bundlewranglerfor local testing- Terraform for production