HeadObject
HeadObject returns an object's headers without its body: the same answer as
GetObject, minus the data. It is the quick way to find out whether an object
exists, how big it is, its ETag, content type and x-amz-meta-* metadata, or whether a
condition holds before a download starts - all without transferring a single byte of the object.
- Every object is stored in
STANDARD, so there is no storage class to keep track of. - The
response-*overrides take Latin-1 text; a value outside it answers500 InternalError, and thefilename*=UTF-8''...form carries any other file name. - Anyone can read the metadata of an object in a bucket whose Bucket Protection is off,
without signing -
response-*overrides need a signed request or a presigned URL. - An unsigned request can be answered from a cache for up to 5 minutes, when the object's own
Cache-Controlallows it. - A key with a
.or..path segment (a/../b) cannot be put in a URL, so it answers400 InvalidURI. - A bucket in another region answers
404, rather than a301redirect. x-amz-expected-bucket-owneris welcome and has nothing to check: a key only ever reaches buckets of its own account.- A query parameter HeadObject does not take answers
501 NotImplemented, so a client knows straight away.
When you'd use it
- A sync or backup tool deciding what to upload. Before sending a file, a nightly backup
compares the stored object's size,
ETagandLast-Modifiedwith the local copy, and uploads only what changed. - Checking that an object is there. A web app that has just received an upload confirms the
object exists before showing its link; a missing one answers
404. - Planning a large download. A download manager reads
Content-Length- and, withpartNumber,x-amz-mp-parts-count- and then fetches the object in ranges or parts with GetObject, several at once. - An agent reading its own notes. An agent keeping files in a bucket stores a summary in
x-amz-meta-*metadata, and reads it back with HeadObject without downloading each file.
Request
HEAD /photos/2024/cat.jpg HTTP/1.1
Host: my-bucket.s3.eu-central-1.strg.com
Authorization: AWS4-HMAC-SHA256 Credential=...
x-amz-date: 20260926T120000Z
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
The path-style form is HEAD /my-bucket/photos/2024/cat.jpg on s3.eu-central-1.strg.com,
which a bucket name containing a dot needs - Addressing explains
both. A presigned HEAD URL works too.
Permissions
The access key needs Read objects (objects:read). For a key limited to certain buckets or
to a key prefix, the bucket and the object key are within them; anything else answers
403 AccessDenied. Access keys has the details.
The Object Lock headers appear in the response when the key also holds Read retention
(object-retention:read), for the retention headers, or Read legal holds
(object-legal-hold:read), for the legal hold header. Without them the response simply leaves
the object's lock out. Object Lock explains both.
Unsigned requests
A request with no signature at all - no Authorization header and no presigned query - reads
the metadata of an object in a bucket whose Bucket Protection is off, just as
GetObject reads the object. A protected bucket answers 403, and a bucket
that does not exist, or is in another region, 404. response-* overrides need a signature, so
an unsigned request carrying one answers 400. Anonymous reads
covers every case.
An unsigned HEAD can be answered from a cache rather than from the stored object, when the
object was stored with a Cache-Control that lets a shared cache keep it (max-age or
s-maxage above zero, and none of no-store, no-cache or private). A cached answer is kept
for the shorter of that lifetime and 5 minutes, and the response still carries the object's
own Cache-Control. The cache is never cleared early: for up to 5 minutes after an object is
overwritten or deleted, Bucket Protection is turned on, or the bucket's
CORS rules change, anonymous readers can still get the previous answer. An
object stored without Cache-Control is never cached, and a request carrying If-Match,
If-Unmodified-Since or any x-amz-* header, or a query parameter other than versionId,
always reads the stored object.
A bucket created a moment ago can answer unsigned requests with 404 for a few seconds, until
every endpoint has heard about it.
URI parameters
| Name | Type | Required | Description |
|---|---|---|---|
Bucket | String | Yes | The bucket that holds the object. |
Key | String | Yes | The object's key. A key with a . or .. path segment (a/../b) cannot be put in a URL and answers 400 InvalidURI. |
versionId | String | No | A specific version of the object. Without it, the current version is used. |
partNumber | Integer | No | 1-10,000. Describes that part of an object uploaded by multipart upload: Content-Length is the part's size. |
response-cache-control | String | No | Sets Cache-Control on the response. |
response-content-disposition | String | No | Sets Content-Disposition on the response. Latin-1 characters only - a value outside it answers 500 InternalError - and the filename*=UTF-8''... form carries a non-ASCII file name. |
response-content-encoding | String | No | Sets Content-Encoding on the response. |
response-content-language | String | No | Sets Content-Language on the response. |
response-content-type | String | No | Sets Content-Type on the response. |
response-expires | String | No | Sets Expires on the response. |
The response-* overrides apply to a 200 or 206 answer only, never to a 304 or an error,
and need a signed request or a presigned URL: an unsigned request carrying one answers 400.
Any other query parameter, apart from presigning's own X-Amz-*, answers 501 NotImplemented.
Request headers
| Name | Type | Required | Description |
|---|---|---|---|
If-Match | String | No | Answers only if the object's ETag matches; otherwise 412. |
If-None-Match | String | No | Answers only if the object's ETag differs; otherwise 304. |
If-Modified-Since | Timestamp | No | Answers only if the object changed after this time; otherwise 304. |
If-Unmodified-Since | Timestamp | No | Answers only if the object has not changed since this time; otherwise 412. |
Range | String | No | A byte range, such as bytes=0-1023. Only the headers change: Content-Length and Content-Range describe the range. A range that cannot be satisfied answers 416. |
x-amz-server-side-encryption-customer-algorithm | String | For SSE-C objects | AES256. Required for an object stored with a customer-provided key (SSE-C). |
x-amz-server-side-encryption-customer-key | String | For SSE-C objects | The base64-encoded 256-bit key the object was stored with. |
x-amz-server-side-encryption-customer-key-MD5 | String | For SSE-C objects | The base64-encoded MD5 digest of that key. |
x-amz-checksum-mode | String | No | ENABLED asks for the object's stored checksum. |
x-amz-request-payer | String | No | Has no effect: STRG has no Requester Pays buckets, so there is never a need to send it. |
x-amz-expected-bucket-owner | String | No | Welcome, with nothing to check: an access key only ever reaches its own account's buckets. |
A signed request signs every x-amz-* header it carries; one left unsigned answers 403.
Authentication has the details.
Request body
None.
Response
A HEAD response never has a body, successful or not.
Response headers
| Name | Description |
|---|---|
Content-Length | The object's size in bytes - or the part's with partNumber, or the range's with Range. |
Content-Type, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Expires | As stored with the object, or as set by a response-* override. |
Content-Range | With Range, the range the answer describes. |
Accept-Ranges | bytes. |
ETag | The object's entity tag. |
Last-Modified | When the object was last written. |
x-amz-meta-* | The object's user metadata, one header per entry. |
x-amz-version-id | The version described. |
x-amz-delete-marker | true when the version addressed is a delete marker. |
x-amz-server-side-encryption | AES256 when the object is encrypted with STRG-managed keys. |
x-amz-server-side-encryption-customer-algorithm, x-amz-server-side-encryption-customer-key-MD5 | For an SSE-C object, echoing the key you sent. |
x-amz-checksum-crc32, x-amz-checksum-crc32c, x-amz-checksum-crc64nvme, x-amz-checksum-sha1, x-amz-checksum-sha256, x-amz-checksum-type | The checksum stored with the object, if it was uploaded with one. Never on a 304. |
x-amz-checksum-sha512, x-amz-checksum-md5, x-amz-checksum-xxhash64, x-amz-checksum-xxhash3, x-amz-checksum-xxhash128 | Not returned: uploads use the checksum algorithms in the row above. |
x-amz-mp-parts-count | With partNumber, the number of parts in a multipart object. |
x-amz-storage-class | Absent or STANDARD: every object is STANDARD. |
x-amz-object-lock-mode, x-amz-object-lock-retain-until-date | The object's retention, when it has one and the key holds Read retention. |
x-amz-object-lock-legal-hold | ON or OFF, when the key holds Read legal holds. |
x-amz-object-lock-event-hold, x-amz-object-lock-event-hold-duration-days, x-amz-object-lock-event-hold-duration-years | Not returned: STRG has no event holds. |
x-amz-tagging-count | Not returned: objects carry no tags. |
x-amz-server-side-encryption-aws-kms-key-id, x-amz-server-side-encryption-bucket-key-enabled | Not returned: STRG encrypts with its own managed keys (AES256) or yours (SSE-C), never SSE-KMS. |
x-amz-restore, x-amz-archive-status | Not returned: there are no archive storage classes, so every object is ready to read. |
x-amz-replication-status | Not returned: STRG has no bucket replication. |
x-amz-website-redirect-location | Not returned: STRG has no website hosting. |
x-amz-expiration | Not guaranteed when a lifecycle policy applies, so a client is better off not depending on it. |
x-amz-missing-meta | Not returned. |
x-amz-request-charged | Not returned: STRG has no Requester Pays buckets. |
x-amz-request-id | The request ID, handy for a support request. |
Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Expose-Headers, Vary | For a browser request whose Origin the bucket's CORS rules allow, signed or unsigned. CORS rules has the details. |
Response body
None.
Errors
A HEAD error has no body, so only the HTTP status reaches your client: the AWS SDK for
JavaScript reports a 404 as NotFound, and boto3 raises a ClientError whose code is the
status ("404"). The table names the S3 error behind each status, and the situation it comes
from.
| Code | HTTP status | When |
|---|---|---|
| - | 304 | If-None-Match matched, or the object is unchanged since If-Modified-Since. Not an error: the object simply has not changed. |
PreconditionFailed | 412 | If-Match did not match, or the object changed since If-Unmodified-Since. |
InvalidRange | 416 | The Range cannot be satisfied. |
NoSuchKey | 404 | The object or version does not exist, or the current version is a delete marker. |
NoSuchBucket | 404 | The bucket does not exist, it is in a region other than the endpoint's, or the account has no storage in this endpoint's region yet. |
AccessDenied | 403 | The access key lacks Read objects, the bucket or key is outside its buckets or prefix, the account is not active, or an x-amz-* header of a signed request is unsigned. For an unsigned request: the bucket's Bucket Protection is on, or the bucket's owner has an invoice at the stage where every operation pauses. |
InvalidAccessKeyId | 403 | The access key does not exist, has expired or been revoked - or it was created moments ago and is not usable yet in a region where the account already stores data, in which case the same request works shortly afterwards. |
SignatureDoesNotMatch | 403 | The request signature does not match. |
RequestTimeTooSkewed | 403 | The request time is more than 15 minutes from the server's. |
AuthorizationHeaderMalformed | 400 | The signature's credential scope names a region other than the endpoint's. |
InvalidRequest | 400 | An unsigned request carries a response-* override. |
InvalidURI | 400 | The key contains a . or .. path segment, signed or unsigned. |
| - | 400 | An unsigned request names a bucket deleted moments ago, or the request is otherwise malformed. |
InternalError | 500 | A response-* override carries a character outside Latin-1. |
NotImplemented | 501 | The request carries a query parameter HeadObject does not take. |
SlowDown | 503 | Too many requests. The Retry-After header says when a retry is welcome; the AWS SDKs retry on their own. |
ServiceUnavailable | 503 | A temporary failure; the same request can be retried. |
Differences from Amazon S3
A few things work a little differently on STRG, each listed here with what it means for your requests.
| Aspect | Amazon S3 | STRG |
|---|---|---|
| Storage class | Several; the header names any but STANDARD | STANDARD for every object |
| Bucket in another region | 301 redirect with x-amz-bucket-region | 404. A bucket is reached through its own regional endpoint |
| Anonymous requests | Allowed on a public object | Allowed in a bucket whose Bucket Protection is off; 403 otherwise |
| Anonymous answers | Always the stored object | Can come from a cache for the shorter of the object's Cache-Control lifetime and 5 minutes, so for up to 5 minutes an overwritten or deleted object can be described as it was, and a bucket just protected can still answer |
Keys with . or .. segments | Allowed | 400 InvalidURI. DeleteObjects deletes such a key |
response-* overrides | Any value | Latin-1, otherwise 500. filename*=UTF-8''... carries a non-ASCII file name |
| Unknown query parameters | Ignored | 501 NotImplemented, so a client knows straight away |
| Tags, SSE-KMS, event holds, archive and replication headers | Returned where they apply | Never returned, since STRG has none of these features to report on |
x-amz-expected-bucket-owner | Checked against the bucket owner | Welcome, with nothing to check |
Examples
Reading the metadata of photos/2024/cat.jpg in my-bucket, and telling a missing object apart
from other failures:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api head-object \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
A missing object exits with an error naming (404) and Not Found.
import boto3
from botocore.exceptions import ClientError
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
try:
head = s3.head_object(Bucket="my-bucket", Key="photos/2024/cat.jpg")
print(head["ContentLength"], head["ContentType"], head["ETag"], head["Metadata"])
except ClientError as error:
if error.response["Error"]["Code"] == "404":
print("no such object")
else:
raise
import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
try {
const head = await client.send(
new HeadObjectCommand({ Bucket: "my-bucket", Key: "photos/2024/cat.jpg" }),
);
console.log(head.ContentLength, head.ContentType, head.ETag, head.Metadata);
} catch (error) {
if (error.name === "NotFound") console.log("no such object");
else throw error;
}
A specific version takes --version-id (CLI), VersionId= (boto3) or VersionId
(JavaScript), with the version's ID.
Checking by its ETag whether an object changed since it was last seen. An unchanged object
answers 304, which the SDKs raise as an error:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api head-object \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--if-none-match '"85f30635602dc09bd85957a6e82a2c21"' \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import boto3
from botocore.exceptions import ClientError
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
try:
head = s3.head_object(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
IfNoneMatch='"85f30635602dc09bd85957a6e82a2c21"',
)
print("changed, new ETag:", head["ETag"])
except ClientError as error:
if error.response["Error"]["Code"] == "304":
print("unchanged")
else:
raise
import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
try {
const head = await client.send(
new HeadObjectCommand({
Bucket: "my-bucket",
Key: "photos/2024/cat.jpg",
IfNoneMatch: '"85f30635602dc09bd85957a6e82a2c21"',
}),
);
console.log("changed, new ETag:", head.ETag);
} catch (error) {
if (error.$metadata?.httpStatusCode === 304) console.log("unchanged");
else throw error;
}
Reading the metadata of an object in a bucket whose Bucket Protection is off, without credentials:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api head-object \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--no-sign-request \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import boto3
from botocore import UNSIGNED
from botocore.config import Config
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
config=Config(signature_version=UNSIGNED),
)
head = s3.head_object(Bucket="my-bucket", Key="photos/2024/cat.jpg")
print(head["ContentLength"], head["ContentType"], head["ETag"])
import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
credentials: { accessKeyId: "", secretAccessKey: "" },
signer: { sign: async (request) => request },
});
const head = await client.send(
new HeadObjectCommand({ Bucket: "my-bucket", Key: "photos/2024/cat.jpg" }),
);
console.log(head.ContentLength, head.ContentType, head.ETag);