Skip to content

HeadObject returns ContentLength: null for objects on Cloudflare R2 #2077

Description

@kralmichal

Hit this in production today and tracked it down. Posting in case anyone else runs into it.

Setup: Cloudflare R2 bucket via the S3-compatible endpoint, accessed through async-aws/s3 (3.2.0) + league/flysystem-async-aws-s3 (3.31.0). PutObject works fine. HeadObject returns 200 with the
right ETag and Content-Type, but getContentLength() returns null, which makes flysystem throw UnableToRetrieveMetadata::fileSize.

It's not async-aws's fault — Cloudflare's edge proxy is the problem.

Reproduced by dumping the raw response headers async-aws receives:

HTTP/1.1 200 OK                                           
Date: Mon, 27 Apr 2026 21:19:33 GMT
Content-Type: image/svg+xml
Last-Modified: Mon, 27 Apr 2026 21:10:20 GMT                                                                                                                                                             
ETag: W/"32338883f6b6423933d02f75f6dd2cdc"
Content-Encoding: gzip                                                                                                                                                                                   
Server: cloudflare                                        
CF-RAY: 9f30ddbbea10d3b4-FRA                                                                                                                                                                             

Content-Encoding: gzip is set, no Content-Length. Cloudflare's proxy gzips compressible HEAD responses, and per HTTP semantics Content-Length then refers to the compressed body — but HEAD has no
body, so the header gets dropped entirely. Tools that send Accept-Encoding: identity (curl, mc) don't trigger this; tools that accept gzip (most HTTP clients including symfony/http-client by
default) do.

Workaround that fixes it for me — pass a custom HTTP client that asks for identity encoding:

app.async_aws.http_client:
    class: Symfony\Contracts\HttpClient\HttpClientInterface                                                                                                                                              
    factory: ['Symfony\Component\HttpClient\HttpClient', 'create']
    arguments:                                                                                                                                                                                           
      - headers:                                          
          Accept-Encoding: identity                                                                                                                                                                      
                                                          
AsyncAws\S3\S3Client:
    arguments:
      - endpoint: '%env(AWS_ENDPOINT)%'
        ...                                                                                                                                                                                              
      - null
      - '@app.async_aws.http_client'                                                                                                                                                                     

After this, getContentLength() returns the correct size.

Opening this mainly so it's findable for the next person who hits it. Happy to send a PR if you'd want Accept-Encoding: identity set by default on HEAD requests, but that may have side effects on
AWS proper, so I'll leave the call to the maintainers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions