S3 Bucket URL Formats and Path vs Virtual-Hosted Style

This is where the tradeoffs get concrete, and honestly, where most of the real debugging time gets lost. Think of virtual-hosted style and path-style as two different keys to the same lock — they both open the door, but they're cut differently, and using the wrong one doesn't just fail to turn; it makes you wonder if the lock is broken.
Virtual-hosted style depends on DNS resolving the bucket name. When the bucket name lives in the hostname, DNS has to resolve it. On AWS, this is handled for you. On a self-hosted system, you have to set up wildcard DNS yourself (*.s3.yourdomain.com or equivalent). And there's a hard wall here: you cannot use a raw IP address with virtual-hosted style. There's simply nowhere to put the bucket name if the hostname is a numeric address.
TLS gets weird with dotted bucket names. AWS's wildcard certificate covers *.s3.amazonaws.com, which works fine for my-bucket. But a bucket named my.bucket.name breaks wildcard certificate matching. A wildcard only covers one subdomain level. Your options at that point are HTTP (bad) or custom certificate verification logic (also not fun). The practical consequence is that your bucket naming conventions now have SSL implications. That was not in the original S3 marketing brochure.
Path-style sidesteps all of this. The hostname is just the endpoint. The server reads the bucket name from the URL path. That means:
- Works with raw IP addresses
- No wildcard DNS required
- No TLS constraint tied to bucket naming
Path-style is the only viable option for on-premises appliances, air-gapped networks, and local dev environments where you're not running a full DNS stack.
The 403 trap. This one has burned a lot of people. Send a path-style request to a newer AWS bucket and you don't get a helpful error explaining the format mismatch. You get 403 Forbidden. That looks exactly like a permissions problem. So you spend an hour auditing IAM policies that are actually fine. If IAM looks correct and the 403 persists, check the URL format before you do anything else. It's the less obvious culprit and frequently the right answer. As one engineer put it after his third time falling into this exact hole: "IAM didn't fail me. I failed IAM."
How AWS's Deprecation of Path-Style Actually Unfolded, and Where It Stands Now
In May 2019, Jeff Barr posted on the AWS blog that path-style access was deprecated for buckets created after September 30, 2020. The stated reasoning was scaling: routing everything through shared path-style endpoints gets harder as you're managing traffic across dozens of regions and an enormous bucket count. Virtual-hosted subdomains also unlock per-bucket security features, custom cipher configuration being one example AWS cited. That kind of per-bucket control is structurally impossible with a shared path-style endpoint.
Then the deadline came and went. AWS extended the timeline. Then extended it again. As of 2025, both formats still work on AWS. Path-style for buckets created on or before September 30, 2020 is explicitly preserved. For newer buckets, enforcement has been deferred indefinitely.
The direction hasn't changed. The timeline just keeps moving. Teams using path-style on buckets created after 2020 are relying on AWS's continued tolerance, not on anything documented or guaranteed. It's a bit like jaywalking on a street where the light has been broken for years — technically against the rules, and nothing bad has happened yet, but you're not the one controlling the traffic.
Where this actually bites people is at the SDK level. Recent AWS SDK versions shifted their default endpoint resolution to virtual-hosted style. Teams upgrading SDK versions found their S3AsyncClient configurations breaking with no clear error pointing at URL format as the cause. The forcePathStyle setting is still in the SDK and isn't going anywhere. But if AWS eventually enforces the deprecation at the service layer, the failure will show up as a service-side exception, not a client config warning. That's a harder failure to diagnose, and it surfaces at the worst possible moment.
Where Path-Style Is Still the Right Choice in 2025
Despite all of the above, there are real reasons to use path-style. They're just specific.
S3-compatible object stores. MinIO in local development is the obvious one. Unless you've explicitly configured wildcard DNS for virtual-hosted subdomains, MinIO defaults to path-style. Setting up wildcard DNS locally is operationally annoying, so path-style is the practical choice. Other S3-compatible services like Ceph, Wasabi, Backblaze B2, and DigitalOcean Spaces each implement the S3 API but have their own defaults and quirks. Client configuration has to match what the server actually expects. Check the docs for each rather than assuming AWS behavior transfers over.
NetApp StorageGRID supports path-style and documents it, though it's on its own deprecation trajectory within that product.
IP-addressed and air-gapped environments. Any deployment where the server is reached by IP address requires path-style. Full stop. This comes up constantly with on-premises object storage appliances and private cloud environments. It is not a legacy concern. It's an active requirement for a lot of infrastructure teams right now.
Legacy enterprise applications. Applications built before 2019 that hardcode endpoints or depend on old SDK versions that defaulted to path-style. Nobody loves talking about how often refactoring these gets deferred. The real risk is when these applications are pointed at buckets created after September 30, 2020. At that point, they're already depending on AWS's continued goodwill, not on anything guaranteed.
How to Configure Clients Correctly for Each Format
AWS SDKs. Virtual-hosted style is the default. For standard AWS usage you don't configure anything. forcePathStyle: true (or the equivalent in your SDK) is the toggle for path-style. Use it for self-hosted S3-compatible targets. Skip it for AWS buckets created after 2020. When upgrading SDK versions, explicitly check whether endpoint resolution behavior changed. It's easy to miss in a changelog, and it's one of the more reliable ways to introduce a silent breakage.
AWS CLI. The CLI honors the SDK default. For non-AWS endpoints like MinIO or local emulators, --endpoint-url with a path-style host is your escape hatch.
Self-hosted and S3-compatible services. For MinIO in local dev, set pathStyle: true in your client config. This sidesteps the wildcard DNS requirement entirely. If the storage server is only reachable by IP, path-style is your only option. Document this at the infrastructure level. If you don't, someone will spend a full afternoon troubleshooting it as an application bug when it's actually a config requirement baked into the environment.
Presigned URLs. The format of a presigned URL depends on how the generating client was configured. A client using virtual-hosted style produces virtual-hosted presigned URLs. Whoever receives that URL needs to resolve the bucket subdomain. For S3-compatible systems where virtual-hosted DNS isn't configured, generate presigned URLs with path-style config. Otherwise the URL simply won't work for the recipient.
Parsing S3 URLs programmatically. A robust parser has to handle both formats. For virtual-hosted, check whether the hostname starts with a known bucket name prefix. For path-style, extract the bucket from the first path segment. You'll also need to handle s3:// URIs, the older dash-separated regional variants (s3-us-west-2.amazonaws.com versus s3.us-west-2.amazonaws.com), and access-point ARN formats. A parser that only handles one format will fail in ways that are genuinely confusing to trace.
Choosing the Right Format for Cloud-Agnostic and Multi-Storage Setups
The S3 API is the interoperability standard. Wasabi, Google Cloud Storage in interop mode, Backblaze B2, MinIO. They all implement the S3 API. The S3 API won because it became the common interface across providers. That's the baseline assumption for any storage-agnostic architecture.
URL format is a portability variable, not a constant. A client configured for virtual-hosted style on AWS will not work against a self-hosted MinIO instance that hasn't set up wildcard DNS. That's a reconfiguration problem, not an application bug. In storage-agnostic architectures, the URL format setting belongs at the infrastructure configuration layer. Externalize the endpoint URL and the path-vs-virtual-hosted toggle as environment-level config. Swapping storage backends becomes dramatically less painful when the format isn't hardcoded in application logic.
Practical rules for teams running multiple backends:
- Default to virtual-hosted style for AWS
- Default to path-style for self-hosted or IP-addressed endpoints
- When the same client code targets both, treat the style setting as environment config, not a code constant
- Document which format each storage target expects
That last point sounds obvious. It almost never gets done. The URL format assumption is one of the most reliably undocumented gotchas when a new developer sets up a local environment for the first time. Write it down somewhere accessible. The hour you spend documenting it will be repaid the first time someone doesn't have to reverse-engineer it from scratch. After all, an undocumented config assumption is just a future incident report waiting to be written.


