S3 "The Specified Key Does Not Exist" (NoSuchKey): Every Cause in Order, the One Command That Proves It, and Why 403 Is Often 404

Logeshwaran
—

"The specified key does not exist" is Amazon S3's NoSuchKey error, HTTP status 404, and it means exactly one thing: in the bucket you asked, there is no object whose key is spelled, byte for byte, the way you spelled it. The fix is almost always a small mismatch between the key you think you uploaded and the key S3 actually stored: a leading slash, a capital letter, a space that became a plus sign, a "folder" that was never an object, or a file that was deleted behind a versioning delete marker. Here is the part that costs people a whole afternoon: the same missing object comes back as 403 AccessDenied instead of 404 NoSuchKey when the caller lacks the s3:ListBucket permission, so half the "NoSuchKey" reports online are really permission problems, and half the "AccessDenied" reports are really typos. This page gives you the one command that settles it, then every cause in the order they actually happen, with fixes for the AWS CLI, Python, JavaScript, Java, CloudFront and static websites.

Jake runs a phone repair shop, and his little website keeps its photos in an S3 bucket. On Monday he "tidied up" in the S3 console, moving images into a folder called Photos, and by Monday afternoon every picture on the site was a broken square. The server logs said the same thing a hundred times: NoSuchKey: The specified key does not exist. His friend Ethan, a developer, asked one question: "What is the exact key, and what is the exact key you're asking for?" Jake said they were the same. They were not; one started with a capital P. This page is the checklist Ethan walked him through: what the error really means, the command that proves whether a key exists, the thirteen causes in order of likelihood, how each SDK reports it, the CloudFront and static-website versions of the same problem, the mistakes that keep it alive, and how Jake's website came back in twenty minutes.

⚡ Quick Answer

• What it means → No object with that exact key in that bucket. Keys are case-sensitive and include the "folder" prefix. What the error says.

• The one command → aws s3api list-objects-v2 --bucket BUCKET --prefix "first-part-of-key", then compare byte for byte. Prove the key.

• Most common causes → leading slash, capital letters, space vs plus sign, a "folder" that is not an object, wrong bucket. All thirteen.

• Getting 403 instead? → S3 hides a missing object behind AccessDenied when you lack s3:ListBucket. 403 vs 404.

• Versioned bucket? → a delete marker makes a live file "not exist"; look for the x-amz-delete-marker header. Versioning.

Nothing on this page needs a support ticket. Every check runs in under a minute with the AWS CLI.

New to S3? Our plain-English guide to Amazon S3 explains buckets, objects and keys in ten minutes, and the idea that "folders" do not exist is the single most useful thing in it for this error.

🧭 NEW HERE? READ THESE FIRST

New to S3 errors? These five pages make the rest of this one easy:

📌 Bookmark this; the prove-the-key command and the 403-vs-404 rule are the two things you will come back for.

What "The specified key does not exist" actually says

In S3, the key is the object's full name inside the bucket, prefixes included. An object you see in the console as Projects.xls inside a folder called Development has the key Development/Projects.xls, slash and all. The AWS documentation puts it plainly: object key names are case sensitive, they include any prefixes, and S3's data model is flat. There is no hierarchy of folders; the console draws one from the slashes.

So when S3 says the key does not exist, it has done a lookup on an exact string and found nothing. It is not a fuzzy "could not find something like that." The raw response, which most SDKs wrap, looks like this:

HTTP/1.1 404 Not Found

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>NoSuchKey</Code>
  <Message>The specified key does not exist.</Message>
  <Key>photos/galaxy-s26-screen.jpg</Key>
  <RequestId>...</RequestId>
  <HostId>...</HostId>
</Error>

The Key element is the most useful line on the page, and almost nobody reads it. It is the key S3 was asked for, after every layer of your code and the SDK has touched it. If it does not match what you expected, the bug is on your side of the request, and you have just found where.

Several near-identical errors are easy to confuse with this one, and they have different fixes. Here they are side by side:

ErrorHTTP statusWhat it really meansFirst check
NoSuchKey404No object with that exact key in that bucketList the bucket by prefix and compare keys
NoSuchBucket404No bucket with that name in this account or Regionaws s3api list-buckets, and check the Region
AccessDenied403No permission, or the object is missing and you lack s3:ListBucketRepeat the request with list permission
NoSuchVersion404The key exists; that version ID does notaws s3api list-object-versions
404 with x-amz-delete-marker: true404The current version is a delete marker; the data is one version downDelete the marker by its version ID
405 with x-amz-delete-marker: true405You asked for the delete marker's own version IDAsk for a real version ID instead
InvalidObjectState403The object is archived in Glacier and not restoredRestoreObject, then wait
PermanentRedirect301Right bucket, wrong Region endpointUse the bucket's Region

The three to know in detail:

  • NoSuchBucket (404): the bucket name is wrong, or you are in the wrong account or Region. Our guide to NoSuchBucket when the bucket exists covers it.
  • AccessDenied (403): either you really lack permission, or the object is missing and S3 is hiding that from you. The next section explains which.
  • NoSuchVersion (404): the key exists but the version ID you asked for does not.

403 AccessDenied or 404 NoSuchKey? S3 picks based on one permission

This is the rule that unlocks most stuck cases, and it comes straight from the GetObject API reference. If the object you request does not exist, the error S3 returns depends on whether you also have the s3:ListBucket permission on the bucket:

  • With s3:ListBucket: S3 returns 404 Not Found, with the NoSuchKey code.
  • Without s3:ListBucket: S3 returns 403 Access Denied, for the same missing object.

S3 does this on purpose. If anyone could tell a missing key from a forbidden one, they could probe your bucket for file names. The side effect for you is that an application running under a tight IAM role, one that can read objects but not list the bucket, will report a plain typo as a permissions failure. Ethan's rule for Jake: a 403 on an object you cannot find in a listing is a 404 wearing a disguise.

Two practical consequences:

  • When debugging, run the checks below with credentials that have s3:ListBucket, so the error you see is the honest one.
  • If your production role must stay tight, code for both outcomes: treat a 403 on GetObject as "missing or forbidden," not as "forbidden," and log the key.

If the 403 survives even with list permission, it is a real permission problem, and our AccessDenied on GetObject guide takes over from here.

Step one: prove whether the key exists, in one command

Before changing any code, find out what S3 actually has. The listing call is the only one that cannot lie to you about spelling, because it returns the stored keys themselves:

aws s3api list-objects-v2 --bucket my-shop-photos --prefix "photos/galaxy" \
    --query "Contents[].Key" --output text

Use a short prefix, the first few characters you are sure about, so that near-misses show up next to what you expected. Then put the key from your error and the key from the listing side by side, character by character. Jake's pair looked like this:

asked for:  photos/galaxy-s26-screen.jpg
stored as:  Photos/galaxy-s26-screen.jpg

One capital letter. S3 sorts keys by their byte values, so Photos/ and photos/ are not even neighbors in a listing; they are two different folders as far as S3 is concerned.

Two more commands cover the other half of the picture:

# Does this exact key exist? (404 = no, 403 = no list permission or no access)
aws s3api head-object --bucket my-shop-photos --key "photos/galaxy-s26-screen.jpg"

# In a versioned bucket: is there a delete marker on top of it?
aws s3api list-object-versions --bucket my-shop-photos --prefix "photos/galaxy-s26-screen.jpg"

head-object asks for the metadata of one exact key and nothing else. It is the fastest yes-or-no in S3. Note that a HEAD request carries no body, so the SDKs report it as a bare 404 or 403 status rather than a NoSuchKey code; the sections on Python and JavaScript below show how that changes your error handling.

If you have thousands of keys and need to search them properly, listing by prefix becomes slow and expensive. Our guide to querying S3 with Athena shows how S3 Inventory plus a SQL query finds a needle in a million keys.

The five-minute diagnostic, in order

  1. Copy the exact key from the error's Key element, not from your code or the console.
  2. List the bucket with a short prefix using credentials that have s3:ListBucket, and compare byte for byte.
  3. If the key is in the listing, HEAD it. A 200 means your production role is the problem, not the key. A 404 here means versioning: check for a delete marker.
  4. If the key is not in the listing, it was never stored under that name: look for a leading slash, case, spaces, the wrong bucket, or an upload that did not finish.
  5. Only then touch permissions. A 403 that survives with list permission is a real AccessDenied, and a different page.

Every cause of NoSuchKey, in the order they actually happen

Here is the whole list, roughly ordered by how often each one turns out to be the answer. The table is the short form; the sections below explain each one.

CauseWhat you seeHow to confirmFix
Leading slashKey in the error starts with /Listing shows the key without itStrip the slash before calling S3
Case mismatchPhotos/ vs photos/Listing with a one-letter prefixFix the code or rename the objects
Space vs plus vs %20my photo.jpg vs my+photo.jpgListing shows the stored bytesStandardize the encoding; avoid spaces
Folder, not objectGET on photos/ or photosNothing stored at that exact keyList by prefix instead of opening the folder
Wrong bucket or accountEverything looks rightListing returns nothing at allCheck the bucket name and the role's account
Delete marker404 plus x-amz-delete-marker: truelist-object-versions shows a marker on topDelete the marker by version ID
Upload never completedKey was "just uploaded"list-multipart-uploads, uploader logsComplete or redo the upload
Lifecycle expirationOld keys vanish on a scheduleBucket lifecycle rules; marker datesChange the rule, restore from versions
Dots or ../ in the keyTools normalize the pathCompare raw key bytesAvoid period-only path segments
Wrong variableKey contains s3:// or an empty prefixRead the Key elementFix the key builder
Look-alike UnicodeKeys look identicalHex-dump the two keysNormalize names before upload
CloudFront folder URLViewer sees AccessDeniedOrigin log shows NoSuchKey for about/Viewer-request function appends index.html
Website folder without indexphotos/ returns an errorNo photos/index.html objectAdd an index document per folder

Check them in order and you will rarely get past number five.

1. A leading slash in the key

Web URLs start with a slash; S3 keys do not. A key of /photos/a.jpg is a valid, different key from photos/a.jpg, and the console shows it as an object inside a folder with an empty name. This bites anyone who builds keys from URL paths, or who passes a path-style URL's path straight through. Strip the leading slash before you call S3, and check the Key element in the error for a slash you did not mean.

2. Capital letters

Keys are case sensitive, byte for byte. Invoice.PDF, invoice.pdf and Invoice.pdf are three objects. Uploads from Windows machines, where file names are not case sensitive, create this mismatch constantly: the user "knows" the file is called invoice.pdf because Windows opens it either way.

3. Spaces, plus signs and percent encoding

A key with a space in it, such as my photo.jpg, is legal. The trouble is that different tools encode it differently. Some turn the space into +, some into %20, and some upload the literal space. An object stored as my+photo.jpg will never match a request for my photo.jpg. The same goes for &, =, @, ;, :, , and ?, which the S3 naming guidelines list as characters that most likely must be URL-encoded or handled specially. The fix is in two halves: list the bucket to see what was really stored, and standardize on one encoding in your code, ideally by avoiding these characters in keys you control. If the plus sign is in a presigned URL, a related error, SignatureDoesNotMatch, shows up instead; our SignatureDoesNotMatch guide covers that case.

4. Asking for a folder, which is not an object

When you create a folder in the S3 console, the console creates a zero-byte object whose key ends in a slash, such as photos/. When you upload files with the CLI or an SDK under a prefix, no such object is created at all. So GetObject on photos/ or photos fails with NoSuchKey in most buckets, because there is nothing there to get. Prefixes are not things; only objects are. If your code "opens the folder first," delete that step and list by prefix instead.

5. Wrong bucket, wrong account or wrong Region

The key exists, just not where you are looking. A staging bucket and a production bucket with the same prefix layout, or a role that assumed into the wrong account, produce an honest NoSuchKey. The listing command settles it in a second. A wrong Region usually gives a different error, PermanentRedirect, and our PermanentRedirect guide handles that one.

6. A delete marker in a versioned bucket

In a bucket with versioning on, a plain delete does not remove anything. S3 adds a delete marker as the newest version, and the object behaves as if deleted: a GET without a version ID returns a 404 and the response header x-amz-delete-marker: true. The data is still there, one version down. The versioning section shows how to see it and bring it back.

7. The upload never finished

The code that uploads and the code that reads may disagree about whether the upload succeeded. A multipart upload that was started but never completed leaves no object, only parts. An upload that threw an exception the caller swallowed leaves nothing. Check the uploader's logs and, for multipart, aws s3api list-multipart-uploads --bucket BUCKET.

8. A lifecycle rule or a cleanup job removed it

Lifecycle rules expire objects after a set number of days, and they do it quietly. If keys that used to work now return NoSuchKey, and they are old, check the bucket's lifecycle configuration before suspecting your code. In a versioned bucket the expiration adds a delete marker, which is cause 6 again.

9. Dots and relative path pieces in the key

Keys containing ./ or ../ segments are legal within limits, but tools, SDKs and browsers normalize them away, so folder/./file.txt becomes folder/file.txt somewhere between your code and S3. The S3 naming guidelines recommend avoiding period-only path segments entirely. Also, downloading an object whose key ends with a period through the console strips the period, which makes the next upload a different key.

10. The key was built from the wrong variable

The boring one. A template that renders {{ folder }}/{{ name }} with an empty folder gives /name, cause 1. A function that passes the full s3://bucket/key URI as the key gives a key starting with s3://. Again, the Key element in the error shows exactly what was sent.

11. Unicode that looks identical

Two keys can look the same on screen and differ in bytes: a composed Γ© versus an e plus a combining accent, a non-breaking space versus a normal one. Files named on a Mac and uploaded from Windows are the classic source. The listing command, with its output piped through a hex viewer, is the only way to see it.

12. CloudFront asking for a key that was never there

When CloudFront fronts a private bucket and a visitor requests /about/, CloudFront asks S3 for the key about/, which does not exist, and S3 answers NoSuchKey. CloudFront then shows the viewer a generic AccessDenied instead, to avoid leaking what is in your bucket. The CloudFront section explains the default root object rule and the one-function fix.

13. A static website without an index document in that folder

On the S3 website endpoint, a request for photos/ returns photos/index.html only if that exact object exists. There must be an index document in every folder, with the same name. Without it, S3 returns an error. The static website section has the rules.

Versioned buckets: the file that "does not exist" is still there

Versioning changes what delete means, and that change is behind a surprising share of NoSuchKey reports from people who swear nobody deleted anything. In a bucket with versioning enabled, a plain delete, one with no version ID, does not remove the object. S3 adds a delete marker as the newest version, and the marker has no data. From then on:

  • A GET without a version ID returns 404 Not Found and the header x-amz-delete-marker: true.
  • A GET with the delete marker's own version ID returns 405 Method Not Allowed, with the same header and a Last-Modified timestamp.
  • Ordinary listings, aws s3 ls and ListObjectsV2, do not show the object at all, because its current version is a marker.

That header is your tell. If a HEAD or GET comes back 404 with x-amz-delete-marker: true, the key exists, the data exists, and someone or something, a user, a script or a lifecycle rule, deleted it. To see the history:

aws s3api list-object-versions --bucket my-shop-photos --prefix "photos/galaxy-s26-screen.jpg"

The output has two lists: Versions, the real copies, and DeleteMarkers. If a marker is on top ("IsLatest": true), restoring the file is one command: delete the marker itself by its version ID. The previous version becomes current again, with no re-upload.

aws s3api delete-object --bucket my-shop-photos --key "photos/galaxy-s26-screen.jpg" \
    --version-id "THE-DELETE-MARKER-VERSION-ID"

Two permission details matter here. Reading a specific version needs s3:GetObjectVersion, not s3:GetObject. And a role that can read current objects but cannot list versions will see only the 404, never the history, so run this check with an administrator-level profile. If the bucket has versioning suspended, deletes still create a marker with a null version ID, and the same recovery applies.

How each tool reports NoSuchKey, and how to handle it

The error is the same on the wire. What differs is the wrapper each SDK puts around it, and the one trap shared by all of them: HEAD requests carry no body, so an existence check through HeadObject never sees the word NoSuchKey, only the status code.

AWS CLI

aws s3 cp s3://bucket/key . prints fatal error: An error occurred (404) when calling the HeadObject operation: Not Found, because the high-level s3 commands check the object with HEAD first. The lower-level aws s3api get-object prints the real code: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist. If you see (403) or AccessDenied from either, re-read the 403-vs-404 rule above before touching permissions.

Python (boto3)

boto3 wraps every service error in ClientError and also exposes the S3 ones as named classes on the client:

import boto3
from botocore.exceptions import ClientError

s3 = boto3.client("s3")

try:
    obj = s3.get_object(Bucket="my-shop-photos", Key="photos/galaxy-s26-screen.jpg")
except s3.exceptions.NoSuchKey:
    print("missing: check the key with list-objects-v2")
except ClientError as e:
    code = e.response["Error"]["Code"]
    status = e.response["ResponseMetadata"]["HTTPStatusCode"]
    if code == "AccessDenied" and status == 403:
        print("forbidden, or missing and the role lacks s3:ListBucket")
    else:
        raise

For head_object, there is no NoSuchKey to catch. The exception is a ClientError whose error code is the bare string "404", so test the status code, not the name:

def exists(bucket, key):
    try:
        s3.head_object(Bucket=bucket, Key=key)
        return True
    except ClientError as e:
        if e.response["ResponseMetadata"]["HTTPStatusCode"] == 404:
            return False
        raise   # 403 and everything else: do not silently say "missing"

boto3's own guide warns that error messages can change and should not be relied on in code; branch on the code and the status, never on the text.

JavaScript (AWS SDK v3)

The v3 SDK exports the error as a class, and every error carries the HTTP status in $metadata:

import { S3Client, GetObjectCommand, NoSuchKey } from "@aws-sdk/client-s3";

const s3 = new S3Client({});

try {
  const res = await s3.send(new GetObjectCommand({ Bucket: "my-shop-photos", Key: "photos/galaxy-s26-screen.jpg" }));
  const body = await res.Body.transformToString();
} catch (err) {
  if (err instanceof NoSuchKey || err.name === "NoSuchKey") {
    console.log("missing:", err.Key ?? "(check the key)");
  } else if (err.$metadata?.httpStatusCode === 403) {
    console.log("forbidden, or missing without s3:ListBucket");
  } else {
    throw err;
  }
}

For HeadObjectCommand, as in Python, check err.$metadata.httpStatusCode === 404; the name will be a generic NotFound, not NoSuchKey.

Java (SDK for Java 2.x)

AWS's own example of an existence check uses HeadObject and catches NoSuchKeyException, and its comment states the rule this whole page keeps returning to: the method returns false only when S3 responds with 404, and if the caller does not have s3:ListBucket permission on the bucket, S3 may return 403 instead of 404 for non-existent objects, which is thrown as an exception.

import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;

public boolean doesObjectExist(String bucketName, String key, S3Client s3) {
    try {
        s3.headObject(HeadObjectRequest.builder().bucket(bucketName).key(key).build());
        return true;
    } catch (NoSuchKeyException e) {
        return false;
    }
}

GetObject throws the same NoSuchKeyException for a missing key. If you need the object's contents, call GetObject directly and catch it; a HEAD-then-GET pair costs two requests and opens a gap in which the object can disappear between them.

CloudFront in front of S3: why a folder URL fails and the one-function fix

Put CloudFront in front of a private bucket and a new flavor of this error appears, usually disguised. A visitor opens https://example.com/about/. CloudFront passes the path to S3 as the key about/. There is no such object, so S3 answers NoSuchKey, and CloudFront, to avoid leaking what is in your bucket, shows the viewer a generic AccessDenied. You see a 403 in the browser; the real cause is a 404 at the origin.

Two settings decide what happens:

  • The default root object applies only to the root URL of the distribution. If it is index.html, a request for / returns index.html. A request for /install/ does not return install/index.html, even if that file exists. The CloudFront documentation is explicit about this, and it notes that the setting must not start with a slash: /index.html can itself produce a 403.
  • S3 index documents, which do work per folder, exist only on the S3 website endpoint. CloudFront with Origin Access Control uses the REST endpoint, which has no index-document behavior.

The fix that keeps the bucket private is a tiny CloudFront Function on the viewer-request event. This is AWS's own sample, from its CloudFront Functions examples repository:

async function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // Check whether the URI is missing a file name.
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    }
    // Check whether the URI is missing a file extension.
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

Attach it to the distribution's default behavior as a viewer request function, and /about/ becomes /about/index.html before CloudFront ever asks S3. If the 403 remains after that, it is a real permissions problem between CloudFront and the bucket, and our guide to CloudFront 403 errors with Origin Access Control covers the bucket policy side.

S3 static websites: an index document in every folder

On the website endpoint, S3 does serve index documents, with rules that explain the two common NoSuchKey reports:

  • A request for photos/, with the trailing slash, returns photos/index.html if it exists. If you created a folder structure, you must have an index document at every level, with the same name.
  • A request for photos, without the slash, makes S3 look for an object named photos first. If that is not found, it looks for photos/index.html; if found, it returns a 302 redirect to photos/. If neither exists, S3 returns an error.
  • The index document name is case sensitive. Index.html is not index.html.
  • In a versioned bucket, only the newest version of the index document is served; a delete marker on it takes the whole folder down.

Single-page apps add one more twist: a route such as /orders/42 has no object at all. The usual fix is a custom error document that serves the app's index.html, or, behind CloudFront, a custom error response that maps 404 and 403 to /index.html with a 200 status. If you are getting 403 rather than 404 from the website endpoint, our guide to S3 static website 403 errors is the right page.

A real example: how Jake's photos came back

Here is the twenty minutes, step by step, because the order matters more than any single command.

  1. Read the error, not the symptom. The site's log had the full XML, and the Key line said photos/galaxy-s26-screen.jpg. That told Ethan what was being asked for.
  2. List with a short prefix. aws s3api list-objects-v2 --bucket my-shop-photos --prefix "P" returned keys starting with Photos/. The console "folder" Jake created had a capital P, and his drag-and-drop had moved every file under it.
  3. Decide which side to fix. Renaming a prefix in S3 means copying every object to a new key and deleting the old one; there is no rename. The website's code had the lowercase path in one place. Ethan changed the code.
  4. Prove it with HEAD. aws s3api head-object on one new key returned 200 with a content length. Photos appeared.
  5. Find the stragglers. Three images were still broken. Their keys had spaces, uploaded from the console as iPhone 17 back.jpg, and the site requested iPhone+17+back.jpg. Ethan renamed those three objects to dashes and updated the three links.
  6. Add a guard. A ten-line script now lists the bucket nightly and compares the keys against the links in the site's pages, so the next tidy-up is caught before a customer sees a broken square.

Total changes: one path in the code, three object names, one nightly check. No permissions were touched, because the listing had shown a 404, not a 403, and that told Ethan to leave IAM alone.

The mistakes that keep NoSuchKey alive

Trusting the console's folder view

The console shows a tidy tree, and the tree is a drawing. Keys are flat strings. When something does not match, look at the strings, through the CLI, not the drawing.

Fixing permissions for a 403 that was really a 404

Hours go into bucket policies that were never the problem. Run the listing with a profile that has s3:ListBucket first; if the key is not in the listing, no policy change will make it appear.

Catching the wrong exception on HeadObject

HEAD has no body, so there is no NoSuchKey code to catch. Code that waits for NoSuchKey from a HEAD call never catches anything and crashes on a generic 404.

Treating a 403 as "missing" in production code

The opposite mistake: swallowing 403s as "not found" hides real permission failures and, worse, can make your app overwrite or recreate objects it was simply not allowed to see.

Building keys from URL paths without stripping the slash

request.path starts with a slash; S3 keys should not. One lstrip("/") saves a support ticket.

Mixing encodings for spaces

Pick one: avoid spaces in keys you control, and when you cannot, encode consistently and verify against a listing. A plus sign and a space are different bytes to S3.

Forgetting that lifecycle rules delete things

If old objects vanish on a schedule, the schedule is probably in the bucket's lifecycle configuration, and in a versioned bucket the proof is a delete marker with a recent date.

Deleting the version instead of the marker

When restoring from a delete marker, delete the marker's version ID. Deleting the data version instead makes the loss permanent.

Expecting CloudFront to serve folder index pages

The default root object covers the root URL only. Subfolders need the viewer-request function above, or the website endpoint with a public bucket.

Reading the message instead of the Key

"The specified key does not exist" is the same for every cause. The Key element is different for every bug. Log it.

S3 "The specified key does not exist": frequently asked questions

What does "The specified key does not exist" mean in S3?

It is the NoSuchKey error, HTTP 404: no object in the bucket has exactly the key you requested. Keys are case sensitive and include the full prefix, so a one-character difference, a leading slash or a different "folder" produces this error.

How do I fix NoSuchKey in S3?

List the bucket with aws s3api list-objects-v2 and a short prefix, compare the stored key with the key in your error byte for byte, then fix whichever side is wrong. Most cases are a leading slash, a capital letter, a space encoded as a plus sign, or the wrong bucket.

Why does S3 return 403 AccessDenied instead of 404 for a missing object?

Because the caller lacks s3:ListBucket. With that permission S3 returns 404 Not Found for a missing object; without it, S3 returns 403 Access Denied, so that nobody can probe your bucket for key names.

How do I check if an S3 object exists?

Use aws s3api head-object with the bucket and exact key. A 200 means it exists; a 404 means it does not; a 403 means you lack access or list permission. In code, catch a 404 status on HeadObject rather than a NoSuchKey error, because HEAD responses have no error body.

Why can I see the file in the S3 console but still get NoSuchKey?

The console shows a folder tree drawn from the key's slashes, and it hides details such as a leading slash, a capital letter, a space, or a delete marker. Copy the exact key from the object's properties page or a CLI listing and compare it with your request.

Does S3 have folders?

No. S3 is flat; "folders" are prefixes in object keys. The console creates a zero-byte object ending in a slash when you click Create folder, but uploads through the CLI or SDKs do not, and a GET on a prefix alone returns NoSuchKey.

Why does NoSuchKey happen after I deleted and re-uploaded a file?

In a versioned bucket, the delete added a delete marker. If the re-upload used a slightly different key, the original key still resolves to the marker and returns 404 with the x-amz-delete-marker: true header. List the object versions to see both.

How do I recover a file that returns NoSuchKey in a versioned bucket?

Run aws s3api list-object-versions with the key as the prefix, find the delete marker with IsLatest true, and delete that marker by its version ID. The previous version becomes current again without re-uploading.

Why does my S3 key with spaces fail?

Different tools encode a space as a plus sign, as %20, or leave it as is, and S3 stores whatever bytes it received. A request that encodes the space differently from the upload will not match. List the bucket to see the stored key, and avoid spaces in new keys.

What is the difference between NoSuchKey and NoSuchBucket?

NoSuchKey means the bucket exists but the object key does not. NoSuchBucket means the bucket name itself was not found in that account or Region. Both are 404s, but the fixes differ: check the key for the first, the bucket name and Region for the second.

Why does CloudFront show AccessDenied when S3 says NoSuchKey?

When CloudFront asks S3 for a key that does not exist, S3 returns NoSuchKey and CloudFront masks it as a generic AccessDenied so the bucket's contents are not leaked. A folder URL such as /about/ is the usual trigger, fixed with a viewer-request function that appends index.html.

Why does my S3 static website return NoSuchKey for a folder?

On the website endpoint, a request for photos/ needs an object named photos/index.html, and every folder needs its own index document with the same case-sensitive name. Without it, S3 returns an error.

How do I catch NoSuchKey in boto3?

Catch s3.exceptions.NoSuchKey around get_object, or catch botocore.exceptions.ClientError and check e.response["Error"]["Code"]. For head_object, check the HTTP status code 404 in ResponseMetadata, because the HEAD response carries no error code.

How do I catch NoSuchKey in the AWS SDK for JavaScript v3?

Import NoSuchKey from @aws-sdk/client-s3 and test err instanceof NoSuchKey or err.name === "NoSuchKey". For HeadObjectCommand, test err.$metadata.httpStatusCode === 404 instead.

Can a leading slash in the key cause NoSuchKey?

Yes. /photos/a.jpg and photos/a.jpg are two different keys. Code that builds keys from URL paths usually adds the slash by accident; strip it before calling S3.

Why does aws s3 cp say 404 Not Found instead of NoSuchKey?

The high-level aws s3 commands check the object with a HEAD request first, and HEAD returns only a status code, so the CLI reports 404 when calling HeadObject. The aws s3api get-object command shows the full NoSuchKey error.

Does a lifecycle rule cause NoSuchKey?

It can. Expiration rules delete objects after the configured number of days, silently. In a versioned bucket the expiration adds a delete marker, which you can see with list-object-versions and remove if the deletion was unintended.

πŸ“š ALSO READ

Where to go next, from the CloudFront and website versions of this error to its S3 siblings:

📌 Bookmark this; the listing command and the 403-vs-404 rule solve this error faster than any amount of policy editing.

By the time the shop closed, every photo was back, and Jake had learned the one habit that prevents this error for good: when S3 says a key does not exist, believe it, and go look at the keys it does have. "It wasn't lying," he said. "It just reads better than I type." Ethan pointed out that S3 reads bytes, and that is both the problem and the whole fix.

📌 If you keep one line from this page

List the bucket, compare the key byte for byte, and remember that a 403 without list permission is a 404 in disguise.

Check for a leading slash, a capital letter, a plus sign, a folder that is not an object, and a delete marker, in that order.

Revision note. Written October 9, 2026, from the S3 API reference and the versioning, naming and CloudFront documentation. If a broken image or a failed job brought you here, the listing command is the fastest way back.

Related