⚠️ This issue respects the following points: ⚠️
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
- 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, current test())
lib/private/Files/Storage/Wrapper/Availability.php (RECHECK_TTL_SEC)
lib/private/Files/ObjectStore/S3ConnectionTrait.php (parallel pattern for object store, uses verify_bucket_exists rather 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?
Configuration report
List of activated Apps
Nextcloud Signing status
Nextcloud Logs
Additional info
No response
Bug description
Summary
apps/files_external/lib/Lib/Storage/AmazonS3.php::test()callsheadBucket()to verify the configured external S3 storage is reachable. TheAvailabilitystorage wrapper invokestest()and, when it throws, recordsavailable=falsein the storage availability cache. Subsequent file operations short-circuit to HTTP 503 untilRECHECK_TTL_SEC(10 min) and the nexttest()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
<bucket-name>s3.<region>.backblazeb2.comus-west-004)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>:nextcloud.log:(The CreateBucket attempt is
S3ConnectionTrait::getConnection()'s autocreate branch, separately fixable viaverify_bucket_exists=false. After fixing that, theAvailabilitywrapper still fails ontest()-> HeadBucket and permanently marks the storage unavailable.)Root cause
apps/files_external/lib/Lib/Storage/AmazonS3.php::test():lib/private/Files/Storage/Wrapper/Availability.phpcallstest()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 onlyreadBuckets); HeadBucket returned 403 in all cases. The same keys returned 200 forListObjectsV2 (MaxKeys=1),GetBucketLocation,PutObject,GetObject,DeleteObjecton 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:GetBucketLocationis also a viable equivalent on B2;ListObjectsV2is more universally accepted across S3-compatible services and matches how the existinggetFolderContents()andunlink()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.phpobjectstoresection) becauselib/private/Files/ObjectStore/S3ConnectionTraituses a separateverify_bucket_existsflag for the bucket-existence check and does not invokeheadBucketfrom aStorage::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)Steps to reproduce
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?
Configuration report
List of activated Apps
Nextcloud Signing status
Nextcloud Logs
Additional info
No response