Skip to content

Storage Fine Tuning ​

This page is for sysadmins who want to tune where OxiCloud places upload data on disk and why the placement matters for throughput and memory. The defaults work; the gains from a tuned layout are significant on busy instances or constrained containers.

The upload lifecycle in 30 seconds ​

        ┌─── direct (single-PUT / multipart) ───┐
client ─┤                                       │    Streamed DIRECTLY into the
        │                                       ├──► content-addressable store:
        │                                       │    CDC chunking + BLAKE3 +
        │                                       │    dedup checks happen while
        │                                       │    the bytes arrive. No spool
        │                                       │    file, no re-read; chunks
        │                                       │    the store already has are
        │                                       │    never written at all.
        │                                       │
        └─── multi-chunk upload ────────────────┤    Chunk PARTS accumulate on
             (`/api/uploads` /                  │    disk under OXICLOUD_CHUNK_DIR
              `/dav/uploads/...`)               │    until /complete, which
                                                │    streams them (in order)
                                                ▼    through the same CDC path.
                                  ┌─────────────────────────┐
                                  │ STORAGE BACKEND          │
                                  │  • local FS (.blobs/)    │
                                  │  • S3-compatible         │
                                  │  • Azure Blob            │
                                  └─────────────────────────┘

Two practical consequences:

  • Direct uploads no longer use a spool directory. Each uploaded byte is written at most once — straight into the blob backend as a CDC chunk. Re-uploads and edited files write only the chunks the store doesn't already have.
  • The chunk-session directory sees write-heavy churn during multi-chunk uploads — fast disk (NVMe) and sufficient free space matter more here than on the final storage backend.

Upload size caps — what each one bounds ​

Three independent caps control how large an upload OxiCloud will accept. Pick them with disk sizing in mind: the chunk directory must be able to hold the worst case (cap × concurrent sessions).

VariableDefaultWhat it capsWhen it fires
OXICLOUD_MAX_UPLOAD_SIZE10 GBWhole-file ceiling. Applies to both direct PUT (per-body) and chunked uploads (declared total_size). The absolute upper bound on any single file in OxiCloud.Chunked: at POST /api/uploads against the JSON-declared total_size, before any chunk is uploaded. Direct PUT: indirectly via OXICLOUD_DIRECT_PUT_MAX_BYTES, which is expected to be ≤ OXICLOUD_MAX_UPLOAD_SIZE.
OXICLOUD_DIRECT_PUT_MAX_BYTES1 GiBNon-chunked PUT body. Per-request cap for PUT /webdav/... and PUT /remote.php/dav/files/.../.... Set below OXICLOUD_MAX_UPLOAD_SIZE so larger files are pushed onto the chunked protocol — which is resumable on failure.During body streaming, as a per-frame accumulator. Excess → 413 with a "use chunked upload" hint.
OXICLOUD_CHUNK_MAX_BYTES100 MBPer-chunk body in a chunked-upload session (PATCH /api/uploads/{id} or PUT /remote.php/dav/uploads/.../chunk). Independent of the whole-file cap — a 5 GB file in 100 MB chunks is 50 PATCHes each bounded by this.During chunk-body streaming. Excess → 413.

Recommendation: prefer chunked uploads for large files ​

The defaults (OXICLOUD_DIRECT_PUT_MAX_BYTES = 1 GiB, well below OXICLOUD_MAX_UPLOAD_SIZE = 10 GB) are deliberately asymmetric. Files between those two caps can only succeed via the chunked protocol. The reason is resilience: a direct PUT at 95 % of 5 GB that drops loses everything (the partially ingested chunks are reclaimed by GC, but the client must restart from byte 0). The same drop on a chunked upload loses one ~5 MB chunk; the client retries that chunk and continues. NextCloud desktop and the OxiCloud web UI already switch to chunked at ~10 MB (CHUNKED_UPLOAD_THRESHOLD).

Disk sizing ​

OxiCloud streams bodies frame-by-frame, so RAM is bounded (~10 MB per in-flight upload for the CDC ingest buffers) regardless of the caps. Disk space scales with the caps:

  • Direct PUT / multipart: no transient spool. Bytes land directly in the blob backend as deduplicated chunks; worst-case extra disk per upload is the file's own (deduplicated) size — the same space the stored file occupies afterwards.
  • Chunked upload: each in-flight session accumulates its chunk parts under OXICLOUD_CHUNK_DIR until /complete streams them into the blob store and the session is cleaned up. Worst case disk per session = file_size (the parts); total = OXICLOUD_MAX_UPLOAD_SIZE × concurrent_chunked_sessions.
SettingsChunked worst case (5 sessions)Safe on 4 GB volume?
Defaults: OXICLOUD_MAX_UPLOAD_SIZE=10 GB50 GB❌ overflows
OXICLOUD_MAX_UPLOAD_SIZE=500 MB2.5 GB✅ fits

Don't put OXICLOUD_CHUNK_DIR on tmpfs ​

In many container setups the OS temp dir is tmpfs — RAM-backed storage that counts against the cgroup memory limit. A few concurrent multi-GB chunked sessions on tmpfs will wake the OOMKiller long before the uploads finish. Point OXICLOUD_CHUNK_DIR at a real-disk directory in containers.

TL;DR ​

VariableDefaultPurpose
OXICLOUD_STORAGE_PATH./storageWhere .blobs/ lives (the canonical content store)
OXICLOUD_CHUNK_DIR{STORAGE_PATH}/.uploadsWhere chunked-upload sessions accumulate

The two rules that matter most:

  1. Keep OXICLOUD_CHUNK_DIR off tmpfs (the default in many containers) — chunk parts count against the cgroup memory limit and can trigger OOMKill on multi-GB uploads.
  2. NVMe for the chunk dir pays off on deployments with heavy large-file traffic: each chunk PUT writes a file and the progress bitmap, and /complete reads them all back in order.

Where each upload surface writes ​

SurfaceDefault destinationConfigurable via
REST chunked PUT (PATCH /api/uploads/{id}){STORAGE_PATH}/.uploads/{upload_id}/chunk_NNNNNNOXICLOUD_CHUNK_DIR
NextCloud chunked PUT (PUT /dav/uploads/.../chunk){STORAGE_PATH}/.uploads/nextcloud/{user}/{upload_id}/{chunk_name}OXICLOUD_CHUNK_DIR
Direct PUT / multipart / WOPI / chunked /completestraight into the blob backend (CDC chunks)OXICLOUD_STORAGE_PATH (local backend)
Final blob storage{STORAGE_PATH}/.blobs/{ab}/{abc…}.blobOXICLOUD_STORAGE_PATH

The local blob backend stages each chunk write under {STORAGE_PATH}/.dedup_temp/ and promotes it with an atomic rename(2) — both directories live under OXICLOUD_STORAGE_PATH, so same-filesystem placement (and therefore atomic promotion) is automatic and not separately configurable.

Single-disk box (most common) ​

Defaults are fine:

bash
OXICLOUD_STORAGE_PATH=/var/lib/oxicloud
# OXICLOUD_CHUNK_DIR unset → /var/lib/oxicloud/.uploads

Container with constrained memory ​

Critical: make sure the chunk dir doesn't sit on tmpfs.

bash
OXICLOUD_STORAGE_PATH=/data
OXICLOUD_CHUNK_DIR=/data/.uploads

Split-disk (NVMe intake + HDD blobs) ​

bash
OXICLOUD_STORAGE_PATH=/mnt/hdd/oxicloud      # .blobs/ + .dedup_temp/
OXICLOUD_CHUNK_DIR=/mnt/nvme/oxi-chunks

Chunk parts land on NVMe (fast PUTs, fast /complete read-back); the deduplicated chunks are written once to the HDD-backed blob store as /complete streams through them.

Sharing the chunk directory ​

The REST and NC chunked surfaces can share OXICLOUD_CHUNK_DIR by design. Each writer tags its output so they never interfere:

WriterOn-disk name pattern
REST chunked sessionsoxi-chunk-{uuid}/ — directories with a well-known prefix
NC chunked subtreenextcloud/{user}/{uuid}/ — under its own root subdir

The 24-hour orphan-session cleanup loop filters strictly on the oxi-chunk- prefix, so it can NEVER delete a non-OxiCloud directory that happens to live alongside chunked sessions.

Quick verification ​

Boot the server with RUST_LOG=info and the first lines after the banner include:

oxicloud: Upload limits loaded from config max_upload_size_mb=10240 chunk_max_bytes_mb=100

That confirms the upload-cap env vars were read. To confirm directory placement, watch for chunk file creation under your OXICLOUD_CHUNK_DIR (or its default {STORAGE_PATH}/.uploads/) during a chunked upload — ls while a sync is in progress shows the {uuid}/chunk_NNNNNN files appearing in real time.

Released under the MIT License.