Skip to Content
Framework GuidesS3 Checksums

S3 Checksums (AWS SDK v3 + Garage)

If your uploads fail with 400 InvalidDigest, your S3 client is sending the AWS SDK’s new default checksums, which Ghayma’s storage (Garage) rejects. Add requestChecksumCalculation: "WHEN_REQUIRED" and responseChecksumValidation: "WHEN_REQUIRED" to your S3Client config.

Symptom

Uploads — especially presigned PUT uploads from a browser — fail with:

400 InvalidDigest Failed to validate checksum for algorithm Crc32: calculated ..., expected Crc32([0, 0, 0, 0])

A plain PutObject from your server may fail the same way.

Cause

Starting with @aws-sdk/client-s3 v3.729.0 (January 2025), the SDK enables an extra integrity checksum on every upload by default (requestChecksumCalculation: "WHEN_SUPPORTED"). It adds an x-amz-checksum-crc32 value to the request.

For presigned URLs the checksum is computed at the time the URL is signed — when there is no body yet — so it is the CRC32 of an empty payload ([0, 0, 0, 0]). The browser then uploads the real file against that URL, and Garage validates the embedded checksum against the actual bytes. They don’t match, so the request is rejected with InvalidDigest.

AWS S3 itself ignores these parameters in this position, which is why the defaults work against AWS but break against S3-compatible stores. This is a known incompatibility that also affected Cloudflare R2, MinIO, Backblaze B2 and DigitalOcean Spaces when the default changed; the universal client-side fix is to only send a checksum when the operation actually requires one.

Fix

Set both checksum options to WHEN_REQUIRED when you construct the client. This applies to every operation, including presigned URLs:

import { S3Client } from "@aws-sdk/client-s3"; const s3 = new S3Client({ endpoint: process.env.STORAGE_ENDPOINT, region: process.env.STORAGE_REGION, credentials: { accessKeyId: process.env.STORAGE_ACCESS_KEY!, secretAccessKey: process.env.STORAGE_SECRET_KEY!, }, forcePathStyle: true, // required for Garage // Garage rejects the SDK's default CRC32 checksums (400 InvalidDigest), // especially on presigned PUTs — only send a checksum when required. requestChecksumCalculation: "WHEN_REQUIRED", responseChecksumValidation: "WHEN_REQUIRED", });

That’s the only change. Presigned-URL generation needs no extra flags — the options are read from the client the presigner is built from:

import { PutObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; // `s3` is the WHEN_REQUIRED client above — the presigned URL will NOT // carry the x-amz-checksum-* query parameters that break Garage. const uploadUrl = await getSignedUrl( s3, new PutObjectCommand({ Bucket, Key, ContentType }), { expiresIn: 600 }, );

This config is production-verified: presigned PUT uploads round-trip byte-for-byte once both options are set to WHEN_REQUIRED.

Other languages

The same default change shipped across AWS SDKs in early 2025. The fix is the same idea everywhere.

Environment variables (any AWS SDK / CLI):

AWS_REQUEST_CHECKSUM_CALCULATION=when_required AWS_RESPONSE_CHECKSUM_VALIDATION=when_required

Python (boto3):

from boto3.session import Config import boto3 s3 = boto3.client( "s3", endpoint_url=os.environ["STORAGE_ENDPOINT"], config=Config( s3={"addressing_style": "path"}, request_checksum_calculation="when_required", response_checksum_validation="when_required", ), )

Go (aws-sdk-go-v2):

cfg.RequestChecksumCalculation = aws.RequestChecksumCalculationWhenRequired cfg.ResponseChecksumValidation = aws.ResponseChecksumValidationWhenRequired

Troubleshooting

  • Still seeing InvalidDigest? Confirm the options are on the same S3Client instance you pass to getSignedUrl — a presigned URL inherits the checksum behaviour of the client it was signed with.
  • Pinning instead of configuring also works as a stopgap: SDK versions before 3.729.0 don’t send the default checksum. Configuring WHEN_REQUIRED is preferred so you can stay current.
  • Multipart uploads from the browser have additional checksum caveats in the AWS SDK; for large files, prefer presigned single-part PUT or upload server-side.