An S3 client can put the bucket name in one of two places. Path-style puts it in the path: https://s3.example.com/my-bucket/photo.jpg. Virtual-hosted style puts it in the hostname: https://my-bucket.s3.example.com/photo.jpg. Amazon prefers the second, and most SDKs pick it by default when they talk to AWS. Almost every S3-compatible endpoint that is not AWS - a self-hosted store, a small provider, a storage server behind one domain - wants the first, because virtual-hosted addressing needs a DNS record and a TLS certificate for every bucket name. If your client is set to the wrong style you get DNS errors, certificate errors or "bucket not found", and the fix is one setting. This post explains both styles, what the region name actually does, and where that one setting lives in each tool you are likely to use.
The two addressing styles#
Every S3 request names three things: the endpoint (which server), the bucket (which container) and the key (which object). The key always goes in the path. The question is only where the bucket goes.
| Style | Request for bucket media, key 2026/cat.jpg | Needs |
|---|---|---|
| Path-style | GET https://s3.example.com/media/2026/cat.jpg | One hostname, one certificate |
| Virtual-hosted | GET https://media.s3.example.com/2026/cat.jpg | A DNS name and a certificate covering every bucket |
On the wire the difference is small. In path-style the Host header is the endpoint and the first path segment is the bucket. In virtual-hosted style the Host header carries the bucket as a subdomain and the path starts with the key. The signature covers both, so a client cannot switch style halfway: it signs the request for the style it is about to send.
# Path-stylePUT /media/2026/cat.jpg HTTP/1.1Host: s3.example.com# Virtual-hosted stylePUT /2026/cat.jpg HTTP/1.1Host: media.s3.example.comA server that only understands path-style receives the virtual-hosted request, sees Host: media.s3.example.com, and either never gets it at all (no DNS record for that name) or treats the first path segment, 2026, as the bucket. That is where the strange "NoSuchBucket: 2026" errors come from.
Why AWS moved to virtual-hosted, and why everyone else did not#
Path-style is the original S3 URL format. Amazon switched its preference to virtual-hosted style because putting the bucket in the hostname lets DNS route each bucket independently: a bucket can live in a different region or on different front-end fleets, and a client resolving media.s3.amazonaws.com lands in the right place without an extra hop. In 2019 AWS announced that path-style requests would be retired for new buckets, then postponed the change indefinitely after pushback. Path-style still works on AWS, but every new AWS feature and every AWS example assumes virtual-hosted style, which is why SDKs default to it.
For an S3-compatible endpoint that is a single server, the trade runs the other way. Virtual-hosted addressing needs:
- A wildcard DNS record,
*.s3.example.com, pointing at the server, so every bucket name resolves. - A wildcard TLS certificate for
*.s3.example.com. Let's Encrypt only issues wildcards through the DNS-01 challenge, which means giving the issuing system access to your DNS zone. - The server configured with its base domain, so it knows to read the bucket from the
Hostheader.
Path-style needs one ordinary A record and one ordinary certificate. Bucket names never touch DNS. That is why the honest default for a self-hosted or single-endpoint store is path-style, and why the documentation of nearly every such product says "set force path style" somewhere near the top.
There is one more practical reason. Bucket names may contain dots (backups.example.org is a legal name). In virtual-hosted style that becomes backups.example.org.s3.example.com, which a wildcard certificate for *.s3.example.com does not cover, because a wildcard matches exactly one label. Path-style has no such problem.
What the region name does#
S3 clients ask for a region even when you are not talking to AWS, and people reasonably wonder what to type.
The region does two jobs on AWS. It selects the endpoint (s3.eu-central-1.amazonaws.com), and it is part of the Signature Version 4 credential scope: every signed request contains a string like 20261008/eu-central-1/s3/aws4_request, and the server recomputes the signature with the region it expects. If the two disagree, AWS answers with AuthorizationHeaderMalformed and tells you the region it wanted.
With a custom endpoint, the first job disappears - you supply the endpoint yourself. The second remains: the region is still baked into the signature. What matters is that the server accepts the region the client signed with. Some S3-compatible servers are configured with one specific region name and reject anything else; others accept any value. When a store accepts any region, the conventional choice is us-east-1, because it is what most tools fall back to when nothing is set, and it avoids a class of SDK quirks where an empty region is treated as an error.
Region also appears in one place where it can bite: some SDKs, given a region but no endpoint, build an AWS URL from it. If you see your client trying to reach s3.us-east-1.amazonaws.com, the endpoint setting was not picked up, not the region.
Bucket names and why the rules exist#
S3 bucket naming rules look arbitrary until you read them as DNS rules, which is what they are. Because a bucket might one day be addressed as a hostname, Amazon's rules require names that are valid DNS labels:
- 3 to 63 characters long.
- Lowercase letters, digits, hyphens and dots only.
- Must start and end with a letter or digit.
- Must not look like an IP address (
192.168.1.10is not a legal bucket name). - No two adjacent dots, and no dot next to a hyphen.
S3-compatible servers usually enforce the same rules even when they only ever use path-style, because clients and tools assume them. Stick to lowercase letters, digits and hyphens - game-backups, media-prod, db-dumps-2026 - and you will never meet a tool that refuses the name. Leave dots out entirely; they add nothing on a path-style endpoint and cause the certificate problem described above if you ever move to a virtual-hosted one.
Bucket names are also visible. They appear in every URL, every presigned link and every log line. Do not put a customer name, a secret or an internal project codename in one.
The setting in every common tool#
This is the part to bookmark. Each line below makes the client use path-style against a custom endpoint.
| Tool or SDK | Path-style setting | Endpoint setting |
|---|---|---|
| AWS CLI | s3 = addressing_style = path in ~/.aws/config | --endpoint-url or endpoint_url |
| boto3 (Python) | Config(s3={'addressing_style': 'path'}) | endpoint_url= |
| AWS SDK for JavaScript v3 | forcePathStyle: true | endpoint: |
| AWS SDK for Go v2 | o.UsePathStyle = true | o.BaseEndpoint |
| AWS SDK for Java v2 | .forcePathStyle(true) | .endpointOverride(URI) |
| AWS SDK for PHP / Laravel | 'use_path_style_endpoint' => true | 'endpoint' => |
| rclone | force_path_style = true (the default) | endpoint = |
| restic | -o s3.bucket-lookup=path | in the repository URL |
| Cyberduck | the "S3 (Deprecated path style requests)" profile | server field |
| WinSCP | Advanced, Environment, S3, URL style: Path | host field |
A few of these deserve a sentence.
The AWS CLI reads the style from its config file. The nested s3 block is the part people miss:
[profile store]region = us-east-1endpoint_url = https://s3.example.coms3 = addressing_style = pathThe top-level endpoint_url in a profile has been supported since AWS CLI 2.13; on older versions you pass --endpoint-url on every command. Using S3 storage with the AWS CLI has the full setup.
In boto3 the style lives in a botocore.config.Config object:
import boto3from botocore.config import Configs3 = boto3.client( "s3", endpoint_url="https://s3.example.com", region_name="us-east-1", aws_access_key_id="AKIA...", aws_secret_access_key="...", config=Config(s3={"addressing_style": "path"}, signature_version="s3v4"),)In the JavaScript SDK v3, forcePathStyle sits on the client constructor:
import { S3Client } from "@aws-sdk/client-s3";const s3 = new S3Client({ endpoint: "https://s3.example.com", region: "us-east-1", forcePathStyle: true, credentials: { accessKeyId: "AKIA...", secretAccessKey: "..." },});Laravel exposes the PHP SDK option through its filesystem config, usually driven by AWS_USE_PATH_STYLE_ENDPOINT=true in .env. rclone already defaults force_path_style to true for S3 remotes, which is one reason it tends to work first time against odd endpoints. restic defaults to auto, which means virtual-hosted for Amazon and Google endpoints and path-style for everything else, so it usually needs no flag either; the option is there when auto-detection guesses wrong. Code examples for both SDKs, including uploads and listing, are in S3 from Node.js and Python.
What the wrong style looks like#
The symptoms are distinctive once you have seen them.
| Symptom | Likely cause |
|---|---|
Could not connect to the endpoint URL: https://media.s3.example.com/ | Virtual-hosted style, no DNS for the bucket subdomain |
getaddrinfo ENOTFOUND media.s3.example.com | Same, from Node |
Certificate error naming media.s3.example.com | Virtual-hosted style; the certificate covers only s3.example.com |
NoSuchBucket naming the first folder of your key | Server read the path as bucket/key; the client sent virtual-hosted |
| Cyberduck: "Cannot read container configuration" | Default S3 profile against a path-style-only server |
SignatureDoesNotMatch after changing only the style | A proxy rewrote Host, or the client cached the old style |
The first two are the most common and the easiest to recognise: look at the hostname in the error. If it starts with your bucket name, the client is using virtual-hosted style and needs the setting from the table above.
The last one is subtler. The signature covers the Host header. If a reverse proxy in front of the store forwards requests with a different Host than the client used, every signature fails even though the credentials are right. A proxy in front of an S3 endpoint should pass the original Host through unchanged.
One error is often blamed on addressing style but has nothing to do with it. Since early 2025, the AWS CLI (from 2.23) and the current AWS SDKs add CRC-based integrity checksums to uploads by default. Some S3-compatible servers do not accept them, and the failure shows up as SignatureDoesNotMatch, MissingContentLength or a checksum complaint on PutObject while listing and downloading work fine. The fix is two settings that make the client send checksums only when an operation requires them: request_checksum_calculation = when_required and response_checksum_validation = when_required in the AWS config profile, or the equivalent options in the SDK. If uploads fail and reads succeed, try that before anything else.
Path-style, HTTPS and your own domain#
A path-style endpoint is just one hostname, so putting HTTPS in front of it is the same job as for any web service: an A record pointing at the proxy, a certificate for that name, and the proxy forwarding to the store's plain-HTTP port. What a reverse proxy does covers the mechanics, and your domain and its certificate covers the DNS side.
This is how RE:NODE's S3 storage is laid out. The endpoint answers plain HTTP on its own port, and each plan carries a proxy slot: point a hostname such as s3.example.com at the address shown, and the certificate is issued and renewed for you. Clients then use https://s3.example.com with path-style addressing and any region name. Plain HTTP on the raw port works for testing, but the request bodies travel unencrypted, so anything leaving your own machine should go through the HTTPS name.
Because the bucket never appears in the hostname, one certificate covers every bucket you will ever create, and nothing needs to change in DNS when you add one.
Presigned URLs and public links#
Addressing style also shapes the URLs you hand to other people. A presigned URL generated by a path-style client looks like https://s3.example.com/media/2026/cat.jpg?X-Amz-Algorithm=...&X-Amz-Signature=..., and the hostname is part of what was signed. Two consequences follow:
- Generate presigned URLs with the same endpoint the browser will use. A URL signed for
http://203.0.113.10:PORTand then rewritten by hand tohttps://s3.example.comfails the signature check. - If you later move from path-style to virtual-hosted (or to a different provider), old presigned URLs stop working. They expire anyway, but long-lived ones in emails will break.
Presigned URLs for uploads and downloads covers expiry, PUT uploads from the browser and CORS.
Choosing a style for your own code#
If you write code that talks to S3, make the endpoint and the style configuration, not constants. A small settings block with S3_ENDPOINT, S3_REGION, S3_BUCKET and S3_FORCE_PATH_STYLE lets the same code run against AWS in one environment and a self-hosted store in another, and makes moving providers a change of four variables. Environment variables and secrets has the pattern.
S3_ENDPOINT=https://s3.example.comS3_REGION=us-east-1S3_BUCKET=mediaS3_FORCE_PATH_STYLE=trueS3_ACCESS_KEY_ID=AKIA...S3_SECRET_ACCESS_KEY=...Before wiring a new endpoint into an application, prove it from the command line. Three commands answer every question about style, region and credentials at once:
$ aws s3 ls --profile store$ aws s3 cp ./test.txt s3://media/test.txt --profile store$ aws s3 ls s3://media/ --profile store --debug 2>&1 | grep -i "host"The first lists buckets, which proves the endpoint and the keys. The second writes an object, which proves the signature on a request with a body. The third prints the request details; the Host line tells you which style the client really used, which is the quickest way to confirm a config change took effect. If all three work in the CLI and your application still fails, the application's SDK is not getting the same settings.
There is rarely a reason to prefer virtual-hosted style on a non-AWS endpoint unless the provider requires it. Path-style is simpler to put behind TLS, survives bucket names with dots, and is what every S3-compatible client supports. On AWS itself, leave the SDK default alone.
FAQ#
Is path-style deprecated?
On AWS it is discouraged and was once scheduled for removal, but the removal was postponed and path-style requests still work. On S3-compatible endpoints it is the normal, supported mode. The word "deprecated" in some tool menus refers to AWS's position, not to the protocol.
What region should I use for an S3-compatible store?
Whatever the provider tells you. If it accepts any name, use us-east-1, which is the value most tools assume and the least likely to trip an SDK. The region only has to be consistent between client and server.
Can I use an IP address as the endpoint?
Yes, with path-style only. A virtual-hosted request would need a hostname like bucket.203.0.113.10, which is not a valid name. For anything beyond local testing, use a hostname with HTTPS so the traffic is encrypted.
Why does the same key work in rclone but not in my app?
rclone defaults to path-style and most SDKs default to virtual-hosted. If the credentials and endpoint are identical, the addressing style is almost always the difference. Set the path-style option in the SDK.
Does addressing style affect performance?
Not on a single-endpoint store. Both styles reach the same server; the only difference is which header carries the bucket name. On AWS the virtual-hosted style can route more directly, which is part of why Amazon prefers it.




Comments
Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.