⚠️ 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
- Configure S3 primary object storage (any
IChunkedFileWrite / IObjectStoreMultiPartUpload backend) and a Redis distributed cache (memcache.distributed).
- 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().
- Upload the large file through the web uploader (
MKCOL /remote.php/dav/uploads/<user>/web-file-upload-<id>, then PUT …/<n> chunks).
- ~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?
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.
Bug description
With chunking v2 (the bulk/web uploader,
OCA\DAV\Upload\ChunkingV2Plugin), the per-session metadata — the multipartuploadId, target path and target id — is stored in the distributed cache with a hard-coded 24h TTL (86400), set once inafterMkcol()and never refreshed by subsequent chunkPUTs (beforePut()only reads it viaprepareUpload()).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 chunkPUTfinds no metadata andcheckPrerequisites()throws:The web uploader then
400s the remaining queued chunks andDELETEs the whole session, discarding ~24h of successfully-uploaded data. NoMOVE/finalization ever happens.This is distinct from #38801: there the distributed cache is absent/misconfigured (the
checkPrerequisites()"no proper distributed cache"BadRequestpath). 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
412right 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 onmaster; 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()— onlygets the metadata.checkPrerequisites()— throwsPreconditionFailed('Missing metadata for chunked upload…')whenuploadId/uploadPathare null (L290).Steps to reproduce
IChunkedFileWrite/IObjectStoreMultiPartUploadbackend) and a Redis distributed cache (memcache.distributed).86400TTL to a few minutes inafterMkcol().MKCOL /remote.php/dav/uploads/<user>/web-file-upload-<id>, thenPUT …/<n>chunks).MKCOL(or after the shortened TTL), observe the next chunkPUTreturn412"Missing metadata for chunked upload", the following chunks return400, and the client issues aDELETEon the session. The upload is lost.Observed server-side log sequence (real incident, single nginx replica):
Expected behavior
An actively-progressing upload must not be garbage-collected on wall-clock age alone. Either:
PUTinbeforePut(), so the session only expires after 24h of inactivity; and/or86400.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?
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
Nextcloud Signing status
Nextcloud Logs
Additional info
partId 1..10000guard (beforePut()) is not the limiter here — failure occurs at part ~1001, well within range; quota is also not involved (plenty of free space).