Serve vaults
cipherfs serve owns vault leases and serves the HTTP library. There is no separate cipherfs-web executable. All local clients use private Unix sockets or Windows named pipes, with file-backed owner credentials; mounting is optional.
# Existing vault, password from the normal prompt or CIPHERFS_PASSWORD.
cipherfs serve --vault ~/vault
# HTTP/JSON listener, initially sealed; unseal through its HTTP API.
cipherfs serve --vault ~/vault --listen-address 127.0.0.1:3000
# Every non-loopback listener requires a PEM certificate chain and private key.
cipherfs serve --vault ~/vault --listen-address 0.0.0.0:3443 \
--tls-certificate /etc/cipherfs/server.pem --tls-private-key /etc/cipherfs/server-key.pem
TCP requests use revocable Bearer JWT sessions. TCP sessions cannot call the private local mount, export or credential operations. Plain HTTP is allowed only on a loopback address; wildcard IPv4/IPv6 and other addresses require TLS. TLS handshakes have a ten-second deadline. Keys and passwords never appear in process arguments or configuration values; the TLS argument names a file.
Configuration
cipherfs serve --config /etc/cipherfs/serve.json accepts this bounded JSON schema. Unknown fields, duplicate aliases/paths and invalid listener policies are rejected. Paths are absolute. Configuration supports up to 128 vaults and 16 listeners; a listener port may be zero for an OS-assigned port.
{
"vaults": [
{"name": "personal", "vault_path": "/srv/cipherfs/personal", "unlock_source_ref": "env:PERSONAL_PASSWORD"},
{"name": "work", "vault_path": "/srv/cipherfs/work", "unlock_source_ref": "env:WORK_PASSWORD"}
],
"listeners": [{"listen_address": "127.0.0.1:3000"}]
}
Every existing vault first acquires its lease and publishes private discovery while sealed. A source-free owner can also be unsealed through the local CLI without TCP. env:NAME reads a fresh password on each attempt; secret_sources and auto_unseal support file, systemd, native keyring and HTTPS sources (see credentials and sealing). Source failure keeps the owner sealed and retries with 1–30 second backoff. Invalid configuration or failure to acquire a vault drains acquired owners and returns an error. TLS listener configuration adds "tls": {"certificate_path": "/etc/cipherfs/server.pem", "private_key_path": "/etc/cipherfs/server-key.pem"}.
Each vault has its own lease, session, synchronization and mount service. APIs use /vaults/personal/api/v1 and /vaults/work/api/v1; a session cannot access another vault. A single configured vault also exposes /api/v1. The service hosts APIs only; use cipherfs-app for the desktop interface.
S3 front door
Build the CLI with --features s3 (the S3 service adapter needs Rust 1.96 or newer), then add s3_gateway to the same configuration. It uses a separate listener and the same vault owners; no second database process, FUSE mount or storage bucket is needed. HTTP/JSON and private local APIs remain available alongside it.
{
"vaults": [{"name": "personal", "vault_path": "/srv/cipherfs/personal"}],
"secret_sources": {"applications": {"kind": "files", "directory_path": "/etc/cipherfs/application-secrets"}},
"s3_gateway": {
"allowed_hosts": ["127.0.0.1:9000"],
"buckets": {"personal-files": {"vault": "personal"}},
"applications": [{
"application_id": "backup",
"access_key_id": "backup-application",
"credential_ref": {"source": "applications", "name": "backup"},
"buckets": ["personal-files"],
"read_only": false
}]
}
}
Provision the application secret separately in a private credential file, outside the vault, with no trailing newline. It must contain 16–256 visible ASCII characters. The configured source is read afresh for each signature verification; source failure denies access. The example does not automatically unseal the vault: use the local CLI or configure an independent auto_unseal source. Application S3 keys cannot unseal a vault or call the JSON/local APIs.
The default S3 address is 127.0.0.1:9000, and the default signing region is us-east-1. Set listen_address, region and tls explicitly when needed. Non-loopback listeners require TLS using the same certificate/private-key schema as HTTP listeners. allowed_hosts contains exact signed HTTP authorities, including the port used by clients. Wildcards, forwarded host routing and absolute-form request targets are rejected. Use path-style access (endpoint/bucket/key) and SigV4; anonymous requests and SigV2 are denied. Each application sees only its configured buckets; read_only denies mutations. Each bucket names a vault and may set a canonical prefix; keys, listings and history are relative to that subtree. A version ID from outside the subtree cannot bypass the key mapping.
The implemented operations are bucket discovery/HEAD/location/versioning, PUT, GET, HEAD, DELETE, ListObjects v1/v2, ListObjectVersions, multipart initiation/part upload/part listing/completion/abort, single byte ranges, and SigV4 presigned requests. Prefix/delimiter listings are paginated. Shared version IDs are CipherFS version identities. Reads of a retained historical version use its original key; concurrent heads are listed and readable at their file's effective key, including across a concurrent move. Shared history remains subject to vault retention and retirement. Current local-only files are accessible and new writes follow the same local-only rules as other vault entries; they do not acquire shared history. Explicit vault directories are omitted from object listings.
The default etag_policy: "version" produces opaque strong validators containing the version identity, including after multipart completion. These are not plaintext MD5 checksums. Choose etag_policy: "content_md5" when a client needs a plaintext MD5 validator. That policy streams and authenticates content to calculate ETags for GET, HEAD and list results, so metadata operations may fetch uncached content; multipart completion returns the MD5 of the whole plaintext. Part ETags are MD5 values used to authenticate the requested completion list. Content-MD5, SHA-1, SHA-256, CRC32, CRC32C and CRC64NVME upload checksums are checked before publication, including verified streaming trailers. An incomplete, cancelled or mismatched upload cannot replace the current object. GET/HEAD support ETag conditions; date conditions and conditional writes return NotImplemented.
Keys map exactly to NFC vault file paths, up to 1,024 UTF-8 bytes. Empty keys, leading/trailing slashes, repeated slashes, dot segments, backslashes and control characters are rejected rather than aliased. Bucket creation/deletion, virtual-host bucket routing, CopyObject, ACLs, tags, custom S3 metadata, S3 encryption options and permanent version deletion are not implemented; use vault management/retention APIs for those supported vault operations. This front door does not change the shared wire format.
Uploads use bounded plaintext buffers and encrypted temporary files. Defaults are 128 multipart uploads and a combined 16 GiB encrypted spool budget, adjustable with max_uploads and spool_budget_bytes; a PUT or part is limited to 5 GiB. Parts other than the last selected part must be at least 5 MiB; at most 10,000 parts may be selected. Upload IDs belong to their initiating application, bucket, key and owner session. Completion publishes one vault write. Abort, 24-hour expiry, sealing and shutdown release temporary state; restart invalidates unfinished uploads. Slow or unconsumed downloads remain cancellable during sealing. Protocol tracing is isolated because upstream debug events can contain decoded keys and signing material. Access-audit stages remain separate from tracing.
Application server controls
The authenticated JSON service also exposes exact-version durability barriers, FileId read/write routes, exact file pins, bounded access-heat prefetch and optional millisecond retention timings. See Application server API for routes, receipts and cancellation semantics.
Status and shutdown
GET /health returns process liveness, sealed, vaults_count, sealed_count and recovery_pending_count, without vault paths, identities or credentials. GET /ready returns 200 only when every configured vault is unlocked, otherwise 503. Alias /health and /ready describe that vault alone. Status endpoints do not replace authenticated file access.
With sync enabled, configured health/readiness responses also include directory_delete_attempts_count: all Directory backend DELETE calls in this process, including failed and absent-key attempts. The counter resets on process startup; it is not a persisted deletion history. After draining, serve prints a JSON serve_stopped event with the final counter. Record both the process lifetime and shutdown count when checking a period spanning restarts.
SIGINT and, on Unix, SIGTERM stop accepting TCP connections, revoke sessions, stop and join mounts, drain accepted operations and synchronization, and release local endpoints and leases. Slow TCP connections have a bounded shutdown drain. cipherfs seal / POST /api/v1/seal revokes sessions, stops sync and mounts, drains work and clears keys while retaining the lease and private endpoint. cipherfs unseal or /api/v1/unseal authenticates that same owner; old handles and sessions stay revoked. Vault lock/logout closes that vault; a configured daemon continues serving its other vaults and permits a later unlock. The local-only single-vault command exits when its vault closes.
Download worker failures emit fixed error categories, OS error numbers and queued byte counts, without plaintext paths, credentials or provider messages. A CLI download that loses its response body reports the byte count already written and exits nonzero; it never treats that prefix as a complete file. Check the owner log to distinguish unavailable content, filesystem errors, cancellation and integrity failures.
Owner mounts
cipherfs mount --vault ~/vault --mount-point ~/mounted
cipherfs mount --vault ~/vault --mount-point ~/mounted --unmount
If a reachable owner exists, these commands ask it to mount or unmount and return after the operation completes; the mount stays with the owner. Only that owner's tracked target can be unmounted through its API. Closing the vault unmounts all its targets before releasing the lease. If no owner exists, the mount command becomes the owner and publishes the same local API; --background retains its existing private password/readiness handshake. The owner build must include fuse (the normal default includes fuse-t); Windows reports mounting as unsupported.
Mounts use the existing empty-target, non-overlap, read-only and backend checks. On macOS, native FUSE-T remains the default; Linux uses kernel FUSE. Configured stores/peers continue supplying Partial content through the owner's existing background runtime.