Store Django Media Files on Backblaze B2

Store Django uploads on Backblaze B2 through its S3-compatible API: create a bucket and application key, configure django-storages with the B2 endpoint, serve private files with presigned URLs, and put public files behind Cloudflare.

Problem statement

Backblaze B2 is low-cost object storage with an S3-compatible API. For Django apps with lots of user uploads (photos, documents, audio), it can cut storage costs substantially compared with the big clouds, and its egress partnership with Cloudflare can make public file delivery inexpensive. Using it from Django takes the S3 backend in django-storages, the right endpoint, an application key scoped to one bucket, and a few B2-specific details.

Media on Backblaze B2Django uploadFileField saveS3 backendB2 S3 endpointB2 bucketprivate by defaultPresigned or CDNprivate or publicB2's S3-compatible API lets django-storages work with an endpoint and a scoped key.
  1. Create a private bucket in B2 and note its S3 endpoint (for example s3.eu-central-003.backblazeb2.com).
  2. Create an application key restricted to that bucket with read and write capabilities.
  3. Configure S3Storage with endpoint_url, bucket_name, region_name (the region part of the endpoint), and the key ID and key.
  4. Keep files private and use presigned URLs, or make a separate public bucket and serve it through Cloudflare.
  5. Set lifecycle rules: B2 keeps old file versions by default unless you configure otherwise.
  6. Use unique upload paths.

Step-by-step solution

1. Bucket and application key

In the Backblaze console, create a bucket (Files in Bucket: Private). The bucket details show the S3 endpoint. Under App Keys, create a key with access to only that bucket. The keyID is the access key and the applicationKey is the secret; the secret is shown once, so store it immediately in your secrets manager.

2. Configure Django

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "myapp-media",
            "endpoint_url": "https://s3.eu-central-003.backblazeb2.com",
            "region_name": "eu-central-003",
            "access_key": env("B2_KEY_ID"),
            "secret_key": env("B2_APP_KEY"),
            "default_acl": None,           # B2 uses bucket-level visibility
            "querystring_auth": True,
            "querystring_expire": 900,
            "file_overwrite": False,
            "signature_version": "s3v4",
        },
    },
    "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}

B2 does not support per-object ACLs the way S3 does; visibility is set per bucket. Leave default_acl unset so uploads do not fail on unsupported ACL headers.

what differs from S3Amazon S3Backblaze B2Object ACLsper object!per bucket onlyVersioningoff by default!keeps versions by defaultPublic deliveryS3 or CloudFrontbucket URL or CloudflareEndpoints3.<region>.amazonaws.coms3.<region>.backblazeb2.comLifecycle rules matter on B2 because hidden versions still cost storagedjango-deployment.com
Set default_acl to None to avoid errors from unsupported ACL headers.

3. Public files through Cloudflare

For public assets, create a second bucket set to Public and serve it through a Cloudflare-proxied CNAME pointing at the bucket's friendly hostname, with a Cloudflare rule rewriting the path to /file/<bucket>/.... Data transfer from B2 to Cloudflare is free under their Bandwidth Alliance, which makes this a cheap way to serve large public media. Configure custom_domain on a public storage alias accordingly.

4. Lifecycle rules

By default, B2 keeps every version of a file: deleting or overwriting creates a hidden version that still counts toward storage. Set the bucket lifecycle to "Keep only the last version" for media, or keep versions for a fixed number of days if you want recovery from accidental deletes.

5. Backups and redundancy

B2 stores data in one region. For important media, replicate to another region with B2 Cloud Replication or copy to a different provider with rclone.

B2 setup for DjangoPrivate bucket, bucket-scoped app keysecret stored onceS3 endpoint and region setfrom bucket detailsdefault_acl left unsetB2 has bucket visibilityLifecycle rule for old versionshidden versions cost moneyPublic bucket behind Cloudflarefree egress to CloudflareMost B2 surprises are ACL errors and storage from kept versionsdjango-deployment.com
Check your bucket's storage graph after a month to confirm versions are being cleaned up.

Verification check: upload a file through the admin and see it in the B2 browser; open the presigned URL from the page and confirm it expires; delete the file in Django and confirm the lifecycle rule removes the hidden version on schedule.

Explanation

Backblaze offers a native B2 API and an S3-compatible API. The S3-compatible endpoint supports SigV4 authentication, presigned URLs, and multipart uploads, so boto3 and django-storages work unchanged apart from the endpoint and a few unsupported features such as object ACLs. B2's versioning-by-default and bucket-level visibility reflect its backup-storage origins, which is why lifecycle rules and bucket separation matter more than on S3.

Edge cases / notes

  • Application key capabilities must include listBuckets for some client operations; if uploads fail with access errors, check the key's capabilities.
  • Large files use multipart uploads automatically through boto3.
  • Download fees apply above a free allowance when not going through partners such as Cloudflare.
  • CORS for direct browser uploads is configured per bucket with the B2 CLI or API.

Next steps

FAQ

Does django-storages work with Backblaze B2?

Yes, through the S3 backend and B2's S3-compatible endpoint.

Why do uploads fail with an ACL error?

B2 does not support per-object ACLs. Leave default_acl unset.

Why is my B2 storage larger than expected?

B2 keeps old versions by default. Add a lifecycle rule to keep only the latest version.

How do I serve public B2 files cheaply?

Through Cloudflare, since transfer from B2 to Cloudflare is free under their partnership.

Keep going