Skip to content

[Bug]: Concurrent PUT to the same chunk returns HTTP 500 after successful upload finalization #64637

Description

@Gdo23678

⚠️ This issue respects the following points: ⚠️

  • This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
  • This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
  • I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
  • I agree to follow Nextcloud's Code of Conduct.
  • I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.

Bug description

While investigating an Android Auto Upload problem, I noticed a server-side
behaviour that I was able to reproduce independently of the Android client.

If two PUT requests for the same chunk of the same chunked-upload collection
overlap, and one request finishes before the upload is finalized with MOVE,
the still-running PUT later returns HTTP 500.

The MOVE itself succeeds and the resulting file is complete and correct.

I reproduced this twice:

  • once with a 1 MiB payload
  • once with a 32 MiB random payload and a deliberately throttled PUT

In the 32 MiB test, the first PUT was confirmed to still be running when the
MOVE was performed.

I am not sure whether HTTP 500 is considered the expected response for this
race condition, so I reduced it to a client-independent curl reproducer.

Steps to reproduce

  1. Create a chunked upload collection:

    MKCOL /remote.php/dav/uploads/{user}/{upload-id}

    Result: HTTP 201

  2. Start a PUT to chunk 000001 and deliberately throttle the request so that
    it remains active.

  3. While the first PUT is still active, start a second PUT of the same chunk:

    PUT /remote.php/dav/uploads/{user}/{upload-id}/000001

    Result: HTTP 201

  4. Confirm that the first PUT is still running.

  5. Finalize the upload:

    MOVE /remote.php/dav/uploads/{user}/{upload-id}/.file

    with:

    Destination: /remote.php/dav/files/{user}/{target-file}
    OC-Total-Length: {payload-size}

    Result: HTTP 201

  6. Wait for the first, still-running PUT to finish.

    Result: HTTP 500

  7. Download the final file.

    Result: HTTP 200. File size and SHA-256 match the original payload.

Expected behavior

I am not sure which WebDAV status code is preferred when an upload collection
has already been successfully finalized while another PUT to that collection
is still in progress.

However, returning HTTP 500 and logging "source directory is not writable"
seems misleading in this situation because the storage is writable, the MOVE
completed successfully, and the final file exists with the correct content.

Ideally this race should be handled without reporting an internal server
error.

Nextcloud Server version

35

Operating system

Other

PHP engine version

PHP 8.4

Web server

Nginx

Database engine version

PostgreSQL

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

Upgraded to a MAJOR version (ex. 31 to 32)

Are you using the Nextcloud Server Encryption module?

None

What user-backends are you using?

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

Configuration report

{
    "system": {
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "filesystem_check_changes": 0,
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": {
            "1": "cloud.example.com",
            "2": "example.com",
            "3": "127.0.0.1",
            "4": "localhost",
            "7": "<internal-ip>",
            "8": "nextcloud",
            "9": "<internal-container-ip>",
            "10": "<internal-container-ip>"
        },
        "tempdirectory": "/temp",
        "skeletondirectory": "/config/custom_skeleton",
        "supportedDatabases": [
            "pgsql"
        ],
        "dboptions": {
            "2": 30,
            "3": 2
        },
        "overwrite.cli.url": "https://cloud.example.com",
        "overwritehost": "cloud.example.com",
        "overwriteprotocol": "https",
        "overwritecondaddr": "^<internal-container-subnet>$",
        "has_internet_connection": true,
        "dbtype": "pgsql",
        "version": "35.0.0.10",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "default_phone_region": "<redacted>",
        "default_language": "<redacted>",
        "default_locale": "<redacted>",
        "maintenance": false,
        "filelocking.enabled": true,
        "filelocking.debug": false,
        "filelocking.ttl": 43200,
        "apps_paths": [
            {
                "path": "/app/www/public/apps",
                "url": "/apps",
                "writable": false
            },
            {
                "path": "/config/www/nextcloud/apps",
                "url": "/custom_apps",
                "writable": true
            }
        ],
        "mail_from_address": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpmode": "smtp",
        "mail_sendmailmode": "smtp",
        "mail_domain": "***REMOVED SENSITIVE VALUE***",
        "mail_smtphost": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpport": "587",
        "mail_smtpauth": true,
        "mail_smtpname": "***REMOVED SENSITIVE VALUE***",
        "mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpauthtype": "LOGIN",
        "mail_smtptimeout": 30,
        "mail_smtp_address_family": "ipv4",
        "ffmpeg": "/usr/bin/ffmpeg",
        "enable_previews": true,
        "preview_format": "webp",
        "updater.release.channel": "stable",
        "loglevel": 2,
        "log_query_overhead": false,
        "updater.secret": "***REMOVED SENSITIVE VALUE***",
        "upgrade.disable-web": true,
        "maintenance_window_start": 1,
        "defaultapp": "files",
        "forbidden_filename_characters": [
            "\\",
            "/"
        ],
        "preview_max_memory": 1024,
        "preview_max_x": 2048,
        "preview_max_y": 2048,
        "preview_max_scale_factor": 1,
        "preview_render_max_scale": 1,
        "preview_max_filesize_image": 50,
        "preview_quality": 85,
        "preview_standard_res": "64 256 512 1024 2048",
        "data-fingerprint": "<redacted>",
        "asset-pipeline.enabled": false,
        "memlimit": "4G",
        "htaccess.RewriteBase": "/",
        "x_permitted_cross_domain_policies": "none",
        "appstoreenabled": true,
        "versions_retention_obligation": "auto, 3",
        "shareapi_allow_share_dialog_user_enumeration": false,
        "preview_ffmpeg_path": "/usr/bin/ffmpeg",
        "lorelejay.preview_on_demand": false,
        "logfilemode": 436,
        "Dav": {
            "timeout": 7200
        },
        "logtimezone": "<redacted>",
        "log_type": "file",
        "logfile": "/data/nextcloud.log",
        "allow_local_remote_servers": false,
        "proxy_proxies": 0,
        "logformat": "",
        "log_rotate_size": 104857600,
        "memories.db.triggers.fcu": true,
        "memories.exiftool": "/app/www/public/apps/memories/bin-ext/exiftool-amd64-musl",
        "show_hidden": true,
        "airlock.ignore_nomedia": true,
        "logperm": 436,
        "preview_max_filesize_executable": 50,
        "preview_max_scale": 1,
        "preview_jpeg_resample": true,
        "image_magick_resample": true,
        "opcache_optimization_level": 2147467263,
        "enabledPreviewProviders": [
            "OC\\Preview\\WebP",
            "OC\\Preview\\HEIC",
            "OC\\Preview\\JPEG",
            "OC\\Preview\\PNG",
            "OC\\Preview\\GIF",
            "OC\\Preview\\BMP",
            "OC\\Preview\\XBitmap",
            "OC\\Preview\\Movie",
            "OC\\Preview\\PDF",
            "OC\\Preview\\MP3",
            "OC\\Preview\\TXT",
            "OC\\Preview\\MarkDown",
            "OC\\Preview\\Krita",
            "OC\\Preview\\SVG",
            "OC\\Preview\\Image",
            "OC\\Preview\\TIFF",
            "OC\\Preview\\Font",
            "OC\\Preview\\OpenDocument",
            "OC\\Preview\\Imagick"
        ],
        "preview_generation_max_instruction_count": 2000000,
        "serverid": "<redacted>",
        "mail_smtpstreamoptions": {
            "ssl": {
                "allow_self_signed": false,
                "verify_peer": true,
                "verify_peer_name": true
            }
        },
        "memcache.local": "\\OC\\Memcache\\APCu",
        "memcache.distributed": "\\OC\\Memcache\\Redis",
        "memcache.locking": "\\OC\\Memcache\\Redis",
        "redis": {
            "host": "***REMOVED SENSITIVE VALUE***",
            "port": 6379,
            "password": "***REMOVED SENSITIVE VALUE***",
            "timeout": 0
        },
        "memories.vod.disable": false,
        "memories.vod.external": true,
        "memories.vod.ffmpeg": "/usr/bin/ffmpeg",
        "memories.vod.ffprobe": "/usr/bin/ffprobe",
        "memories.vod.bind": "",
        "memories.vod.connect": "<internal-service>:47788",
        "memories.vod.nvenc": true,
        "memories.vod.path": "/config/www/nextcloud/apps/memories/bin-ext/go-vod-amd64",
        "memories.index.path.blacklist": "\\.thumbnails|@Recycle|eaDir",
        "preview_exclude_folders": [
            ".thumbnails",
            "Thumbnails",
            "Recycle-Bin",
            "recyclebin",
            "@eaDir"
        ],
        "preview_concurrency_all": 4,
        "preview_concurrency_new": 2,
        "memories.vod.use_gop_size": true,
        "referrer_policy": "no-referrer-when-downgrade",
        "check_data_directory_permissions": false,
        "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
        "files.chunked_upload.max_parallel_count": 1,
        "files.chunked_upload.max_size": 104857600
    }
}

List of activated Apps

Enabled:
  - activity: 8.0.0
  - app_api: 35.0.0
  - appstore: 2.0.0-dev.0
  - bruteforcesettings: 8.0.0
  - cloud_federation_api: 2.0.0-dev.0
  - comments: 2.0.0-dev.0
  - contactsinteraction: 2.0.0-dev.0
  - dav: 2.0.0-dev.1
  - drop_account: 3.1.0
  - federatedfilesharing: 2.0.0-dev.1
  - federation: 2.0.0-dev.0
  - files: 3.0.0-dev.0
  - files_accesscontrol: 6.0.0
  - files_downloadlimit: 5.3.0
  - files_lock: 35.0.0
  - files_pdfviewer: 8.0.0
  - files_sharing: 2.0.0-dev.1
  - files_trashbin: 2.0.0-dev.0
  - files_versions: 2.0.0-dev.0
  - logreader: 8.0.0
  - lookup_server_connector: 2.0.0-dev.0
  - memories: 9.0.1
  - nextcloud_announcements: 7.0.0
  - notifications: 8.0.0
  - notify_push: 1.4.1
  - oauth2: 2.0.0-dev.0
  - office: 1.1.0
  - password_policy: 7.0.0
  - photos: 8.0.0
  - privacy: 7.0.0
  - profile: 2.0.0-dev.0
  - provisioning_api: 2.0.0-dev.0
  - registration: 3.1.0
  - related_resources: 6.0.0
  - serverinfo: 7.0.0
  - settings: 2.0.0-dev.0
  - sharebymail: 2.0.0-dev.0
  - sharing: 1.0.4
  - support: 7.0.0
  - text: 9.0.0
  - theming: 3.0.0-dev.0
  - twofactor_backupcodes: 2.0.0-dev.0
  - twofactor_totp: 17.1.0
  - updatenotification: 2.0.0-dev.0
  - viewer: 8.0.0
  - webhook_listeners: 2.0.0-dev.0
  - workflowengine: 3.0.0-dev.0

Nextcloud Signing status

No errors have been found.

Nextcloud Logs

{"reqId":"<redacted>","level":3,"time":"2026-09-21T16:14:26+02:00","remoteAddr":"<redacted-ip>","user":"<redacted-user>","app":"core","method":"PUT","url":"/remote.php/dav/uploads/<redacted-user>/<upload-id>/000001","scriptName":"/remote.php","message":"unable to rename, source directory is not writable : uploads/<upload-id>","userAgent":"curl/8.21.0","version":"35.0.0.10","data":{"app":"core"}}

{"reqId":"<redacted>","level":3,"time":"2026-09-21T16:14:26+02:00","remoteAddr":"<redacted-ip>","user":"<redacted-user>","app":"webdav","method":"PUT","url":"/remote.php/dav/uploads/<redacted-user>/<upload-id>/000001","scriptName":"/remote.php","message":"renaming part file to final file failed $renameOkay: false, $fileExists: false)","userAgent":"curl/8.21.0","version":"35.0.0.10","data":{"app":"webdav","source_storage":"home::<redacted-user>","target_storage":"home::<redacted-user>","source_internal_path":"uploads/<upload-id>/<temporary-part-file>","target_internal_path":"uploads/<upload-id>/000001"}}

{"reqId":"<redacted>","level":3,"time":"2026-09-21T16:14:26+02:00","remoteAddr":"<redacted-ip>","user":"<redacted-user>","app":"webdav","method":"PUT","url":"/remote.php/dav/uploads/<redacted-user>/<upload-id>/000001","scriptName":"/remote.php","message":"Konnte temporäre Datei nicht in die endgültige Datei umbenennen","userAgent":"curl/8.21.0","version":"35.0.0.10","exception":{"Exception":"Sabre\\DAV\\Exception","Message":"Konnte temporäre Datei nicht in die endgültige Datei umbenennen","Code":0,"File":"/config/www/nextcloud/apps/dav/lib/Connector/Sabre/File.php","Line":339}}

Additional info

Environment

  • Nextcloud Server: 35.0.0.10 (35.0.0)
  • Container: LinuxServer.io Nextcloud Docker container on an Unraid host
  • PHP: 8.4.16
  • Database: PostgreSQL 16.15
  • Web server / reverse proxy: Nginx / SWAG
  • Redis is used for file locking
  • Server-side encryption: disabled
  • User backend: default database backend
  • files.chunked_upload.max_parallel_count: 1
  • files.chunked_upload.max_size: 104857600

The reproducer was run through the normal public HTTPS path. The concurrent
requests were deliberately created by curl; this test does not rely on the
Android client to create them.

Observed request sequence

In the 32 MiB test:

  • MKCOL upload collection -> HTTP 201
  • PUT 000001 (slow request starts)
  • PUT 000001 (second request) -> HTTP 201
  • MOVE .file -> HTTP 201
  • PUT 000001 (slow request completes) -> HTTP 500
  • GET final file -> HTTP 200

Immediately before sending the MOVE request, the slow PUT process was
confirmed to still be active.

The slow PUT completed about 50 seconds after the successful MOVE and
returned HTTP 500.

The resulting 32 MiB file was nevertheless complete:

  • downloaded size matched the original size
  • SHA-256 matched the original payload

The same behaviour was also reproduced independently with a 1 MiB payload.

Related Android issue

I originally noticed a similar request sequence while investigating my
Android Auto Upload issue: nextcloud/android#17671

The Android issue concerns the client-side Auto Upload problem. This server
report is intentionally narrower and only describes the independently
reproducible server response to overlapping PUT requests.

This report does not claim that Nextcloud Server causes the duplicate client
requests. The curl reproducer deliberately creates the overlap and is only
intended to isolate the server-side response to that race condition.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. Needs triagePending check for reproducibility or if it fits our roadmap35-feedbackbug

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions