What is a presigned URL in AWS - sharing a private file safely

Logeshwaran.C

A presigned URL is a normal-looking web link that lets someone download or upload one specific file in a private Amazon S3 bucket, for a limited time, without ever having an AWS account or password of their own. The link carries a hidden, time-stamped signature instead of a login. Here's the part almost nobody expects: that link is not tied to the person you emailed it to. It's tied to nothing but time and the permissions of whoever created it — so if it lands in the wrong inbox, S3 has no way to tell the difference between your customer and a stranger.

⚡ Quick Answer

Sharing a file for download? S3 console → open the object → Object actionsShare with a presigned URL. No code, expires in 1 minute–12 hours.

Need it to last longer, or want to script it? Run aws s3 presign s3://your-bucket/your-file --expires-in 3600 from the AWS CLI. Up to 7 days.

Letting someone upload TO you? That's a different presigned URL (a PUT one, not a GET one) — see the upload section below.

Whichever route, the file stays private the whole time. Only someone holding that exact link can touch that one object, and only until it expires. Full steps and every gotcha — including the KMS-encryption trap almost nobody warns you about — are below.

What Is a Presigned URL..?

Jake runs a small phone repair shop, and last month a customer's insurance company asked him to send over a 2 GB video of a "before and after" repair as proof of the damage claim. Jake's first instinct was to just make the video public on his website's S3 bucket and paste the link into an email. Ethan stopped him before he hit send.

‍♂️ Jake's Reality Check

"It's just one video. Why not make the whole folder public for five minutes, send the link, then flip it back to private?"

Because "flip it back" is a manual step, and manual steps get forgotten. A public bucket stays public until someone remembers to undo it — and search engines and scanners index public S3 URLs faster than most people realize. A presigned URL does the opposite: the bucket never becomes public at all. The link itself expires on its own, on a clock you set when you create it.

By default, every object you put into Amazon S3 is private — only the account that owns it can touch it. A presigned URL is how the owner (or anyone the owner has given permission to) temporarily hands that access to someone else, without touching the bucket's public settings at all. Amazon's own documentation describes it plainly: the object owner may share objects with others by creating a presigned URL, and the credentials baked into that URL are the credentials of whoever generated it — not the person clicking the link.

That last detail is the one worth sitting with. The link doesn't authenticate the person using it — it authenticates itself. Ethan put it to Jake this way: "Think of it less like a key you hand someone, and more like a coat-check ticket. The ticket doesn't know or care who's holding it. Whoever presents it at the counter within the time window gets the coat. Photocopy the ticket and leave it on a park bench, and whoever finds it gets the coat too." Jake pushed back — "so anyone who forwards my email gets the file?" — and Ethan didn't soften it: "Yes. That's exactly the trade you're making for not needing them to have an AWS login."

Technically, when you generate a presigned URL, Amazon S3 attaches four things to it behind the scenes: which bucket, which object key (the file's path and name inside the bucket), which HTTP method (GET to download, PUT to upload, HEAD to just check the file's metadata), and an expiration window. All of that gets baked into a cryptographic signature appended to the URL as a long string of query parameters. When someone clicks the link, S3 recalculates that signature on its end and checks it matches — if the URL has been altered in any way, or the clock has run out, the request is refused before anything is served.

You'll also see this called a "pre-signed URL" with a hyphen — same thing, Amazon's own pages use both spellings depending on which guide you're reading. Don't let that trip you up when you're searching AWS's documentation for more detail.

Sharing a File, or Asking Someone to Send You One?

Before you generate anything, answer one question: which direction is the file moving?

Route 1 — You have the file, someone else needs it

This is a GET presigned URL. It's the one Jake used for the insurance video, and it's by far the most common reason people search for this topic — sharing an invoice, a contract, a large export, a video, a backup file, anything currently sitting in your bucket that another person needs to open or download.

Route 2 — Someone else has the file, and it needs to land in your bucket

This is a PUT presigned URL. It's what mobile apps and websites use when they let a customer upload a profile photo or a document directly to S3, without the app's own server ever having to handle the file in the middle. If you're building (or fixing) an upload feature, skip ahead to the upload section — the console can't generate this type of link for you the easy way, so you'll need a short script.

✅ Why this distinction matters

A presigned URL is only ever good for the one action it was created for. A GET link that lets someone download a file cannot be reused to upload a different one, and vice versa. If you're troubleshooting "my presigned URL isn't working," the first thing to check is whether you generated it for the right direction.

Sharing a File to Download: the S3 Console, No Code

If you just need to send one file to one person and you're comfortable clicking around the AWS Management Console, this is the fastest path — no terminal, no code editor, no SDK to install.

  1. Sign in to the AWS Management Console and open the Amazon S3 console.
  2. In the left navigation pane, choose General purpose buckets.
  3. In the bucket list, click the name of the bucket that holds the file.
  4. In the Objects list, select the specific file (the "object," in S3's terminology — it just means the individual file sitting in the bucket) you want to share.
  5. Open the Object actions menu and choose Share with a presigned URL.
  6. Set how long the link should stay valid.
  7. Choose Create presigned URL.
  8. The URL is copied to your clipboard automatically — there's also a copy button if you need it again.

Two limits are worth knowing before you rely on this method: when you generate the link through the console, the maximum expiration time you're allowed to set is 12 hours, and the object itself can be up to 5 TB. If you need a link that lasts longer than half a day, or you want to generate dozens of these on a schedule, the console isn't the tool — move to the CLI or an SDK, both covered next.

 What changed between versions

  • Before: presigned URLs for any AWS Region worked the same regardless of when the Region launched.
  • Now: any AWS Region launched after March 20, 2019 requires you to explicitly specify both the Region and its endpoint URL when generating a presigned link with the CLI — the older, simpler command silently fails for those newer Regions.
  • What that means for you: if you're getting a confusing error and your bucket lives in a newer Region (Cape Town, Jakarta, Hyderabad, and similar recent additions), that's very likely why — see the CLI example below.

Sharing a File to Download: the AWS CLI and SDKs

The CLI (Command Line Interface — a way of controlling AWS by typing commands into a terminal window instead of clicking through a website) gets you the same result as the console, but with two big advantages: it can be scripted, and it can set an expiration of up to 7 days instead of the console's 12-hour ceiling.

The command looks like this:

aws s3 presign s3://your-bucket-name/your-file.pdf --expires-in 604800

--expires-in is written in seconds, so 604800 is 7 days, 3600 is 1 hour, and 86400 is 24 hours. If your bucket lives in a Region launched after March 2019, add the Region and endpoint explicitly:

aws s3 presign s3://your-bucket-name/your-file.pdf --expires-in 604800 --region af-south-1 --endpoint-url https://s3.af-south-1.amazonaws.com

If you're building this into an application rather than typing it by hand, the AWS SDKs give you the same capability in code. Here's the shape of it in Python, using Boto3 (Amazon's official Python toolkit for talking to AWS services):

import boto3

s3_client = boto3.client("s3")

url = s3_client.generate_presigned_url(
    ClientMethod="get_object",
    Params={"Bucket": "your-bucket-name", "Key": "your-file.pdf"},
    ExpiresIn=3600
)
print(url)

Once you have the URL, downloading it is as simple as pasting it into any browser, or if you're testing from a terminal:

curl -X GET "your-generated-presigned-url" -o "downloaded-file.pdf"
Method Max expiration Best for
S3 console 12 hours A single, one-off share, no code needed
AWS CLI 7 days* Quick scripted links, terminal-comfortable users
AWS SDK (any language) 7 days* Building the link-generation into your own app
AWS CloudShell 7 days* A one-off script, but no local install at all

*7 days applies when signing with a long-lived IAM user's access keys. If you sign with temporary credentials instead — an IAM role, an EC2 instance profile, or AWS STS — the real ceiling is however long those credentials themselves last, even if you type in a longer number. More on that shortly.

One more thing worth knowing if you're uploading or downloading large files: presigned URLs support checksums (a checksum is a short fingerprint calculated from a file's contents, used to prove the file wasn't corrupted or tampered with in transit — like a seal on a package that breaks if anyone opens it early). Presigned URLs signed with the older Signature Version 2 only support MD5 checksums, while URLs signed with the current Signature Version 4 support a much wider set — CRC32, CRC32C, SHA-1, SHA-256, SHA-512, MD5, and a few newer high-speed algorithms. If your upload workflow cares about verifying integrity, make sure you're signing with Version 4 and passing the matching checksum header.

Letting Someone Upload Straight Into Your Bucket

Ethan's favorite use of this feature has nothing to do with sending files out — it's letting customers send files in, without ever giving them an AWS login. Say Jake wants customers to upload a photo of their broken phone screen through his shop's website, straight into his S3 bucket, without his server having to babysit the file transfer in the middle.

The Python shape is nearly identical to the download version, just with put_object instead of get_object:

import boto3

s3_client = boto3.client("s3", region_name="us-east-1")

url = s3_client.generate_presigned_url(
    ClientMethod="put_object",
    Params={
        "Bucket": "your-bucket-name",
        "Key": "uploads/customer-photo.jpg",
        "ContentType": "image/jpeg"
    },
    ExpiresIn=1000
)
print(url)

Whoever receives that URL can then upload the actual file with a single PUT request:

curl -X PUT -T "/path/to/local/photo.jpg" -H "Content-Type: image/jpeg" "the-generated-presigned-url"

⚠️ What this actually breaks if you get it wrong

If a file already exists in your bucket under that exact object key, an upload made through a presigned URL silently replaces it — no confirmation prompt, no version warning unless you have S3 Versioning turned on separately. If you're generating upload keys, make them unique (a timestamp or a random ID works well) unless overwriting is exactly what you want.

The content type you specify when generating the URL has to match what's used in the actual upload request — if you tell S3 to expect image/jpeg and the upload arrives as application/octet-stream, the signature check fails and the upload is rejected. This trips people up constantly when they copy an example from one place and their upload code from another. Also worth knowing: once the upload finishes, the object belongs to whoever owns the bucket — not to the uploader — regardless of whose file it originally was.

How Long Should the Link Actually Stay Alive?

There's no single right number, but there's a wrong way to think about it. Jake's first instinct — "just make it last a week so I don't have to think about it again" — is exactly backwards. Every extra hour a presigned URL stays valid is another hour it can be forwarded, cached, screenshotted into a chat log, or found sitting in someone's Downloads folder.

✅ Why this is the sane default

Set the expiration to the shortest window that realistically covers how the link will be used. A one-time invoice download for a customer who's on the phone with you right now? 15 minutes is plenty. A large file for someone in a different time zone who might not open your email until tomorrow? 24–48 hours, not a week "just in case."

Amazon S3 checks the expiration at the moment the HTTP request is made, not at the moment the download finishes. Practically, that means if someone starts downloading a large file one second before the link expires, the download keeps running to completion even after the clock runs out. But if their connection drops partway through and they try to resume after expiration, the resume attempt fails outright — they'd need a fresh link.

Also worth knowing: a presigned URL isn't single-use by default. It can be used repeatedly by anyone who has it, as many times as they like, right up until the expiration time hits. If you need something closer to single-use behavior, that's not a setting on the URL itself — it requires extra logic on your side, like deleting or moving the object immediately after the first successful download, or tracking usage separately and revoking the underlying credentials once it's been used.

Who Is Actually Allowed to Create One

This is the part that trips people up most, so it's worth spelling out plainly: anyone with valid AWS security credentials can attempt to create a presigned URL, but for it to actually work, whoever generates it must already have permission to do the thing the link is for. Generating a URL doesn't grant new permissions out of nowhere — it just delegates the permissions the creator already holds, for a limited time, to whoever ends up with the link. AWS's own guidance on this is unambiguous: a presigned URL cannot provide access to a resource in a way that wasn't already granted to the principal that generated it.

Which credentials you sign with also decides how long the URL is allowed to live:

Credential type Real maximum expiration
IAM user (long-term access keys) Up to 7 days, with Signature Version 4
IAM role credentials Expires the moment the role session ends — even if you asked for longer
EC2 instance profile credentials Typically around 6 hours (the instance's own credential lifetime)
AWS STS temporary credentials Only as long as that temporary session lasts

Notice the pattern: temporary credentials can never outlive themselves, no matter what expiration number you type in when generating the URL. If you sign with a role that expires in one hour and ask for a 7-day presigned URL, you get a URL that dies in one hour anyway. Also — and this is the one people find genuinely surprising — if the credentials used to create the URL are revoked, deleted, or deactivated at any point, the presigned URL stops working immediately, even if the expiration time you originally set hasn't arrived yet. That's actually your emergency brake, and it's covered more in the "what this can't do" section below.

When the Bucket Is Encrypted: the SSE-KMS Trap

Here's a real question that catches people off guard the first time it happens: everything about your presigned URL checks out — the IAM permissions look right, the bucket policy allows s3:GetObject, the link hasn't expired — and it still fails with AccessDenied. Nine times out of ten, the bucket is encrypted with SSE-KMS (server-side encryption using an AWS Key Management Service key, rather than the simpler encryption Amazon manages entirely on its own), and there's a permission missing that has nothing to do with S3 itself.

‍♂️ Jake's Reality Check

"But I gave my app user full S3 permissions. Why is it still denying the download?"

Because encrypting a file with a KMS key adds a second lock on top of the S3 permission. S3 saying yes isn't enough — KMS also has to say yes to decrypting it, separately.

According to AWS's own documentation on server-side encryption with KMS keys, downloading a KMS-encrypted object requires kms:Decrypt permission on the key, and encrypting one during upload requires kms:GenerateDataKey. A multipart upload — the method S3 uses automatically for larger files — needs both. When someone sends a GET request through a presigned URL, S3 checks whether the IAM user or role that generated the URL is authorized to decrypt that object's key. If that permission isn't there, the request fails, even though the S3-level permissions look perfectly fine.

Action KMS permission needed on the key
Download (GET presigned URL) kms:Decrypt
Upload, single request (PUT presigned URL) kms:GenerateDataKey
Multipart upload (large files) kms:GenerateDataKey and kms:Decrypt

Two extra wrinkles catch people who've fixed the IAM policy and still can't work out why nothing changed:

  1. Check the KMS key policy, not just the IAM policy. AWS KMS keys have their own resource-based policy in addition to whatever IAM policy is attached to your user or role. Even a perfectly correct IAM policy won't help if the key's own policy doesn't allow that principal to use it.
  2. It applies even to Amazon's own managed key. If you're relying on the default AWS managed key (commonly shown as aws/s3) rather than a customer-managed key, you might assume it behaves like the simpler Amazon S3-managed encryption and needs no extra permission. It doesn't — as long as the encryption type is SSE-KMS at all, the KMS permission requirement applies regardless of who manages the underlying key.
  3. For cross-account sharing, both sides need access. If the bucket and the KMS key live in a different AWS account than the person receiving the presigned URL, the bucket policy must grant access to that account, the KMS key policy must separately grant that account's principal kms:Decrypt, and the requester's own IAM policy must allow access to both the bucket and the key. Missing any one of these three produces the same generic access-denied message, which is exactly why this scenario eats so much troubleshooting time.

If your bucket uses the simpler SSE-S3 encryption (Amazon's own managed keys, with no separate KMS permission layer), none of this applies — a presigned URL works the same way for encrypted and unencrypted objects in that case. This whole section is specific to SSE-KMS.

Sharing Across AWS Accounts

If Jake's shop used one AWS account for the website and a separate one for backups, and he needed to generate a presigned URL for an object that lives in the backup account's bucket, that's a cross-account scenario, and it changes what error messages you'll see. AWS's own troubleshooting documentation for access-denied errors notes that enhanced, more descriptive access-denied messages are only returned for requests within the same AWS account, or for cross-account requests where both accounts belong to the same organization set up in AWS Organizations. A cross-account request from outside that organization gets a generic, unhelpful "Access Denied" with none of the extra detail that would normally point you at the actual cause.

A separate detail that matters here: S3's Block Public Access settings, when enabled at the account or bucket level, block cross-account access to the bucket entirely — except for AWS's own service principals — while still letting people inside the owning account manage it normally. If a presigned URL generated by a different account is failing outright with no useful error, this setting is worth checking before anything else.

The bucket's Object Ownership setting also matters in cross-account setups, particularly for uploads. Depending on whether the bucket is configured as "Bucket owner preferred" or "Object writer," a bucket can end up holding objects actually owned by different AWS accounts, which affects who has permission to manage those objects afterward — including generating future presigned URLs for them.

Locking It Down Further: Signature Age and Network Rules

A presigned URL is, in AWS's own words, a "bearer token" — anything that carries it can use it. If your use case is more sensitive than sharing a repair video (medical records, financial statements, anything with real consequences if it leaks), there are two documented ways to tighten the screws beyond just shortening the expiration.

Cap how old a signature can be, at the bucket level

You can write a bucket policy that rejects any presigned request where the signature is older than a set number of milliseconds, using the s3:signatureAge condition key. This is a backstop against someone generating a 7-day link and then sitting on it — even if the link technically hasn't expired yet, the bucket itself can refuse it after, say, 10 minutes:

{
  "Version":"2012-10-17",
  "Statement": [
    {
      "Sid": "Deny old presigned signatures",
      "Effect": "Deny",
      "Principal": { "AWS": "*" },
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::your-bucket-name/*",
      "Condition": {
        "NumericGreaterThan": { "s3:signatureAge": "600000" }
      }
    }
  ]
}

Restrict where the link can be used from

Using IAM policies, you can require that requests — including presigned URL requests — originate from a specific network range. If you're accessing S3 over its public endpoint, the condition key is aws:SourceIp; if you're going through a VPC endpoint (a private network path into AWS that never touches the public internet), it's aws:SourceVpc or aws:SourceVpce. This restriction can sit on the IAM principal generating the link, on the bucket itself, or both, and it applies to all S3 access from that source, not just presigned URLs.

Automating It: CloudShell and Generating Links at Scale

If you don't want to install the AWS CLI locally at all — maybe you're on a locked-down work laptop, or you just need to do this once from a browser — AWS CloudShell is worth knowing about. It's a browser-based terminal, built into the AWS Management Console, with the CLI already installed and your console credentials already available to it. AWS publishes a dedicated tutorial for exactly this task, and the setup is short:

  1. Make sure you have an IAM user with the AWSCloudShellFullAccess policy attached, so you're allowed to open CloudShell in the first place.
  2. Confirm the IAM permissions needed to create a presigned URL are also attached — the same s3:GetObject (or s3:PutObject for uploads) permissions covered earlier in this post.
  3. Open CloudShell from the AWS Management Console — it's the terminal icon in the top navigation bar.
  4. Run the same aws s3 presign command shown earlier in this post, directly in the CloudShell terminal.

Past the one-off case, if you're generating presigned URLs regularly as part of a real application — a customer portal, a document-signing flow, a photo upload feature — the pattern almost always ends up as a small backend function: something receives a request from your app (a click, a form submission), checks that the requester is allowed to have that file, then calls generate_presigned_url on the server side and hands the resulting link back to the app. The presigned URL generation itself never happens on the client device, because doing that would mean shipping real AWS credentials inside a mobile app or a browser — which defeats the entire purpose of using presigned URLs to avoid handing out credentials in the first place.

The adjacent task worth planning for once this is running: rotating the credentials that sign these URLs on a schedule, the same way you'd rotate any other IAM access key, and updating your monitoring so an unexpected spike in presigned-URL-related 403 errors gets flagged instead of silently piling up in a log nobody reads.

What a Presigned URL Cannot Do — Said Plainly

This is the section most articles skip, and it's the one Jake actually needed most.

‍♂️ Jake's Reality Check

"Can I put a password on it? Can I see if the customer actually opened it?"

No, on both, out of the box. A presigned URL has no built-in password layer and no built-in "seen" receipt. Setting expectations here up front saves a lot of frustration later.

You cannot revoke one link individually. Once generated, a specific presigned URL can't be selectively cancelled while leaving other links from the same credentials untouched. Your only real levers, per Amazon's own documentation, are: let it run out naturally, delete or rename the underlying object so the link points at nothing, or revoke/deactivate the entire set of credentials that signed it (which kills every presigned URL made with those credentials, not just the one you're worried about).

You cannot see who used it, or how many times. S3 access logs and AWS CloudTrail will record that a request happened against that object, but neither one tells you it was specifically "the customer" versus "someone the customer forwarded it to." If usage tracking matters for your workflow, you need to build that separately — for example, generating a unique object key per recipient so each download shows up distinctly in your logs.

The signature itself is sensitive and shouldn't be logged in plain text. The long string of characters at the end of a presigned URL — the part after X-Amz-Signature= — is effectively the access token for that request. If your own web server, proxy, or analytics tool logs full request URLs (a lot of them do, by default), that signature ends up sitting in a log file readable by anyone with log access, for as long as those logs are retained — which can easily outlast the URL's own expiration window if logs are kept for months.

When It Doesn't Work: Every Error, By Symptom

Presigned URLs fail loudly and unhelpfully. Here's what each real error actually means.

Error What it actually means
403 Forbidden The IAM user or role that generated the link doesn't have the needed permission (like s3:GetObject), or a bucket policy is explicitly denying the request.
AccessDenied on a KMS-encrypted object S3 permissions are fine, but the KMS key permission (kms:Decrypt for downloads, kms:GenerateDataKey for uploads) is missing — see the SSE-KMS section above.
SignatureDoesNotMatch Usually a clock drift issue (your system clock isn't synced), a corporate proxy silently rewriting headers or query strings, or the URL got copied with a character altered or truncated.
ExpiredToken The temporary credentials used to sign the URL (not the URL's own expiration) have run out. Refresh the credentials and generate a new URL.
AccessDenied — HeadersNotSigned: if-range If your request signs the Range header (used for partial downloads), S3 also requires If-Range to be signed if it's present. Add it to the signed headers when generating the URL.

For SignatureDoesNotMatch specifically, work through this checklist in order: confirm your system clock is synced to a time server (even a few minutes of drift breaks the signature check), confirm you're using the URL exactly as generated with no whitespace or line breaks added by an email client, confirm you're not testing behind a corporate proxy that might be modifying the request, and when using curl, always wrap the URL in quotes — the query string contains characters like & that your shell will otherwise interpret as commands.

One more scenario Ethan sees constantly: someone generates a presigned URL from their laptop, tests it successfully, then hands the exact same link off to a teammate — and it still works fine for the teammate too, because (as covered above) the link isn't tied to a person. If it's failing for a specific person but working for you, the far more likely culprit is their network blocking the request, their client stripping query parameters, or the link having actually expired between when you generated it and when they clicked it.

When You've Outgrown Presigned URLs

If what you actually need is IP restriction baked into the link itself, an effective date as well as an expiration date, or a link that can grant access to many files at once through a single signed cookie, that's a different, related AWS feature: Amazon CloudFront's signed URLs and signed cookies. CloudFront is Amazon's content delivery network, and Amazon documents this explicitly as the path for restricting access to private content — documents, media, or business data — for cases where you need controls that plain S3 presigned URLs don't offer.

✅ The honest rule of thumb

If you're sharing a handful of files with people you already trust, on a short clock, a plain S3 presigned URL is simpler and does the job. If you're serving private content to a large or ongoing audience and need finer-grained controls, that's a sign to look at CloudFront's signed URLs and signed cookies as a separate, more involved setup — not something to bolt onto S3 presigned URLs after the fact.

Neither one solves the "no password, no download receipt" gap on its own — for that, you're building an authentication layer of your own in front of whichever option you choose.

Frequently Asked Questions

What is a presigned URL in simple terms?

It's a temporary web link that grants access to one specific private file in Amazon S3, generated using someone's AWS credentials, without requiring the person clicking the link to have AWS credentials of their own.

Is using a presigned URL the same as making my S3 bucket public?

No. The bucket and every other object inside it stay completely private. Only the single object the URL was generated for becomes reachable, and only until the link expires.

How long can a presigned URL stay valid?

From the S3 console, up to 12 hours. From the AWS CLI or an SDK signed with a long-term IAM user, up to 7 days. If you sign with temporary credentials (a role, an EC2 instance profile, or STS), the link dies when those credentials expire, even sooner if that's shorter.

Can a presigned URL be used more than once?

Yes. It can be used repeatedly, by anyone who has it, as many times as they like, right up until it expires. It isn't single-use unless you build that behavior yourself.

Who is allowed to create a presigned URL for my bucket?

Anyone holding valid AWS credentials can attempt it, but the URL only works if those credentials already have permission to perform the requested action on that object. Generating the link never grants new permissions on its own.

Can I revoke a presigned URL after I've already sent it?

Not that individual link on its own. Your real options are: wait for it to expire, delete or rename the object so the link points at nothing, or revoke the entire set of credentials used to sign it — which invalidates every presigned URL made with those credentials, not just one.

Does the person clicking the link need an AWS account?

No. That's the entire point of the feature — the credentials embedded in the URL belong to whoever generated it, so the person using it needs nothing more than the link and a browser or a tool like curl.

Can I password-protect a presigned URL?

Not natively. S3 has no built-in mechanism to require a password on top of a presigned URL. If that's a requirement, you'd need to build your own authentication step in front of the link, or send the link through a channel that already requires a login (a portal, an authenticated email, and so on).

Can I see who downloaded a file through a presigned URL?

Not who, specifically. S3 access logs and CloudTrail will record that a request was made against the object, but not the identity of the person clicking the link, since they never authenticated as themselves. If you need that visibility, generate a distinct object key per recipient so each one is distinguishable in your logs.

Why did my presigned URL stop working before its expiration time?

This almost always means the underlying credentials expired or were revoked before the URL's stated expiration arrived. Role sessions, EC2 instance profile credentials, and STS temporary credentials all have their own lifetime, and the presigned URL can never outlive that, no matter what number you set when generating it.

Why am I getting a 403 Forbidden error on a presigned URL?

Check that the IAM user or role that generated the URL actually has the required permission (such as s3:GetObject) for that action, and confirm the bucket policy isn't explicitly denying access to the object.

Why am I getting a SignatureDoesNotMatch error?

Common causes are a system clock that's out of sync, a corporate proxy modifying headers or query strings in transit, or the URL being altered slightly (extra whitespace from a copy-paste, for example). Verify all three, and make sure the request method, headers, and parameters exactly match what was used when the URL was generated.

What happens if someone uploads a file with the same name using a presigned URL?

Amazon S3 replaces the existing object with the newly uploaded one. There's no warning or confirmation step built in, unless you've separately enabled S3 Versioning on the bucket.

Can I generate a presigned URL from the S3 console without writing any code?

Yes. Open the object in the S3 console, use Object actions → Share with a presigned URL, set the expiration, and the link is copied to your clipboard. This method tops out at a 12-hour expiration and a 5 TB object size.

Should I use an S3 presigned URL or a CloudFront signed URL?

For occasional, short-lived sharing of individual files, a plain S3 presigned URL is simpler and gets the job done. If you need finer controls like an effective start date, IP restriction baked into the link, or access to many files through one signed cookie, that points toward Amazon CloudFront's signed URLs and signed cookies instead — a separate, more involved setup built specifically for serving private content at scale.

Why do I get Access Denied on a KMS-encrypted object even though my S3 permissions look right?

S3 permissions and KMS permissions are separate layers. Downloading a KMS-encrypted object requires kms:Decrypt on the key in addition to normal S3 read access; uploading requires kms:GenerateDataKey. This applies even when the bucket uses AWS's own managed key, and in cross-account setups it requires both the bucket policy and the KMS key policy to explicitly grant the other account access.

Revision note. Written September 2026.. This will need a look again if AWS changes the console's 12-hour cap or the CLI/SDK's 7-day ceiling. If you're staring at a link that expired at the worst possible moment, or an Access Denied error that makes no sense yet, take a breath — it happens to everyone, and the fix is almost always smaller than it feels right now, See you on next post, Happy learning..!

Related