S3 "This XML File Does Not Appear to Have Any Style Information Associated With It": What the Browser Is Really Saying, Every Error Code Decoded (AccessDenied, NoSuchKey, NoSuchBucket), the Two S3 Addresses, CloudFront and Presigned Links
"This XML file does not appear to have any style information associated with it. The document tree is shown below." is not an AWS error. It is your browser saying it received a plain XML document with no instructions for displaying it, so it is showing you the raw tree. The actual error is the short tag underneath, <Code>, and nine times out of ten it reads AccessDenied, NoSuchBucket, NoSuchKey or PermanentRedirect. Amazon S3 answers every failed request on its normal endpoint with that XML, whether the request came from a program or from a person clicking a link, which is why a file you just uploaded, a website you just built, or a CloudFront URL you just copied can greet a visitor with what looks like a crash. Here is the surprise that explains most cases: the link you shared points at S3's API, not at a website. S3 has two kinds of address for the same bucket, and only one of them is meant for browsers. This page decodes every code the XML can carry, gives the fix for each, and shows how to tell the two addresses apart so the XML never shows up again.
Jake runs a phone repair shop. He uploaded his price list as a PDF to an S3 bucket, copied the link from the console's Object URL field, and pinned it to the shop's social page. The first customer to click it saw a gray page of angle brackets and the words "Access Denied." The second customer messaged him to ask whether the shop had been hacked. His friend Ethan, a developer, read the XML in about four seconds, laughed, and said "that's just S3 talking." This page is what Ethan explained: what the browser message means, how to read the XML, what each code means and how to fix it, the two S3 addresses and which one to share, why CloudFront shows the same page, why a shared link stops working after a week, and the three honest ways to put a file on the internet from S3.
New to S3? Our plain-English guide to Amazon S3 explains buckets, objects and permissions in about ten minutes. You can follow this page without it; every step starts from zero.
What "this XML file does not appear to have any style information" actually means
Web pages are HTML, and browsers know how to draw HTML. XML is a cousin: the same angle-bracket shape, but with no built-in rules for how it should look. When a browser is handed an XML document that does not point at a stylesheet, Firefox and Chrome print the sentence you searched for, then draw the raw tags as a collapsible tree. That is the whole meaning of the message. It is the browser being honest that it has data, not a page.
So the question is never "how do I fix the XML message"; it is "why did S3 send me XML, and what does the XML say?" Here is the document behind the message, in the shape S3 always uses:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>7F3A1B2C4D5E6F70</RequestId>
<HostId>kQ3n…long base64 string…</HostId>
</Error>
Four lines matter:
- Code is the error, written for programs. It is the only line you need to search for, and the table below decodes every one.
- Message is the human version. For AccessDenied it is just "Access Denied"; for others it is more useful, such as "The specified key does not exist" or "Request has expired."
- RequestId and HostId identify this exact request in AWS's logs. They are harmless to share and are what AWS Support asks for.
- Some errors add fields: a wrong-Region error includes the correct
Endpoint, an expired link includesExpiresandServerTime, a redirect includes the bucket.
There is one more thing the browser hides: the HTTP status. Every code pairs with a number, and the number tells you the category: 403 means "exists but you may not," 404 means "not here," 301 means "wrong address," 400 means "your request was malformed." To see it, open the link in a terminal:
curl -s -D - "https://example-bucket.s3.us-east-1.amazonaws.com/price-list.pdf" -o /dev/null
The first line of the output is the status, for example HTTP/1.1 403 Forbidden. Drop the -o /dev/null to see the XML too. In the browser, the developer tools' Network tab shows the same number.
Jake: "So my customers were reading a message meant for a computer program."
Ethan: "Exactly. S3's front desk speaks XML to programs. Your customers walked up to the program's window instead of the shop's door, and it answered the only way it knows."
Every <Code> the XML can carry, with its HTTP status and the fix
These are the codes you will meet in a browser, with S3's own description and status, and the one-line fix. Codes that only appear in uploads and programs (multipart, checksums, conditional writes) are left out so the table stays useful.
| Code | Status | S3's description | What to do |
|---|---|---|---|
| AccessDenied | 403 | Access Denied | The object is private, Block Public Access is on, a policy denies it, or a presigned link expired. Section |
| NoSuchKey | 404 | The specified key does not exist. | The path after the bucket name is wrong (case, spaces, folder, extension). Section |
| NoSuchBucket | 404 | The specified bucket does not exist. | Bucket name typo, bucket deleted, or wrong account. Section |
| PermanentRedirect | 301 | The bucket you are attempting to access must be addressed using the specified endpoint. | Use the Region-specific address the XML gives you. Our PermanentRedirect guide |
| TemporaryRedirect | 307 | You are being redirected to the bucket while DNS updates. | A brand-new bucket; use the Region address and wait a little |
| AllAccessDisabled | 403 | All access to this Amazon S3 resource has been disabled. | AWS disabled the bucket (abuse report, unpaid bill). Check the account's email and open a Support case |
| InvalidObjectState | 403 | The action is not valid for the current state of the object. | The object is in Glacier or Deep Archive; restore it first |
| SignatureDoesNotMatch | 403 | The request signature we calculated does not match the signature you provided. | A presigned link was altered when pasted, or the signer's clock or key is wrong. Section |
| InvalidAccessKeyId | 403 | The AWS access key ID you provided does not exist in our records. | The key that signed the link was deleted; make a new link with live credentials |
| ExpiredToken | 400 | The provided token has expired. | The temporary credentials that signed the link ran out before the link did. Section |
| RequestTimeTooSkewed | 403 | The difference between the request time and the server's time is too large. | The signing machine's clock is wrong. Our clock-skew guide |
| AuthorizationHeaderMalformed | 400 | The authorization header you provided is invalid. | Usually the wrong Region in the signature; the XML names the right one |
| InvalidRequest | 400 | Varies; for example "Please use AWS4-HMAC-SHA256." | An old-style signed link against a newer Region; regenerate with Signature Version 4 |
| InvalidBucketName | 400 | The specified bucket is not valid. | Capital letters, underscores or a stray character in the bucket part of the link |
| MethodNotAllowed | 405 | The specified method is not allowed against this resource. | A form or script is POSTing to a plain object URL; use the right operation or a presigned POST |
| NoSuchVersion | 404 | The version ID specified in the request does not match an existing version. | The link carries a versionId that was deleted; link to the object without it |
| InvalidRange | 416 | The requested range cannot be satisfied. | A video or PDF viewer asked for bytes past the end; usually a cached old size. Hard refresh |
| SlowDown | 503 | Reduce your request rate. | A burst of requests to one prefix; retry with backoff |
| InternalError | 500 | We encountered an internal error. Please try again. | Retry; if it persists, check the AWS Health Dashboard |
| AccountProblem / NotSignedUp / InvalidPayer | 403 | A problem with the AWS account, or the account is not signed up for S3, or access to the object has been disabled. | Account-level; billing or Support, not a bucket setting |
Two codes deserve their own warning. AccessDenied for a file that does not exist is normal: if the viewer lacks permission to list the bucket, S3 answers a missing object with 403, not 404, so it does not reveal what exists. Our NoSuchKey guide explains that rule and the one command that proves which you have. And the XML with no <Error> at all, a document starting <ListBucketResult> with a list of <Key> entries, is not an error: it is a public listing of everything in your bucket, which is a problem of a different kind. See the listing section.
AccessDenied: the file is private, and the three honest ways to share it
Every object in S3 is private when created. The console's Object URL is a real address, but following it as an anonymous visitor is the same as walking into a bank and asking for someone's safe-deposit box: the answer is Access Denied. That is the default, not a fault. You have three legitimate ways to let a visitor see the file.
Way 1: make the object public with a bucket policy (for things meant to be public)
This is the right choice for a price list, a brochure, a public website. Two settings must agree. First, Block Public Access, which is on by default at both the account and the bucket level, must be turned off for the bucket; AWS applies "the most restrictive combination" of the two levels, so an account-level block overrides a bucket-level allow. Second, a bucket policy must grant everyone s3:GetObject. AWS's own policy for a public website:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::example-bucket/*"]
}
]
}
Replace the bucket name, and consider narrowing /* to a folder such as /public/* so that only the files you put there are public. Steps: S3 console, the bucket, Permissions, Block public access, Edit, clear "Block all public access," save; then Bucket policy, Edit, paste, save. If the policy editor refuses with "Policy has invalid resource," the bucket name in the policy does not match; if it refuses to save at all, Block Public Access is still on at the account or bucket level.
What you should not do in 2026 is reach for the old "make public" ACL on each file. New buckets have ACLs disabled (the "Bucket owner enforced" ownership setting), and trying to set one returns AccessControlListNotSupported. Policies replaced ACLs; our guide to the "bucket does not allow ACLs" error covers the old tutorials that still teach it. The exception: if a bucket contains objects uploaded by a different account and ACLs are enabled, the bucket policy does not cover them and an object ACL granting READ to AllUsers is needed.
Way 2: share a presigned link (for one person, for a while)
A presigned URL is the object's address plus a signature that lets anyone holding it read the object until an expiry you choose, with no change to the bucket's permissions. The console makes one in three clicks: select the object, Object actions, "Share with a presigned URL," pick a duration up to 12 hours. The command line allows up to 7 days:
aws s3 presign s3://example-bucket/price-list.pdf --expires-in 604800
Right for an invoice, a draft, a customer's photos. Wrong for a link on a social page, because it will stop working, and the section below explains exactly when.
Way 3: put CloudFront in front (for a website, HTTPS, or a private bucket)
CloudFront is AWS's content network. With an Origin Access Control, it reads from a bucket that stays fully private and serves the files to the public over HTTPS, under your own domain if you want. Block Public Access stays on, which is the right setup for anything bigger than a single file. Our guides to CloudFront with an S3 origin and to putting a custom domain with HTTPS in front of S3 walk through it.
AccessDenied that survives all three
If the policy is right, Block Public Access is off and the XML still says AccessDenied, the remaining causes are a Deny somewhere (a bucket policy statement, a service control policy from an organization, a VPC endpoint policy), an object encrypted with a KMS key the viewer cannot use, an object owned by another account with ACLs on, or a Requester Pays bucket. Our S3 AccessDenied guide takes those one gate at a time.
Jake: "So the price list wants Way 1, and the customer's repair photos want Way 2."
Ethan: "And the day you want the shop's own domain on it, Way 3. The XML you saw was S3 refusing to pick for you."
NoSuchKey and NoSuchBucket: the link is wrong, and how
These two are 404s: nothing to do with permission, everything to do with the address.
NoSuchKey ("The specified key does not exist") means the bucket was found but the path after it was not. S3 has no folders, only keys that contain slashes, and it matches them exactly. The usual culprits, in order: a capital letter that should be lowercase (Price-List.pdf versus price-list.pdf); a space that became + or %20 in one place and not the other; a link to a "folder" (/brochures/) which is not an object; a file that was renamed or overwritten; and a link copied from one bucket and pasted under another. Copy the Object URL from the console again rather than typing it. Thirteen causes, including the delete-marker and CloudFront ones, are in our NoSuchKey guide.
NoSuchBucket ("The specified bucket does not exist") means the bucket part of the address was not found. Bucket names are global and exact, so this is a typo, a deleted bucket, a bucket in another AWS account, or a name with a dot in it being read as a subdomain. When the bucket exists and you can see it in the console, our NoSuchBucket-but-it-exists guide lists the eight reasons S3 still cannot find it, most of them Region mistakes.
The two S3 addresses, and why your website shows XML
This is the section for everyone whose static website shows XML, which the search results call "S3 website shows XML" and "CloudFront XML error." The same bucket answers at two different kinds of address, and only one is a website.
| REST (API) endpoint | Website endpoint | |
|---|---|---|
| Address looks like | bucket.s3.us-east-1.amazonaws.com | bucket.s3-website-us-east-1.amazonaws.com (or s3-website. with a dot in some Regions) |
| Errors come back as | XML (this page's symptom) | HTML, including your own error document |
Visiting the root / | Returns a list of object keys (XML) | Returns your index document |
Visiting /about/ | NoSuchKey, because no object is called about/ | Serves about/index.html |
| Redirect rules | Not applicable | Object-level and bucket-level redirects |
| Content it can serve | Public and private (with signatures) | Public only |
| HTTPS | Yes | No; use CloudFront or Amplify Hosting for HTTPS |
| Methods | All bucket and object operations | GET and HEAD on objects only |
Read the second row twice. If your visitors ever see XML, they are on the REST address. A website that shows XML at / or at /about/ is being served from the API endpoint, where there is no index document and no folder handling. The fix is not a permission; it is the address. Turn on Static website hosting under the bucket's Properties, set the index document (usually index.html) and an error document, and use the website endpoint the console then shows. Your DNS record or CloudFront origin should point at that address, with s3-website in it.
Two side effects of the website endpoint are worth knowing. It serves only public content, so Way 1's bucket policy is still required, and on the website endpoint a missing object returns 404 while an existing object without read permission returns 403, which lets a visitor tell the two apart. And it has no HTTPS at all, so a modern browser will label it "Not secure"; the cure is CloudFront or Amplify Hosting in front. Our static website 403 guide covers the permission half; this page covers the address half.
CloudFront showing the same XML
CloudFront passes S3's error body straight through to the browser, so a CloudFront URL can show the identical XML page. Three causes account for nearly all of them:
- The origin is the REST endpoint and the viewer asked for a folder. CloudFront's default root object only applies to the very top of the site (
/);/about/goes to S3 as the keyabout/, and S3 answers NoSuchKey or, with a private bucket, AccessDenied. Either make the origin the website endpoint (public bucket, HTTP between CloudFront and S3), or keep the private REST origin and add a CloudFront Function that rewrites/about/to/about/index.html. For a single-page app, map 403 and 404 to/index.htmlwith status 200 in the distribution's custom error responses. - Origin Access Control without
s3:ListBucket. With a private bucket, a missing file comes back as AccessDenied (403) rather than NoSuchKey (404) unless the OAC's policy also allows listing. That makes a typo look like a permissions failure. Our CloudFront 403 from S3 guide has the policy. - The error was cached. CloudFront caches error responses too, by default briefly. After you fix the bucket, invalidate the path or wait out the error caching TTL before concluding the fix failed.
"Request has expired": presigned links and the clock behind them
A presigned link that worked yesterday and shows AccessDenied today, with the Message "Request has expired" and Expires and ServerTime fields in the XML, has simply run out. Make a new one. The surprise is how early "today" can be, because a presigned URL dies at whichever comes first: the expiry you chose, or the end of the credentials that signed it. AWS's rules:
| Who made the link | Longest it can live |
|---|---|
| The S3 console | 12 hours |
| The CLI or an SDK with an IAM user's long-term keys | 7 days (604,800 seconds) |
| Code on an EC2 instance using its role | Until the role's credentials rotate, typically about 6 hours |
| Code in Lambda, ECS or anything using an assumed role | Until the role session ends; AssumeRole defaults to 1 hour, ECS rotates every 1 to 6 hours |
| Any link whose signing key is later deleted or deactivated | Dies immediately (InvalidAccessKeyId or ExpiredToken) |
So a "7-day" link generated inside a Lambda function usually lasts an hour, and the XML for that case says ExpiredToken: "The provided token has expired." If you need week-long links from server code, sign them with an IAM user's keys kept for that purpose, or regenerate links on demand instead of storing them. A download that is already in progress when the link expires completes; a restart after expiry fails.
SignatureDoesNotMatch on a presigned link nearly always means the link was changed in transit: an email client wrapped it, a chat app rewrote &, someone pasted it with a trailing period, or a proxy added headers. Send it as a short link or in a plain-text field, and compare the pasted URL with the original character for character. On the signing side, a clock more than a few minutes off produces RequestTimeTooSkewed instead; our clock-skew guide fixes that in a minute.
The XML that lists all your files: not an error, a leak
Sometimes the page is not an <Error> at all. It starts with <ListBucketResult> and contains a <Contents> entry for every object: names, sizes, dates. That is what the REST endpoint returns for a GET on the bucket root when the viewer is allowed to list it, and if an anonymous visitor sees it, your bucket policy grants s3:ListBucket to everyone, which the public-website policy above deliberately does not. Edit the policy so that Principal: "*" gets s3:GetObject on bucket/* and nothing on the bucket itself. For a website, use the website endpoint, where the root serves the index document instead. A directory listing of a customer-files bucket is the kind of thing that ends up in a news story; check for it once.
Reading the Code from a program, not a browser
The same XML reaches your code when a script or app hits one of these errors, and the SDKs parse it for you, so the thing to match on in code is the same Code element, never the message text. Three common shapes:
Python (boto3). Every S3 failure raises ClientError, and the XML's fields sit inside its response dictionary:
import boto3
from botocore.exceptions import ClientError
s3 = boto3.client("s3", region_name="us-east-1")
try:
s3.get_object(Bucket="example-bucket", Key="price-list.pdf")
except ClientError as e:
code = e.response["Error"]["Code"] # "AccessDenied", "NoSuchKey", ...
status = e.response["ResponseMetadata"]["HTTPStatusCode"]
request_id = e.response["ResponseMetadata"]["RequestId"]
if code == "NoSuchKey":
print("wrong path:", e.response["Error"]["Message"])
elif code == "AccessDenied":
print("private or denied; status", status, "request", request_id)
else:
raise
One boto3 quirk from our NoSuchKey guide: head_object on a missing key raises a bare "404" as the Code, not NoSuchKey, because a HEAD response has no body for the XML to travel in. Match both.
JavaScript (SDK v3). The error's name is the Code, and the status is in metadata:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: "us-east-1" });
try {
await s3.send(new GetObjectCommand({ Bucket: "example-bucket", Key: "price-list.pdf" }));
} catch (err) {
console.log(err.name, err.$metadata.httpStatusCode, err.$metadata.requestId);
// "NoSuchKey 404 ..." or "AccessDenied 403 ..."
}
The command line. aws s3 cp s3://example-bucket/price-list.pdf . prints the Code and Message on one line ("An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist."). aws s3 ls s3://example-bucket/ is the fastest way to prove whether the key is really there and spelled the way you think.
Two rules for code that talks to S3. Treat NoSuchKey and AccessDenied as the same question ("can this caller see this key?") unless the caller also has list permission, because without it a missing key arrives as 403. And log RequestId with every failure; it is the one thing AWS Support can look up.
For IT admins: finding out who is getting the XML
When customers or staff report the XML page and you cannot reproduce it, the answer is in the logs rather than in guesswork.
- S3 server access logs record every request to the bucket, including the HTTP status, the error code, the key requested and the requester's IP, written to a second bucket you choose. Turn them on under the bucket's Properties. A day of logs shows exactly which keys return 403 and 404, and whether the requests are anonymous or signed. There is no charge for the logging itself beyond storing the log files.
- CloudTrail data events for S3 record object-level API calls with the IAM identity that made them. Use them when the question is "which role or user is being denied," rather than "which link is wrong." Data events are billed per event, so enable them for the bucket you are investigating, not the account.
- The access analyzer for S3, in the S3 console's left menu, lists buckets that are public or shared outside your account. If a bucket you believed private appears there, that is where the ListBucketResult leak came from.
For a shop like Jake's, one practical setup covers everything: a public/ prefix with a read-only policy, Block Public Access on for every other bucket, CloudFront in front of the public one for HTTPS, and access logging on so the next "I saw a strange page" message takes two minutes to answer.
The two-minute diagnosis, in order
- Read the <Code>. Not the browser sentence, not the Message. Find it in the table.
- Check the address. Does it contain
s3-website? If this is a website and it does not, that is the whole problem. - Get the status.
curl -s -D - URL -o /dev/null. 403 is permission or expiry; 404 is the path; 301 is the Region; 400 is a malformed link. - For 403 on a file meant to be public: Block Public Access off at bucket and account, bucket policy with s3:GetObject for
*, no Deny statements, object not KMS-encrypted with a restricted key. - For 403 on a presigned link: compare
ExpireswithServerTimein the XML, and check what credentials signed it. - For 404: copy the Object URL from the console again; look for case and spaces; make sure you are not linking a folder.
- Through CloudFront: repeat steps 1 to 6 directly against the S3 address first. If S3 is fine and CloudFront is not, it is the origin type, the OAC policy or a cached error.
Stop the XML page from ever reaching a customer
- Never share a REST-endpoint URL with the public. Share a website-endpoint URL, a CloudFront URL, or a presigned link, depending on which of the three ways applies.
- Set an error document on the website endpoint (
error.html) so that even a wrong link shows your page, in your words, with your phone number on it. - Put CloudFront in front of anything customers touch, for HTTPS and for custom error pages that replace S3's XML with something human.
- Keep a public folder. Grant public read on
/public/*only, and keep everything else private by default. - Treat presigned links as tickets, not addresses. They expire by design; generate them when needed.
S3 "This XML file does not appear to have any style information": frequently asked questions
What does "this XML file does not appear to have any style information associated with it" mean?
It is the browser's standard message for any XML document with no stylesheet, not an AWS error. S3 returned an XML error response; the real error is the Code element inside it, usually AccessDenied, NoSuchKey, NoSuchBucket or PermanentRedirect.
How do I fix the XML error with AccessDenied on an S3 link?
The object is private. Either turn off Block Public Access on the bucket (and account) and add a bucket policy granting s3:GetObject to everyone on the objects you want public, or share a presigned URL, or serve the bucket through CloudFront with Origin Access Control.
Why does my S3 static website show XML instead of my pages?
You are using the bucket's REST endpoint (bucket.s3.region.amazonaws.com), which returns XML errors and has no index document. Enable static website hosting and use the website endpoint, which has s3-website in the address and returns HTML, or put CloudFront in front.
Why does CloudFront show "this XML file does not appear to have any style information"?
CloudFront passes S3's XML error through. Usual causes: a REST origin asked for a folder path (default root object only applies at /), an Origin Access Control without s3:ListBucket turning missing files into AccessDenied, or a cached error. Fix the origin type or policy, then invalidate.
What is the difference between an S3 REST endpoint and a website endpoint?
The REST endpoint supports every operation, public and private content and HTTPS, and returns XML errors. The website endpoint serves only public content over HTTP, supports only GET and HEAD, serves an index document for folders, supports redirects, and returns HTML error pages.
How do I see the real error behind the XML page?
Read the Code element in the XML, then get the HTTP status with curl -s -D - URL -o /dev/null or the browser's Network tab. 403 is permission or expiry, 404 is a wrong path or bucket, 301 is the wrong Region, 400 is a malformed request.
Why does my S3 link say NoSuchKey when the file is there?
Keys match exactly: a capital letter, a space encoded differently, a folder path with no object, or a renamed file all produce NoSuchKey. Copy the Object URL from the console again. Without list permission a missing key shows as AccessDenied instead.
Why does a presigned S3 URL say "Request has expired"?
The link passed its expiry, or the credentials that signed it expired first. Console links last up to 12 hours, CLI and SDK links up to 7 days with IAM user keys, but links signed by EC2 or Lambda roles die when the role session ends, often within an hour. Generate a new link.
What does ExpiredToken mean on an S3 link?
The temporary security token used to sign the presigned URL has expired, so S3 returns 400 ExpiredToken. Sign with long-lived IAM user keys for longer links, or regenerate links on demand.
Why does the XML list all the files in my bucket?
A ListBucketResult document means the viewer was allowed to list the bucket, which for an anonymous visitor means your bucket policy grants s3:ListBucket to everyone. Restrict the policy to s3:GetObject on bucket/* and use the website endpoint for a site.
Does making a bucket public fix the XML error?
For AccessDenied on files meant to be public, yes, via Block Public Access off plus a bucket policy. It does nothing for NoSuchKey, NoSuchBucket or PermanentRedirect, which are address problems, and it is the wrong fix for private files, which need presigned URLs or CloudFront.
Why can't I make an object public with an ACL anymore?
New buckets disable ACLs (Bucket owner enforced), so setting one returns AccessControlListNotSupported. Use a bucket policy instead. The only case needing an object ACL is a bucket with ACLs enabled holding objects uploaded by another account.
What is PermanentRedirect in the S3 XML?
A 301 saying the bucket must be addressed through a different Region endpoint, which the XML names in its Endpoint element. Use the Region-specific address or set the Region in your client.
Why does the S3 website endpoint not support HTTPS?
By design: website endpoints are HTTP only. For HTTPS, serve the site through CloudFront or Amplify Hosting, which also lets you keep Block Public Access on and show custom error pages instead of XML.
What are RequestId and HostId in the S3 error XML?
Identifiers for that exact request in AWS's logs. They contain nothing sensitive and are what AWS Support asks for when you open a case about an S3 error.
How do I replace the S3 XML error page with my own?
On the website endpoint, set an error document in the static website hosting settings. Through CloudFront, add custom error responses that map 403 and 404 to your own page. The REST endpoint itself always returns XML.
Jake's price list took the first way: Block Public Access off for that one bucket, a policy granting read on /public/*, the PDF moved into that folder, and a fresh Object URL on the social page that opened as a PDF instead of a tree of brackets. The customer who asked about hacking got a reply and a discount. Ethan's parting advice was the one line below, and a promise that the day Jake wants his own domain on it, CloudFront takes an evening.
📌 If you keep one line from this page
The browser sentence means nothing; the <Code> means everything, and an address without s3-website in it was never a website.
Public things get a bucket policy, private things get a presigned link, and anything customers touch gets CloudFront in front.
Revision note. Written October 10, 2026, for everyone whose first S3 link went out before they knew there were two kinds. "Don't shoot the messenger," says the proverb; the XML only carried the news, and the news was one line long.