Features
An overview of what TinyS3 implements. For the operation-by-operation matrix see S3 API support.
Protocol
- AWS SigV4 and SigV2 signatures, header-based and presigned (7-day cap)
- Streaming chunked signing (
STREAMING-AWS4-HMAC-SHA256-PAYLOAD) - Browser POST-policy uploads with condition validation
- Virtual-hosted and path-style addressing
- Multi-user credentials via IAM; anonymous access via bucket policy / public ACL
- Auth rate limiting (
503 SlowDown), clock-skew rejection
Objects & buckets
- Put/get/head/delete/copy, ranged reads, all conditional headers
- Multipart uploads including UploadPartCopy with source ranges
- Bucket & object tagging, bulk delete (1000 objects, quiet mode, preconditions)
- Versioning with delete markers, version-specific reads, restore
- Object lock: retention (governance/compliance), legal hold
- Folders,
response-*query overrides, Snowball auto-extract (tar) - Bucket notifications — async webhook POSTs on object create/remove, configured via
PutBucketNotificationConfigurationwith event and prefix/suffix filters - Bucket quotas — hard per-bucket byte limits; over-quota writes refused with
507 QuotaExceeded - Custom response headers (TinyS3 extension, not S3) — configure extra HTTP headers added to successful object GET/HEAD responses per bucket, managed via the admin API (
/buckets/{bucket}/response-headers). Useful for serving assets into a cross-origin-isolated page (Cross-Origin-Resource-Policy,Cross-Origin-Embedder-Policy). Reserved/protocol headers and header-injection attempts are rejected.
Access control
- Canned and explicit-grant ACLs (group and email grantees)
- Bucket policies with condition keys (
s3:prefix, tags, encryption headers, copy-source, …) - Public access block, ownership controls, policy status
- Front-door IP access control — a blocklist (deny listed IPs/CIDRs) and a default-deny allowlist (when enforced, only listed IPs pass), enforced on the trusted-proxy-resolved client IP ahead of routing and auth, persisted cluster-wide. Loopback always passes and both have a kill-switch, so a bad rule is always recoverable. Managed from the console (Settings → IP Access Control) and the admin API.
Encryption
| Mode | Key management | Notes |
|---|---|---|
| SSE-C | Customer-provided per request | Key never stored server-side |
| SSE-S3 | Server master key or KMS | Requires TINY_S3_SSE_MASTER_KEY (or a KMS provider) |
| SSE-KMS | Server master key or KMS | Same key source; KMS key IDs bind the data key |
| At-rest shard encryption | TINY_S3_DISK_ENCRYPTION_KEY | Erasure shards AES-256-CTR + keyed MAC |
Requests demanding server-side encryption without a configured master key are refused with 501 — never silently stored in cleartext.
Envelope encryption & external KMS. Optionally, SSE objects are encrypted with a random per-object data key that is wrapped by a pluggable KMS provider and stored (wrapped) in the object's metadata. Providers (kms.provider): local (wraps under the TINY_S3_SSE_MASTER_KEY) and bitwarden (the root key is held in Bitwarden Secrets Manager and fetched at startup). It is opt-in — declare a kms: block to turn it on; without one, SSE uses the legacy master-key scheme and existing deployments are unchanged. The provider is an isolated module; adding another backend never touches the storage core. Objects written before this (no envelope) still decrypt via the legacy key path. See Configuration.
Storage & resilience
- Simple filesystem or Reed-Solomon erasure coding (default 4+2, tolerates 2 disk losses)
- Background health scanner detects and repairs missing/corrupted shards
- Tombstone-based fault-tolerant delete; per-write shard generations
- Lifecycle expiration (current- and noncurrent-version expiration, expired-delete-marker cleanup, abort-incomplete-multipart) and transition (cloud tiering) rules. A
Transitionmoves aged objects' bytes to a configured tier (a saved remote S3 endpoint + bucket) and leaves a local stub; GET streams them back transparently. A transition to a storage class with no configured tier is refused (501) rather than accepted and ignored. Tiers are managed via the admin API (/rest/api/v1/tiers). The transition is crash-safe: the tier pointer is written durably before the local bytes are freed, so a crash mid-transition leaves the object fully local, never lost. Tier endpoints must usehttps(object data crosses the network), are SSRF-guarded at both save time and connect time, and a tiered read is verified against the stored size (and ETag, for single-part objects) before any bytes are served — a tier returning the wrong, truncated, or substituted object is refused, not streamed under a valid-looking ETag. (v1: encrypted, inline, and versioned-bucket objects are not tiered; deleting a transitioned object best-effort removes the tier copy.)
Bucket replication
Rule-driven, asynchronous replication to a local or remote bucket — durable queue with retries and a background scanner, per-object x-amz-replication-status, delete-marker propagation, metadata mutations, existing-object replication, loop-safe two-way, SSE re-encryption, and an admin resync endpoint. See Bucket replication.
IAM & STS
The AWS IAM API: users, groups, roles, managed & inline policies, access keys — persisted and enforced on every S3 request. See IAM API.
STS temporary credentials — AssumeRole and GetCallerIdentity. A caller allowed by a role's trust policy receives short-lived session credentials (access key, secret, session token) that authorize S3 requests against the role's policies. Session tokens are stateless (HMAC-signed with a key derived from the root secret), so they survive restarts and work cluster-wide with no session store; the session secret is never transmitted after the AssumeRole response. Temporary sessions cannot manage IAM or mint further credentials, and the trust-policy subset evaluates Effect/Action/Principal — a statement carrying a Condition fails closed (never grants) rather than ignoring it. Session duration is bounded to AWS's 900–43200s.
S3 Select
SQL over CSV and JSON objects with proper event-stream response framing.
Static website hosting & logging
Per-bucket website hosting with custom domains: map a domain to a bucket — either the ?customDomain mapping (bucket keeps its own name) or by naming the bucket after the domain — point DNS at the server, and requests arriving with that Host are served as a website: index-document resolution (/ and prefix/ → index.html), error documents on 404, anonymous access via the bucket policy. bucket.<server-host> subdomains work too. Website serving is Host-based — it runs ahead of the console/REST routing, so a mapped domain's / and /index.html resolve to the bucket's objects, never the console. (The bare API host is not a website endpoint; use a mapped domain or subdomain.) Plus S3 server access logging to a target bucket.
Operability
- Native TLS
- Prometheus metrics (
/tinys3/metrics,text/plain; version=0.0.4) and JSON metrics (/rest/api/v1/metrics) — request and error counters labelled by operation, HTTP-status class and error code; per-operation latency summaries (_sum/_count) plus a max gauge; upload/download and disk-I/O byte counters; storage-capacity gauges (total/free/used); and, in cluster mode, cluster health (nodes up, write quorum, rack fault tolerance). Point a Prometheus scrape at/tinys3/metrics. - Distributed tracing — optional OpenTelemetry (OTLP) export of one SERVER span per S3 request (operation, bucket/key, status, latency). Off by default; enable with the
telemetry:block orTINY_S3_OTEL_ENABLED/TINY_S3_OTEL_ENDPOINT. A build without a collector opens no connection. - Configurable logging — one named, rotating log file (async, no request-path cost) plus the console, via
logging:in the config orTINY_S3_LOG_FILE/TINY_S3_LOG_LEVEL - Audit logging — one structured JSON record per S3 request (operation, bucket/key, principal, status, latency, bytes, source, TLS) for security/compliance retention, emitted off the request thread to a JSON-lines file and/or a tamper-evident WORM archive bucket (versioned + object-lock, hash-chained, with per-flush manifest seals). The console Audit page adds a live request tail and a paginated archive browser. Independent of S3 access logging; off by default. Configured via the
audit:block orTINY_S3_AUDIT_ENABLED/TINY_S3_AUDIT_FILE. Never records credentials, signatures, or keys; delivery is best-effort (drops with a sealedAUDIT_GAPmarker under backpressure rather than slowing a request) - Tunable write durability (
storage.fsync:none/data/full) — fsync object data before it is visible, optionally the rename too; mode-aware default - REST admin API: server info, erasure health, healing, transfer & sync jobs
- Web console at
/console/ - Public endpoint via Cloudflare Tunnel — optional opt-in plugin that exposes the server on a public
https://URL (--allow-cloudflared-public-endpoint)
Not implemented
- STS
AssumeRoleWithWebIdentity(OIDC federation), cross-account principals - Analytics family (
?metrics,?inventory,?analytics,?intelligent-tiering) - BitTorrent (
?torrent— deprecated by AWS), Object Lambda, S3 Express