Service
S3
AWS API Action
Any S3 API called via virtual-hosted-style URL (e.g. GetObject, PutObject, presigned URL
access) over HTTP/2.
Expected behavior
A virtual-hosted-style request such as https://{bucket}.s3.{region}.localhost.floci.io:4566/{key}
should be routed to the correct bucket/key regardless of whether the client negotiates HTTP/1.1
or HTTP/2, since both are valid ways for a browser to reach an HTTPS endpoint.
Actual behavior
When the client uses HTTP/2 (which browsers select automatically via ALPN over HTTPS),
virtual-hosted-style requests are misinterpreted as path-style requests: the bucket name segment
in the Host is silently ignored, and the first path segment is treated as the bucket name
instead. The same request over HTTP/1.1 (same TLS, same URL) works correctly.
This was initially mistaken for an HTTP vs HTTPS difference ("works over HTTP, fails over
HTTPS"), but forcing HTTP/1.1 over HTTPS with curl --http1.1 proved it succeeds — the actual
variable is the HTTP protocol version, not TLS.
Root cause (confirmed by reading the source): S3VirtualHostFilter determines whether to
rewrite a virtual-hosted-style request by calling requestContext.getHeaderString("Host"). If
this returns null, the filter does nothing and the request falls through to path-style
handling. HTTP/2 (RFC 9113) has no Host header — it uses the :authority pseudo-header
instead. Since S3VirtualHostFilter never reads :authority, it never fires for HTTP/2
requests, and the request is silently handled as path-style with the wrong bucket resolved from
the URL path.
Practical impact: any browser accessing a presigned URL or making a direct virtual-hosted-style
S3 request over HTTPS will use HTTP/2 by default and hit this bug. Only clients that explicitly
downgrade to HTTP/1.1 (e.g. most AWS SDKs, or curl --http1.1) are unaffected — this is why
server-to-server calls (e.g. via AWS SDK for Java) worked fine while browser-based access failed
consistently.
This is unrelated to #1625 (which only affects bucket-less s3. hosts) — this bug affects
virtual-hosted-style hosts that do include a bucket name, and has been present since
virtual-hosted-style support was added (reproduced on v1.5.15 through v1.5.31).
Reproduction
# Same URL, same TLS/cert, only the negotiated HTTP version differs.
# Bucket "my-bucket" exists and contains key "nested/object.png".
# HTTP/1.1 (forced): correctly resolves bucket "my-bucket", key "nested/object.png" -> 200
curl -i --http1.1 --resolve my-bucket.s3.localhost.floci.io:4566:127.0.0.1 \
"https://my-bucket.s3.localhost.floci.io:4566/nested/object.png"
# HTTP/2 (curl default over HTTPS via ALPN): bucket name from Host is ignored;
# "nested" is misinterpreted as the bucket name and "object.png" as the key -> 404 NoSuchBucket
curl -i --resolve my-bucket.s3.localhost.floci.io:4566:127.0.0.1 \
"https://my-bucket.s3.localhost.floci.io:4566/nested/object.png"
Environment
- Floci version / image tag: v1.5.31 (
floci/floci:latest as of 2026-07-07); also reproduced on
v1.5.15, v1.5.27, v1.5.28
- Java SDK version (if applicable): N/A for the failing case (browser
fetch/<img> requests
via presigned URL). AWS SDK for Java is unaffected because it uses HTTP/1.1.
- How you're running Floci (Docker / native /
mvn quarkus:dev): Docker (docker compose)
Service
S3
AWS API Action
Any S3 API called via virtual-hosted-style URL (e.g.
GetObject,PutObject, presigned URLaccess) over HTTP/2.
Expected behavior
A virtual-hosted-style request such as
https://{bucket}.s3.{region}.localhost.floci.io:4566/{key}should be routed to the correct bucket/key regardless of whether the client negotiates HTTP/1.1
or HTTP/2, since both are valid ways for a browser to reach an HTTPS endpoint.
Actual behavior
When the client uses HTTP/2 (which browsers select automatically via ALPN over HTTPS),
virtual-hosted-style requests are misinterpreted as path-style requests: the bucket name segment
in the Host is silently ignored, and the first path segment is treated as the bucket name
instead. The same request over HTTP/1.1 (same TLS, same URL) works correctly.
This was initially mistaken for an HTTP vs HTTPS difference ("works over HTTP, fails over
HTTPS"), but forcing HTTP/1.1 over HTTPS with
curl --http1.1proved it succeeds — the actualvariable is the HTTP protocol version, not TLS.
Root cause (confirmed by reading the source):
S3VirtualHostFilterdetermines whether torewrite a virtual-hosted-style request by calling
requestContext.getHeaderString("Host"). Ifthis returns
null, the filter does nothing and the request falls through to path-stylehandling. HTTP/2 (RFC 9113) has no
Hostheader — it uses the:authoritypseudo-headerinstead. Since
S3VirtualHostFilternever reads:authority, it never fires for HTTP/2requests, and the request is silently handled as path-style with the wrong bucket resolved from
the URL path.
Practical impact: any browser accessing a presigned URL or making a direct virtual-hosted-style
S3 request over HTTPS will use HTTP/2 by default and hit this bug. Only clients that explicitly
downgrade to HTTP/1.1 (e.g. most AWS SDKs, or
curl --http1.1) are unaffected — this is whyserver-to-server calls (e.g. via AWS SDK for Java) worked fine while browser-based access failed
consistently.
This is unrelated to #1625 (which only affects bucket-less
s3.hosts) — this bug affectsvirtual-hosted-style hosts that do include a bucket name, and has been present since
virtual-hosted-style support was added (reproduced on v1.5.15 through v1.5.31).
Reproduction
Environment
floci/floci:latestas of 2026-07-07); also reproduced onv1.5.15, v1.5.27, v1.5.28
fetch/<img>requestsvia presigned URL). AWS SDK for Java is unaffected because it uses HTTP/1.1.
mvn quarkus:dev): Docker (docker compose)