celld is an alpha. It is not safe for hostile multi-tenant use. Security fixes apply to the latest release only, so older alpha builds do not receive fixes.
celld opens two HTTP listeners. The public listener serves the deployed Worker, and the internal listener serves the peer protocol and the operator API.
Use --listen for the public listener. Expose only this listener through a load
balancer, a reverse proxy, or a public firewall rule.
Use --internal-listen for the internal listener. Its default address is
127.0.0.1:0, so celld selects an available loopback port at each start.
The startup output reports the selected address.
Use --advertise to give peers the address of the internal listener. Bind the
internal listener to a private interface, or protect it with a private overlay.
Do not expose this listener to the public internet.
An explicit advertised address requires an explicit internal-listener address. Set both command-line options, or use their equivalent environment variables. celld also rejects an explicit non-loopback public listener without an explicit internal listener. This rule identifies an obsolete one-listener configuration.
celld cannot verify that an advertised hostname or a translated port reaches the internal listener. You must route the advertised address to the internal listener, and you must not route it to the public Worker listener.
The public listener reserves only /__celld/health. A healthy node returns a
200 response with {"ok":true}, and an unhealthy node returns a 503 response.
The deployed Worker owns /health and every other public path.
The internal listener does not pass an unknown path to the Worker. It returns a 404 response, so an operator request cannot become an application request.
A Worker reads the request URL from request.url. An application can route on
the hostname and build an absolute link from this URL. Therefore, celld ignores
X-Forwarded-Host and X-Forwarded-Proto by default.
Set --trust-forwarded-headers only when a trusted proxy replaces both headers.
celld then reads the last value in each header, so an earlier client value does
not override the proxy value. The equivalent environment variable is
CELLD_TRUST_FORWARDED_HEADERS=1.
celld always takes the path and query from the request target. It ignores an absolute-form request target's scheme and authority, so a direct client cannot bypass the host policy through the request line.
The operator API does not authenticate its requests. A client that can reach the internal listener can inspect state, start direct work, evict a cell, or stop the process. Therefore, a firewall or a private overlay must restrict access to trusted operators and fleet nodes.
Peer requests on the same listener keep their protocol authentication. Each peer request has an HMAC, a body signature, a clock limit, and replay protection. The private network adds protection, but it does not replace the peer authentication.
celld does not terminate TLS on the internal listener. Use an encrypted overlay such as WireGuard or Tailscale when the private network does not provide the required confidentiality.
The internal operator API is available in the released binary. The API is an alpha interface, so a release can change its paths or response formats.
/statereports the current occupancy, eviction, and restoration values. It remains available while a graceful shutdown drains existing work./metricsreports the same node lifecycle state as bounded Prometheus text. It includes no cell, request, tenant, or bucket identifiers and remains available during graceful drain./cell/NAMEresolves or activates a cell for an operator check./evict/NAMEevicts a resident cell./do/NAMEsends a direct Durable Object request.POST /shutdownstarts a graceful ownership handoff.POST /shutdown?handoff=preserveprepares a clean same-node reload and keeps the ownership records./__celld/probeserves the signed diagnostic probe.
The peer protocol also uses reserved internal paths. An operator must not call these paths directly, and celld continues to authenticate each peer request.
The fleet bucket is the root of authority for the fleet. It stores the deployments, the cell state, the ownership leases, the node leases, and the shared peer-authentication secret.
A person who holds the bucket credentials controls the fleet. Give each credential access to one fleet bucket only, and replace a credential after a suspected disclosure.
Set CELLD_DATA_ENCRYPTION_KEYRING to a secret JSON object containing one
active key and any retained read keys:
{
"active_key_id": "2026-08",
"keys": {
"2026-07": "BASE64_32_BYTE_KEY",
"2026-08": "BASE64_32_BYTE_KEY"
}
}Key IDs may contain only ASCII letters, digits, periods, underscores, and hyphens, and may be at most 64 characters long.
Celld generates a random 256-bit data key for every LTX body and SQLite checkpoint/fork image, encrypts the database bytes with AES-256-GCM, and wraps that data key with the active versioned AES-256-GCM key-encryption key. Both authenticated-data domains bind the envelope header and exact bucket key, so a copied or modified ciphertext fails restore. Ownership records, epoch seals, deployment pointers, and checkpoint manifests remain plaintext because they are coordination and integrity metadata rather than customer database bytes.
Set CELLD_DATA_ENCRYPTION_REQUIRED=1 on a fleet that carries customer data.
Startup then fails without a valid keyring. Rotate without downtime by adding a
new key-encryption key, making it active, and retaining every old key until no
LTX, checkpoint, fork seed, rollback deployment, or retained object references
it. Removing a still-referenced key makes restore fail closed.
CELLD_DATA_ENCRYPTION_ALLOW_PLAINTEXT_READS=1 is only for a reviewed migration
of an existing plaintext fleet. New writes are still encrypted, but old objects
may be read. New fleets must leave it disabled. The keyring itself must come
from a secret manager or KMS-protected deployment channel; never place it in a
Worker bundle, bucket object, command line, repository, or log.
Nodes that predate this envelope format cannot read encrypted LTX. Upgrade the complete fleet before enabling encryption, then enable the same retained keyring on every node as one coordinated configuration change. Do not roll back to a pre-encryption binary after the first encrypted object is published.
An operator can make bucket deployment pointers fail closed by setting
CELLD_DEPLOYMENT_VERIFY_KEYS_FILE on every node. The file is a JSON object
whose keys are release-key IDs and whose values are base64-encoded 32-byte
Ed25519 public keys. Once configured, a node rejects an unsigned pointer, an
unknown key ID, a changed pointer, and a changed deployment manifest before it
loads Worker code.
Create a signed pointer with celld deploy --attestation release.json --signing-key release.seed --signing-key-id KEY_ID. The attestation must be a
JSON object of string values. Celld adds its compile-time version and commit;
the input must not assign the reserved celld_version or celld_commit keys.
The signing-key file contains only a
base64-encoded 32-byte Ed25519 seed; keep it out of the fleet and expose it only
to the deployment job. Nodes need only the public-key map. Rotate by adding the
new public key to every node, signing a deployment with the new key, and then
removing the old key after every supported rollback deployment has been
re-signed or retired.
The signature binds the fleet pointer, rollout percentage, exact manifest bytes, and operator metadata. Module and asset hashes remain part of the Celld manifest validation path. This feature does not make a compromised Worker or a holder of the fleet bucket credentials harmless; it prevents bucket access alone from selecting unapproved Worker code when the signing key is separate.
Each cell is a SQLite database with one writer. One node owns a cell at a time, and an ownership epoch fences each cell.
A node that loses its lease cannot modify the current cell state. The ownership and fencing page describes this mechanism.
A fleet has no shared multi-tenant scheduler or shared placement layer. A defective cell can access only its own database, but it can consume resources on its fleet nodes.
celld does not authenticate the users of the deployed application. It also does not terminate public TLS. Put the required authentication and TLS in front of the public listener.
Keep the internal listener private, and keep the bucket credentials secret. See the limitations page for the complete alpha boundary.