Skip to content

[Bug]: Chunking v2 upload session expires after a fixed 24h TTL (never refreshed), breaking large uploads on slow connections with 412 "Missing metadata for chunked upload" #61604

Description

@nfk

⚠️ This issue respects the following points: ⚠️

Bug description

With chunking v2 (the bulk/web uploader, OCA\DAV\Upload\ChunkingV2Plugin), the per-session metadata — the multipart uploadId, target path and target id — is stored in the distributed cache with a hard-coded 24h TTL (86400), set once in afterMkcol() and never refreshed by subsequent chunk PUTs (beforePut() only reads it via prepareUpload()).

As a result, any chunked upload whose total wall-clock duration exceeds 24h fails, even though the distributed cache (Redis) is correctly configured and healthy. ~24h after the MKCOL, the cache entry simply ages out; the next chunk PUT finds no metadata and checkPrerequisites() throws:

412 Precondition Failed
Missing metadata for chunked upload. The distributed cache does not hold the information of previous requests.

The web uploader then 400s the remaining queued chunks and DELETEs the whole session, discarding ~24h of successfully-uploaded data. No MOVE/finalization ever happens.

This is distinct from #38801: there the distributed cache is absent/misconfigured (the checkPrerequisites() "no proper distributed cache" BadRequest path). Here the cache is present and working — the entry expires purely on age.

Impact: large files become effectively un-uploadable for users on slow uplinks. Real case: a ~10–12 GB file over an ADSL upstream (~120 KB/s effective). ~1000 chunks (10 MiB each) upload fine over ~24h, then the very next chunk gets 412 right at the 24h mark. The user can never beat the clock because the upload physically takes longer than the session lifetime.

Offending code (apps/dav/lib/Upload/ChunkingV2Plugin.php, same on master; line numbers from v32.0.11):

  • afterMkcol()$this->cache->set($this->uploadFolder->getName(), [...], 86400); (L133–137) — set once at session creation.
  • beforePut() — uploads the part but never re-sets the cache entry → no sliding expiration.
  • prepareUpload() — only gets the metadata.
  • checkPrerequisites() — throws PreconditionFailed('Missing metadata for chunked upload…') when uploadId/uploadPath are null (L290).

Steps to reproduce

  1. Configure S3 primary object storage (any IChunkedFileWrite / IObjectStoreMultiPartUpload backend) and a Redis distributed cache (memcache.distributed).
  2. Throttle the client uplink so a single large file takes > 24h to upload (e.g. ~12 GB at ~120 KB/s). To repro fast, temporarily lower the 86400 TTL to a few minutes in afterMkcol().
  3. Upload the large file through the web uploader (MKCOL /remote.php/dav/uploads/<user>/web-file-upload-<id>, then PUT …/<n> chunks).
  4. ~24h after the MKCOL (or after the shortened TTL), observe the next chunk PUT return 412 "Missing metadata for chunked upload", the following chunks return 400, and the client issues a DELETE on the session. The upload is lost.

Observed server-side log sequence (real incident, single nginx replica):

25/Jun 04:48:39  MKCOL …/web-file-upload-XXXX            201   ← session created
26/Jun 04:46:49  PUT   …/web-file-upload-XXXX/1000        200   ← last good chunk (~24h in)
26/Jun 04:53:18  PUT   …/web-file-upload-XXXX/1001        412   ← metadata expired
26/Jun 04:53:26  PUT   …/web-file-upload-XXXX/1002..1005  400
26/Jun 04:53:27  DELETE …/web-file-upload-XXXX            204   ← client aborts, data lost

Expected behavior

An actively-progressing upload must not be garbage-collected on wall-clock age alone. Either:

  • Refresh the TTL (sliding expiration) on every successful chunk PUT in beforePut(), so the session only expires after 24h of inactivity; and/or
  • make the TTL admin-configurable (system config) instead of a hard-coded 86400.

Nextcloud Server version

32

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.3

Web server

Nginx

Database engine version

PostgreSQL

Is this bug present after an update or on a fresh install?

Fresh Nextcloud Server install

Are you using the Nextcloud Server Encryption module?

Encryption is Disabled

What user-backends are you using?

  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other

Configuration report

{
  "system": {
    "memcache.local": "\\OC\\Memcache\\APCu",
    "apps_paths": [
      { "path": "/var/www/html/apps", "url": "/apps", "writable": false },
      { "path": "/var/www/html/custom_apps", "url": "/custom_apps", "writable": true }
    ],
    "integrity.check.disabled": true,
    "filelocking.enabled": true,
    "log_type": "syslog",
    "loglevel": 1,
    "default_phone_region": "FR",
    "memcache.distributed": "\\OC\\Memcache\\Redis",
    "memcache.locking": "\\OC\\Memcache\\Redis",
    "overwriteprotocol": "https",
    "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
    "objectstore": {
      "class": "\\OC\\Files\\ObjectStore\\S3",
      "arguments": {
        "bucket": "001",
        "region": "region",
        "hostname": "mystorage.com",
        "port": "443",
        "storageClass": "",
        "objectPrefix": "urn:oid:",
        "autocreate": true,
        "use_ssl": true,
        "use_path_style": true,
        "legacy_auth": false,
        "key": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "sse_c_key": "***REMOVED SENSITIVE VALUE***"
      }
    },
    "passwordsalt": "***REMOVED SENSITIVE VALUE***",
    "secret": "***REMOVED SENSITIVE VALUE***",
    "trusted_domains": ["mydomain"],
    "datadirectory": "***REMOVED SENSITIVE VALUE***",
    "dbtype": "pgsql",
    "version": "32.0.11.1",
    "dbname": "***REMOVED SENSITIVE VALUE***",
    "dbhost": "***REMOVED SENSITIVE VALUE***",
    "dbtableprefix": "oc_",
    "dbuser": "***REMOVED SENSITIVE VALUE***",
    "dbpassword": "***REMOVED SENSITIVE VALUE***",
    "installed": true,
    "instanceid": "***REMOVED SENSITIVE VALUE***",
    "maintenance": false,
    "appstoreenabled": false,
    "redis.cluster": {
      "seeds": "***REMOVED SENSITIVE VALUE***",
      "password": "***REMOVED SENSITIVE VALUE***"
    },
    "overwritehost": "mydomain",
    "files.chunked_upload.max_size": 10485760
  }
}

List of activated Apps

Enabled:
  - activity: 5.0.0
  - admin_audit: 1.22.0
  - bruteforcesettings: 5.0.0
  - calendar: 6.5.0
  - circles: 32.0.0
  - cloud_federation_api: 1.16.0
  - comments: 1.22.0
  - contacts: 8.3.13
  - contactsinteraction: 1.13.1
  - dashboard: 7.12.0
  - dav: 1.34.2
  - federatedfilesharing: 1.22.0
  - federation: 1.22.0
  - files: 2.4.0
  - files_downloadlimit: 5.0.0
  - files_reminders: 1.5.0
  - files_sharing: 1.24.1
  - files_trashbin: 1.22.0
  - files_versions: 1.25.0
  - lookup_server_connector: 1.20.0
  - notifications: 5.0.0
  - oauth2: 1.20.0
  - oidc_login: 3.3.1+murena.20260513
  - onlyoffice: 9.14.0
  - password_policy: 4.0.0
  - photos: 5.0.0
  - privacy: 4.0.0
  - profile: 1.1.0
  - provisioning_api: 1.22.0
  - serverinfo: 4.0.0
  - settings: 1.15.1
  - sharebymail: 1.22.0
  - spreed: 22.0.13
  - systemtags: 1.22.0
  - text: 6.0.2
  - theming: 2.7.0
  - twofactor_backupcodes: 1.21.0
  - updatenotification: 1.22.0
  - user_status: 1.12.0
  - viewer: 5.0.0
  - webhook_listeners: 1.3.0
  - workflowengine: 2.14.0
Disabled:
  - encryption: 2.20.0
  - files_external: 1.24.1
  - user_ldap: 1.23.0

Nextcloud Signing status

Nextcloud Logs

[25/Jun/2026:04:48:39] "MKCOL  /remote.php/dav/uploads/<user>/web-file-upload-XXXX"        201   ← session created
[26/Jun/2026:04:46:49] "PUT    /remote.php/dav/uploads/<user>/web-file-upload-XXXX/1000"    200   ← last good chunk (~24h in)
[26/Jun/2026:04:53:18] "PUT    /remote.php/dav/uploads/<user>/web-file-upload-XXXX/1001"    412   ← metadata expired (the bug)
[26/Jun/2026:04:53:26] "PUT    /remote.php/dav/uploads/<user>/web-file-upload-XXXX/1002"    400
[26/Jun/2026:04:53:26] "PUT    /remote.php/dav/uploads/<user>/web-file-upload-XXXX/1003"    400
[26/Jun/2026:04:53:26] "PUT    /remote.php/dav/uploads/<user>/web-file-upload-XXXX/1004"    400
[26/Jun/2026:04:53:26] "PUT    /remote.php/dav/uploads/<user>/web-file-upload-XXXX/1005"    400
[26/Jun/2026:04:53:27] "DELETE /remote.php/dav/uploads/<user>/web-file-upload-XXXX"         204   ← web uploader aborts, ~24h of data lost

Additional info

  • The partId 1..10000 guard (beforePut()) is not the limiter here — failure occurs at part ~1001, well within range; quota is also not involved (plenty of free space).
  • Workarounds available to admins/users today: upload from a faster link so the transfer completes in < 24h, or split the file into parts each uploadable in < 24h. There is no config knob to extend the session lifetime.

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions