S3 external storage rejects bucket-restricted Backblaze B2 keys due to HeadBucket check
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
⚠️ This issue respects the following points: ⚠️
- This is a bug, not a question or a configuration/webserver/proxy issue.
- This issue is not already reported on Github OR Nextcloud Community Forum (I've searched it).
- Nextcloud Server is up to date. See Maintenance and Release Schedule for supported versions.
- I agree to follow Nextcloud's Code of Conduct.
Bug description
Summary
apps/files_external/lib/Lib/Storage/AmazonS3.php::test() calls headBucket() to verify the configured external S3 storage is reachable. The Availability storage wrapper invokes test() and, when it throws, records available=false in the storage availability cache. Subsequent file operations short-circuit to HTTP 503 until RECHECK_TTL_SEC (10 min) and the next test() again succeeds.
Backblaze B2 (which Nextcloud supports as an S3-compatible target via the AmazonS3 external storage backend) returns HTTP 403 Forbidden for HeadBucket on any application key that has a bucket restriction (i.e., any key created with bucketIds), regardless of the key's capabilities. Therefore an external storage mount backed by a B2 bucket-restricted key is permanently marked unavailable, even though the key can perform all relevant file operations (Get / Put / List / Delete) on that bucket.
Affected version
Nextcloud Server 32.0.9 (confirmed). The same code path exists in older versions and main.
Steps to reproduce
- Create a Backblaze B2 application key restricted to a single bucket, e.g.:
b2 key create --bucket <bucket-name> mykey listFiles readFiles writeFiles deleteFiles readBuckets writeBuckets listAllBucketNames - In Nextcloud, configure an external storage mount with the "Amazon S3" backend:
- Bucket:
<bucket-name> - Hostname:
s3.<region>.backblazeb2.com - Region: B2 region (e.g.,
us-west-004) - Use SSL / use path-style: enabled
- Authentication: Access key with the application key id and key from step 1
- Bucket:
- Open the Files app or run
occ files_external:verify <mount-id>.
Expected behavior
The mount is usable. test() succeeds whenever the underlying credentials can list / read / write objects in the configured bucket, even if the S3-compatible service does not grant HeadBucket on that credential.
Current behavior
occ files_external:verify <mount-id>:
- status: error
- code: 1
- message: Aws\S3\Exception\S3Exception: Error executing "HeadBucket" on "https://s3.us-west-004.backblazeb2.com/<bucket>":
(client): 403 Forbidden (Request-ID: ...)
nextcloud.log:
files_external · OCP\Files\StorageNotAvailableException
Creation of bucket "<bucket>" failed. Error executing "CreateBucket" on "https://s3.us-west-004.backblazeb2.com/<bucket>":
AWS HTTP error: AccessDenied (client): not entitled
(The CreateBucket attempt is S3ConnectionTrait::getConnection()'s autocreate branch, separately fixable via verify_bucket_exists=false. After fixing that, the Availability wrapper still fails on test() -> HeadBucket and permanently marks the storage unavailable.)
Root cause
apps/files_external/lib/Lib/Storage/AmazonS3.php::test():
public function test(): bool {
$this->getConnection()->headBucket([
'Bucket' => $this->bucket,
]);
return true;
}
lib/private/Files/Storage/Wrapper/Availability.php calls test() and records unavailability on exception. RECHECK_TTL_SEC = 600.
On Backblaze B2, the S3-compatible API returns 403 Forbidden for HeadBucket on any application key with a bucket restriction. Verified on a real B2 account with five key shapes (master, MBAK 4-bucket, SBAK 1-bucket full caps, SBAK 1-bucket without listAllBucketNames, SBAK 1-bucket with only readBuckets); HeadBucket returned 403 in all cases. The same keys returned 200 for ListObjectsV2 (MaxKeys=1), GetBucketLocation, PutObject, GetObject, DeleteObject on the same bucket.
Proposed fix
Replace HeadBucket in test() with a check that does not require the HeadBucket privilege but that any working external-storage credential already has. ListObjectsV2(MaxKeys=1) works across AWS S3, MinIO, Wasabi, and Backblaze B2:
public function test(): bool {
$this->getConnection()->listObjectsV2([
'Bucket' => $this->bucket,
'MaxKeys' => 1,
]);
return true;
}
GetBucketLocation is also a viable equivalent on B2; ListObjectsV2 is more universally accepted across S3-compatible services and matches how the existing getFolderContents() and unlink() paths in the same file already exercise the bucket.
Workaround in user environment
Until this is fixed upstream, B2-backed external storage mounts are unusable in Nextcloud. The B2 bucket can still be used as the primary object store (config.php objectstore section) because lib/private/Files/ObjectStore/S3ConnectionTrait uses a separate verify_bucket_exists flag for the bucket-existence check and does not invoke headBucket from a Storage::test()-equivalent path.
Additional references
apps/files_external/lib/Lib/Storage/AmazonS3.php(line ~594, currenttest())lib/private/Files/Storage/Wrapper/Availability.php(RECHECK_TTL_SEC)lib/private/Files/ObjectStore/S3ConnectionTrait.php(parallel pattern for object store, usesverify_bucket_existsrather than calling HeadBucket unconditionally)- Backblaze B2 application key documentation (bucket restriction limits HeadBucket on S3-compatible API)
Steps to reproduce
- use external strage. backblaze. B2
- setup external strage
Expected behavior
can upload files.
Nextcloud Server version
32
Operating system
None
PHP engine version
None
Web server
None
Database engine version
None
Is this bug present after an update or on a fresh install?
None
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
List of activated Apps
Nextcloud Signing status
Nextcloud Logs
Additional info
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/files_external/lib/Lib/Storage/AmazonS3.php at test(), then read how lib/private/Files/Storage/Wrapper/Availability.php handles failures. Compare the bucket check with lib/private/Files/ObjectStore/S3ConnectionTrait.php and verify the external-storage entry point with a bucket-restricted Backblaze B2 key. Done means the mount verifies and supports file operations without requiring HeadBucket.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, php
- Domain
- backend, cloud
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100