S3 Storage for Laravel: How to Integrate Backblaze B2 with Flysystem

S3 Storage for Laravel: How to Integrate Backblaze B2 with Flysystem

Cloud storage using the S3 protocol is flexible, scalable, and widely used but often expensive. Backblaze B2 offers a cost-effective alternative with high availability. Thanks to its S3 compatibility, it can be seamlessly integrated into Laravel using Flysystem. However, a recent API change caused issues that can be quickly resolved with a small adjustment.

Flysystem is an abstraction library for Laravel that standardizes access to various storage solutions such as local disks, S3, or Backblaze B2. This allows for easy switching between storage providers without modifying the code. Benefits include simple configuration, consistent API methods, and broad support for both cloud and local storage.

Issues with Backblaze and S3 Compatibility in Laravel Flysystem

Backblaze B2 has always been reliable and straightforward for us. This makes the service an attractive alternative to AWS S3, especially for Laravel developers using the Flysystem storage interface. However, Backblaze recently made a change to its API that caused unexpected integration issues with S3 drivers—including Laravel Flysystem. 

The Issue: Rejected Requests Due to New API Restrictions

InvalidArgument (client): Unsupported header 'x-amz-checksum-crc32' received for this API call.
Unsupported header 'x-amz-checksum-crc32' received for this API call.

The changes to the Backblaze API resulted in certain requests being rejected with a 400 Bad Request error. This happens particularly when a request includes specific checksum headers. The following headers are now blocked by default by Backblaze:

  • x-amz-checksum-crc32
  • x-amz-checksum-crc32c
  • x-amz-checksum-crc64nvme
  • x-amz-checksum-sha1
  • x-amz-checksum-sha256
  • x-amz-checksum-algorithm


Since Laravel Flysystem automatically operates with S3 compatibility, these headers may be sent to Backblaze by Laravel’s storage layer—leading to the 400 Bad Request error.

The Quick Fix:
Adjusting the Flysystem Configuration

Fortunately, this issue can be fixed within seconds. Laravel Flysystem provides two configuration parameters that control this checksum calculation. With a simple adjustment in the storage configuration, we can ensure that these headers are only sent or validated when necessary.

To do this, add the following options within the S3 adapter in your config/filesystems.php file:

'request_checksum_calculation' => 'when_required',
'response_checksum_validation' => 'when_required',

These two parameters ensure that Laravel does not unnecessarily send or validate checksum headers, restoring compatibility with Backblaze B2. After this adjustment, all file uploads and downloads using Backblaze as S3 storage should work smoothly again.

What does the full integration of B2 into Flysystem look like?

The full integration can look like this. Here is an example configuration for config/filesystems.php:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Filesystem Disk
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default filesystem disk that should be used
    | by the framework. A "local" driver, as well as a variety of cloud
    | based drivers are available for your choosing. Just store away!
    |
    */

    'default' => env('FILESYSTEM_DRIVER', 'local'),

    /*
    |--------------------------------------------------------------------------
    | Filesystem Disks
    |--------------------------------------------------------------------------
    |
    | Here you may configure as many filesystem "disks" as you wish, and you
    | may even configure multiple disks of the same driver. Defaults have
    | been setup for each driver as an example of the required options.
    |
    | Supported Drivers: "local", "ftp", "sftp", "s3"
    |
    */

    'disks' => [

        /* your other disks... */

        'backblaze_bucket' => [
            'driver' => 's3',
            'key' => env('B2_ACCESS_KEY_ID'),
            'secret' => env('B2_SECRET_ACCESS_KEY'),
            'region' => env('B2_DEFAULT_REGION'),
            'bucket' => env('B2_BUCKET'),
            'url' => env('B2_URL'),
            'endpoint' => env('B2_ENDPOINT'),
            'use_path_style_endpoint' => env('B2_USE_PATH_STYLE_ENDPOINT', false),
            'throw' => env('B2_THROW_ERROR', true),
            'request_checksum_calculation' => 'when_required',
            'response_checksum_validation' => 'when_required',
        ],
    ],
];

The corresponding environment variables could look like this:

B2_ACCESS_KEY_ID="xxxxxxxxxxxxxxxx"
B2_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxx"
B2_DEFAULT_REGION=us-west-000
B2_BUCKET=your-bucket-slug
B2_ENDPOINT=https://s3.us-west-004.backblazeb2.com
B2_URL=https://your-bucket-slug.s3.us-west-004.backblazeb2.com/