S3 Presigned URL "Request Has Expired": The Real Expiry
An S3 presigned URL that returns Request has expired has passed its permitted signature time window, but increasing X-Amz-Expires is not always the fix. A SigV4 URL encodes its creation time in X-Amz-Date and its requested lifetime in X-Amz-Expires. The catch is that temporary credentials can expire first: a link signed in Lambda for seven days may stop working after the underlying role session ends. First inspect both query parameters, identify the signing credentials, and then check for a shorter bucket-policy signature-age restriction.
Jake runs a phone-repair shop and keeps customer repair reports in a private S3 bucket. In this illustrative story, he emails a download link on Friday and tells the customer it will work through the following week. The link opens on his computer immediately after he creates it, but the customer reports that it has expired about an hour later.
This failure is especially frustrating because the file is still in S3, the object permissions may not have changed, and the URL itself visibly says it was given a much longer lifetime. A link is not independent of the authority that signed it. That distinction drives the diagnosis and keeps you from changing bucket policies unnecessarily.
Jake: "I put seven days on the link. How can it stop after one hour?"
Ethan: "The link cannot outlive the credentials that signed it. Seven days describes the requested signature lifetime, not a promise that the signer will remain valid."
What “Request has expired” means in Amazon S3
When the browser shows Request has expired, inspect the XML response and the request URL before changing anything else. This phrase points to a time-based rejection of the signed request. The failure does not, by itself, say the object was deleted or that the bucket suddenly became public or private.
Presigned URLs are signed HTTP requests. Their query parameters carry enough information for S3 to calculate whether the request is still within its authorized window. Anyone possessing a valid download URL can use it as a bearer credential for the permitted operation while the relevant permissions and signing credentials remain valid.
The signature binds important request details, including the method, host, path, selected query parameters, and signed headers. Editing those pieces after generation can change the signature and produce a different error. Copy the whole original URL, including everything after the question mark, before diagnosing its time fields.
A link may fail with an XML code such as AccessDenied, ExpiredToken, or SignatureDoesNotMatch depending on the failing check and request context. The text Request has expired is not a guaranteed wrapper for every form of early expiration. Your diagnostic procedure should keep the exact code and message rather than treating every HTTP 403 as the same problem.
S3 evaluates validity when an HTTP request begins. A download already underway may complete after the nominal deadline, but a later retry or range request needs a still-valid URL. This explains why a large download can appear to work past the deadline even though opening the same link again fails.
Read X-Amz-Date and X-Amz-Expires without guessing
If you are staring at a very long URL, look first for X-Amz-Date and X-Amz-Expires. The date uses UTC in the form YYYYMMDDTHHMMSSZ, and the expiry value is a duration in seconds. Add the duration to the encoded date to get the nominal deadline.
A value of 3600 represents one hour, 86400 represents one day, and 604800 represents seven days. These values describe time windows, not object retention periods. They also cannot extend the lifetime of a revoked, deleted, or expired signing credential.
An example URL might include X-Amz-Date=20261015T120000Z and X-Amz-Expires=3600. Its nominal deadline is October 15, 2026 at 13:00 UTC. If the underlying credential expires at 12:30 UTC, the URL can cease working at 12:30 even though the two visible time fields still add up to 13:00.
You may also see X-Amz-Credential, X-Amz-Algorithm, X-Amz-SignedHeaders, and X-Amz-Signature. A URL signed with session credentials commonly contains X-Amz-Security-Token. That token is sensitive; keep complete presigned URLs out of public tickets, analytics logs, and screenshots.
Parsing the visible fields helps with the nominal deadline, but it cannot recover the precise expiration of hidden temporary credentials from the URL alone. It also does not reveal every bucket policy condition that might deny the request. Treat the parsed time as an upper bound, not an unconditional guarantee.
from datetime import datetime, timedelta, timezone
from urllib.parse import urlsplit, parse_qs
url = input("Paste the presigned URL: ").strip()
params = parse_qs(urlsplit(url).query)
issued = datetime.strptime(params["X-Amz-Date"][0], "%Y%m%dT%H%M%SZ")
issued = issued.replace(tzinfo=timezone.utc)
seconds = int(params["X-Amz-Expires"][0])
nominal_deadline = issued + timedelta(seconds=seconds)
print("Signed at UTC:", issued.isoformat())
print("Requested lifetime seconds:", seconds)
print("Nominal deadline UTC:", nominal_deadline.isoformat())
print("Temporary-token parameter present:", "X-Amz-Security-Token" in params)
print("Caution: credential and policy expiry may be earlier.")
The real maximum expiry for S3 presigned URLs
You can request up to seven days, or 604800 seconds, with AWS Signature Version 4 presigned URLs generated by the CLI or supported SDKs. The underlying SigV4 signing-key lifetime enforces that ceiling. Passing a larger requested value is not a reliable method for building a longer-lived link.
The S3 console provides a shorter selection range: one minute to twelve hours for links created there. This explains why two team members can see different expiry choices even when they are linking to the same object. The relevant creation method matters as much as the object itself.
Temporary credentials frequently make the practical maximum much shorter than seven days. A role session may have hours or less remaining when your application signs a URL. The link then becomes unusable when that session ends, regardless of the requested X-Amz-Expires value.
You might read advice to increase the IAM role maximum session duration to twelve hours. That setting does not magically renew an already signed URL or guarantee a particular Lambda execution role credential lifetime. For links needed days later, design a mechanism to issue a new short-lived URL or use a distribution service better suited to longer-lived access.
The shortest applicable restriction wins: nominal expiry, signing-credential expiry, explicit policy denies, and relevant signature-age rules can all narrow access. Asking only whether seven days is supported misses the bigger question of whether the signer will still be authorized to serve the request.
| How it was created | Requested maximum or behavior | Practical restriction |
|---|---|---|
| S3 console | 1 minute to 12 hours | Signer permissions and credential validity still apply |
| AWS CLI / SigV4 SDK | Up to 604800 seconds (7 days) | Temporary credentials may end sooner |
| Lambda execution role | Can request 7 days in code | Role credentials often expire far earlier |
| CloudFront signed URL | Can support substantially longer terms | CloudFront signing and access policy must be configured |
Here is where most of you will find your answer, so slow down for a moment. If your link was signed by a Lambda function, an ECS task, or any assumed role, the lifetime you typed is not the lifetime you got. You are about to see why, and the fix costs you nothing.
Why Lambda and IAM role URLs expire early
When your Lambda signs an S3 link, the SDK normally discovers credentials from the function execution environment. Those are temporary role credentials, not a permanent key with a seven-day guarantee. Generating a seven-day URL with a role session that has one hour left does not lengthen that session.
The URL may contain a session token encoded in X-Amz-Security-Token. That token is one strong reason to investigate credential expiration first. A token-bearing URL can look perfectly ordinary to a customer, who has no way to see the server-side expiration schedule.
You cannot repair an expired link simply by changing its X-Amz-Expires value in a text editor. Changing a signed query parameter invalidates the signature. Instead, invoke the trusted signing service again and deliver a newly generated URL backed by currently valid credentials.
An AWS Lambda function is a good place to issue short-lived download links in response to an authenticated customer request. It is usually a poor place to issue seven-day links that you expect to survive without renewal. Keep authorization in your application, then generate a fresh URL only when a user is ready to download.
If the actual role session duration is unknown, leave the specific number of hours out of your promise. Measure the signing and failure times, inspect the execution identity, and check whether the error mentions an expired token. AWS rotates temporary role credentials; building customer promises around an assumed rotation timetable is fragile.
Jake: "Can I just set the expiry to a huge number in my Lambda code?"
Ethan: "No. The maximum SigV4 window is seven days, and the role session can end much sooner. Give customers a fresh link when they request the file."
The shortest diagnostic path from a failed link to its cause
If a customer sends you a failed URL, preserve the complete HTTP response code and error body in a secure place. Next, identify which exact URL they used; applications sometimes regenerate links and then cache or email an older one. Keep full signed URLs out of any widely accessible logging system.
First read the visible date and expiry fields. If the current UTC time is later than their sum, the nominal link has expired and a new one is expected. If the nominal deadline lies in the future, move to temporary credentials, clock differences, and signature-age policy restrictions.
Next check whether the link was signed by a Lambda role, ECS task role, EC2 instance profile, assumed role, or other temporary principal. Try issuing a new link with that same approved application path. If the replacement works while the old one fails, the evidence narrows the problem to time, credential validity, or a policy that treats signature age differently.
Then verify the object using an authorized CLI identity. A head-object operation with appropriate permissions can help establish that the key exists, but a 403 result may remain ambiguous when the caller lacks permission to list the bucket. A 403 on a missing key is not proof of expiration.
Finally compare the same URL from a separate browser or network without changing any query parameters. If one client fails and another succeeds at the same time, investigate proxies, URL rewriting, request method, signed headers, and network-based bucket conditions. Treat apparent user-specific behavior as evidence about the request path, not necessarily about the URL clock.
- Preserve the exact S3 XML error and the HTTP status without sharing the secret signature publicly.
- Decode
X-Amz-DateandX-Amz-Expires; compare their sum to current UTC. - Determine whether the signer used temporary credentials, especially a Lambda or assumed role.
- Inspect bucket and access point conditions such as
s3:signatureAge. - Verify the bucket, Region, and object key using an authorized identity.
- Create a new link without modifying its query parameters and test the actual customer request path.
| Observed result | What it suggests | Next check |
|---|---|---|
| Request has expired | Nominal signature window ended | Read X-Amz-Date and X-Amz-Expires |
| ExpiredToken or token-related access failure | Underlying temporary credential no longer valid | Signer role and issuance age |
| AccessDenied before nominal deadline | Policy restriction or authorization failure | s3:signatureAge and resource policies |
| NoSuchKey | Requested object key absent or wrong | Exact key and object version |
| SignatureDoesNotMatch | Signed request altered or mismatch | Host, method, headers, query and clock |
Correct clock skew on the machine that signs URLs
If a brand-new URL appears expired immediately, check the signing machine clock. SigV4 requests contain a signing timestamp, and a machine substantially out of sync with UTC can generate a time window that appears old or otherwise invalid. The fix is to correct time synchronization and sign again.
A developer laptop with a stale clock can create misleading tests even if AWS itself is healthy. Containers and instances generally inherit clock behavior from their environment, so focus on the machine or workload performing the signature. The client downloading an ordinary presigned URL does not choose the X-Amz-Date already embedded in it.
Compare UTC time on the signing host with an accurate time source. On Windows, inspect the system time and synchronization service; on Linux, a command such as timedatectl status can show the local clock and synchronization state. If the clock is wrong, fix the underlying synchronization rather than hand-editing the URL.
Time problems may also cause errors that are not worded exactly as Request has expired. The signature can be not yet valid or otherwise fail AWS request-time validation. Capture the returned code before assigning a diagnosis, particularly when a URL fails the moment it is created.
After fixing the clock, generate a completely new signature. The old URL still carries its old signing time and signed query fields. Reissuing is inexpensive and provides a clean point from which to measure the requested lifetime.
Find bucket policies that shorten the URL with s3:signatureAge
If a URL requests a seven-day lifetime but stops sooner while its temporary credentials remain valid, examine policy restrictions. The s3:signatureAge condition key can deny requests once a signature is older than a specified age. Its value is measured in milliseconds, not seconds.
For example, a policy that denies presigned requests with a signature older than ten minutes uses a threshold of 600000 milliseconds. This can reject an otherwise valid one-hour URL after ten minutes. The URL still displays X-Amz-Expires=3600, which makes the early failure easy to misinterpret.
An explicit deny in a bucket policy cannot be bypassed by granting additional IAM access to the signer. Adjust such conditions only if the security owner agrees that a longer window is appropriate. In many workflows the better fix is generating short-lived URLs at the moment of use.
Other policy conditions can restrict source IPs, VPC endpoints, TLS behavior, principals, or object tags. These restrictions can make a valid URL fail for one customer but succeed for another. Review all applicable identity policies, bucket or access-point policies, and organization-level controls before treating the issue as a pure expiration problem.
When a policy is the cause, the exact returned error may be AccessDenied rather than Request has expired. That distinction matters because a time calculation alone cannot tell you whether a policy denied the request. Investigate the actual message and the effective restrictions together.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyOldPresignedRequests",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"NumericGreaterThan": {
"s3:signatureAge": "600000"
}
}
}]
}
The example policy is deliberately restrictive and should be reviewed before applying it. It blocks matching requests whose signature age exceeds the threshold; it does not issue or renew URLs. If your business promises longer download windows, align the customer experience with this condition rather than weakening a security control unnoticed.
From here on you get working commands rather than theory. Pick the one that matches your tooling, run it with your own bucket and key, and compare the URL you get with the one that failed. You will often spot the difference in the first line of the query string.
Generate an S3 presigned download URL with AWS CLI
If you prefer the terminal, use aws s3 presign with the complete S3 URI, the bucket Region, and an explicit lifetime in seconds. The CLI uses credentials selected by your active profile or environment. That choice determines which identity is authorized to read the object and whether the signed URL can outlive the credential session.
The default CLI presign lifetime is 3600 seconds. For predictability, provide --expires-in even when you want exactly one hour. The maximum permitted duration is 604800 seconds, but only when the signing credentials remain valid for that long and other policies allow it.
Use aws sts get-caller-identity to identify the current account and signing principal before generating a link. This does not expose a temporary credential expiration timestamp, but it can reveal that you are using an assumed role rather than the profile you expected. Confirm the S3 bucket Region as well.
Test the returned URL using an HTTP GET exactly as generated. Quote the full URL in shells so ampersands are not interpreted as command separators. Keep it out of shared logs and ticketing systems, because whoever holds the link can reach the object.
If a local long-lived profile produces a link that survives while the identical command run under an assumed role fails early, the difference is a strong clue. It is not a reason to embed long-lived access keys in application code. Prefer a renewable delivery workflow for customers and temporary credentials for the application.
aws sts get-caller-identity
aws s3 presign \
s3://amzn-s3-demo-bucket/reports/repair-1001.pdf \
--region us-east-1 \
--expires-in 3600
# PowerShell single-line equivalent:
aws s3 presign s3://amzn-s3-demo-bucket/reports/repair-1001.pdf --region us-east-1 --expires-in 3600
Generate and validate a boto3 presigned URL in Python
When your application uses Python, the Boto3 S3 client provides generate_presigned_url(). Pass get_object, the exact bucket and key, and an explicit ExpiresIn value. The generated URL relies on the credentials resolved by the client at signing time.
For predictable SigV4 behavior, configure the client with Config(signature_version="s3v4"). The Region should match the bucket. A Region or endpoint mismatch can produce a redirect or signature-related error that looks unrelated to your application logic.
The SDK can construct a URL without fetching the object first, so successful URL generation does not prove that the object exists or that the signer has ongoing permission to read it. Test the complete download flow with an authorized object and a real HTTP GET. Include this distinction in deployment tests.
Use one reusable signing function with a deliberately short default. Record issuance time, requested expiry, key identifier, and non-sensitive request metadata in application logs. Leave the complete signed URL and its session token out of your records, particularly where operators or analytics vendors can see them.
If you need a link that will work at a later date, store the object key and customer authorization in your own application rather than the presigned link itself. When the customer opens their authenticated download page, request a new URL. This sidesteps expired Lambda credentials without exposing permanent AWS keys.
import boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
region_name="us-east-1",
config=Config(signature_version="s3v4"),
)
url = s3.generate_presigned_url(
ClientMethod="get_object",
Params={
"Bucket": "amzn-s3-demo-bucket",
"Key": "reports/repair-1001.pdf",
},
ExpiresIn=900, # 15 minutes
HttpMethod="GET",
)
print(url)
Generate presigned URLs with the JavaScript SDK v3
If your application is written in Node.js, use the modular AWS SDK for JavaScript v3. The packages @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner provide the S3 command and signing helper. This avoids relying on the older SDK v2 API shape.
The getSignedUrl() helper accepts an S3 client, a command, and an options object containing expiresIn. Specify the lifetime in seconds rather than assuming a default. The client obtains credentials from its configured provider chain, including temporary execution-role credentials in Lambda.
A presigned GetObjectCommand is intended for a download. If you later sign a PutObjectCommand for upload, remember that signed request headers and content-type expectations can differ. A request that changes the method or signed headers may fail with a signature mismatch rather than an expiration message.
For web applications, generate links on the server after authorizing the customer and return only the link needed for that operation. Signing role credentials belong on your server, not in browser JavaScript. The browser can use a presigned URL without direct access to your AWS credentials.
Always test the consumer path, not only the SDK call that generates the URL. A link can be syntactically well formed while pointing at the wrong key, using the wrong Region, or depending on a signer that will expire much sooner than the requested window.
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const client = new S3Client({ region: "us-east-1" });
const command = new GetObjectCommand({
Bucket: "amzn-s3-demo-bucket",
Key: "reports/repair-1001.pdf",
});
const url = await getSignedUrl(client, command, {
expiresIn: 900, // 15 minutes
});
console.log(url);
Worked example: a seven-day Lambda link dies after an hour
Imagine Jake emails a repair report at noon UTC and generates a link with ExpiresIn=604800. The S3 URL contains X-Amz-Date=20261015T120000Z and X-Amz-Expires=604800. The nominal expiry is noon UTC on October 22, 2026, exactly seven days after signing.
The report opens at 12:05 UTC. At 13:10 UTC, the customer tries again and receives a credential-related denial. The URL still carries the correct seven-day expiry fields, so merely checking X-Amz-Expires leads to the wrong conclusion. The signing Lambda used temporary execution-role credentials, and in this hypothetical example that role session became invalid at 13:00 UTC.
First, Jake checks that the object remains present by making an authorized S3 request. Second, he compares the original nominal deadline with the observed failure and checks the signer type. Third, he generates a fresh fifteen-minute URL from a new authorized request; the new link works because it uses currently valid credentials.
The permanent product fix is to store only the report key and permission mapping in the customer database. Jake emails a durable link to his own authenticated download page instead of an S3 bearer URL. When the customer opens that page, the server authorizes the request and generates a fresh S3 URL with a short validity window.
That approach preserves the customer promise to access the report next week without assuming an individual S3 signature must remain valid the whole time. The failure was in the delivery design, not a need to weaken bucket privacy. This is an illustrative scenario, not a claim about a specific observed Lambda role-session duration.
| Illustrative event | UTC time | What it proves |
|---|---|---|
| Original URL signed | October 15, 2026 12:00 | Nominal deadline October 22 at 12:00 |
| Successful GET | October 15, 2026 12:05 | URL initially usable |
| Example role session ends | October 15, 2026 13:00 | Temporary credential can become invalid |
| Customer retry fails | October 15, 2026 13:10 | Credential deadline beats URL deadline |
| Fresh 15-minute URL issued | October 15, 2026 13:12 | New approved signing path restores access |
Jake: "So I should email a link to my own download page instead?"
Ethan: "Yes. Your page can authenticate the customer and make a fresh S3 URL whenever they need it. That keeps the bucket private without depending on a week-old role token."
Tell an expired link from a missing object key
If the URL returns NoSuchKey, the requested object key does not exist at the target location for that request. This differs from a signature that has passed its time window. Check the full key, case, prefixes, URL encoding, object version, and whether a lifecycle process moved or removed the object.
The visible bucket and key in a URL can look nearly right while referring to a different resource. S3 object keys are case-sensitive, so Reports/file.pdf and reports/file.pdf are different keys. Your application should retain the canonical object key rather than reconstructing it from a display filename.
S3 may return AccessDenied instead of NoSuchKey when the caller lacks permission to list the bucket. Therefore, a 403 alone cannot prove the object exists. Use a suitably authorized identity to inspect the exact object and isolate object-state issues from presigned-request issues.
An expired presigned URL does not delete or change the S3 object. If the file truly has been deleted, issuing a fresh URL for the same missing key will not restore it. Investigate versioning, replication, backups, and object lifecycle independently from URL signing.
For simple download workflows, validate object existence as part of an authenticated server request before returning the link. That gives customers a clear not-found message from your own application instead of a confusing raw S3 XML error.
aws s3api head-object \
--bucket amzn-s3-demo-bucket \
--key reports/repair-1001.pdf \
--region us-east-1
# An HTTP 403 from HeadObject can be ambiguous when permissions are limited.
Why the presigned link works for its creator but not others
If you can open the file in your AWS console but your customer cannot open the presigned URL, those are different authorization paths. Your console browser normally uses your signed-in AWS identity. The customer’s GET uses the permissions embedded in the presigned request along with the signing credential and applicable resource policies.
Sometimes the creator is not testing the same URL at all. One page may generate a fresh link every time it loads while an email contains an older static link. Record the issuance timestamp and a non-sensitive identifier for each generated link so you can distinguish the two cases.
Network-based restrictions are another reason two people see different results. A bucket policy that restricts source addresses, VPC endpoints, or other conditions can block a customer even when the URL has not expired. Check the actual HTTP error body rather than assuming every 403 is a lifetime problem.
Corporate security products and messaging platforms may rewrite or escape URL query strings. If a query parameter is removed, decoded differently, or reordered in an incompatible signing context, the resulting request can fail signature validation. Copy the original generated link without modifying any signed component and test it with an ordinary HTTP client.
For presigned uploads, signed headers such as Content-Type introduce another difference between your test and the customer’s request. Your client needs to send the headers covered by the signature. That problem is usually reported as a signature mismatch, so diagnosis begins with the exact error, not the user location alone.
Can you revoke an S3 presigned URL before its deadline?
An S3 presigned URL is not a separately managed resource with a simple delete-this-link API. Access depends on the current validity of its signing credentials and applicable policies. Disabling or revoking the signing credentials can invalidate links created with them, but that may affect other applications sharing the same identity.
You can also change object or bucket permissions to block relevant requests, introduce a restrictive signature-age policy, or remove the object. These approaches have different scopes and consequences. A bucket-wide explicit deny is a serious change that can break unrelated downloads.
For sensitive documents, a safer product-level pattern is a short-lived URL generated by an authorized application immediately before download. Your own application can revoke the customer’s eligibility so that no new URL is issued. Previously issued URLs can remain usable until their own applicable restrictions invalidate them.
If immediate revocation of an existing link is an essential requirement, plan for it when choosing architecture. A reverse proxy or application-controlled download endpoint may offer more direct per-request authorization than handing out longer-lived S3 bearer URLs. CloudFront signed URLs have their own key and policy controls, but not a universal one-click revocation mechanism for an individual issued URL.
Document the business effect of any revocation action before making it. Changing a shared access key or broad bucket policy to invalidate one customer link can disrupt every other client using that principal. Prefer narrowly scoped access designs instead of emergency credential changes whenever possible.
When CloudFront signed URLs are better than S3 presigned URLs
If customers need downloads many days or months after an email is sent, CloudFront signed URLs can be a better fit than long-lived S3 SigV4 URLs. CloudFront verifies URLs using configured trusted signing keys and expiration conditions. Its signed URLs can be set for substantially longer periods, including years, depending on your content-distribution policy.
The two systems have different signing mechanics. S3 presigned links depend on AWS request-signing credentials and their validity, while CloudFront signed URLs use private keys associated with trusted signers for a distribution. Moving to CloudFront requires a distribution and private-content controls; swapping the hostname in an S3 URL is not enough.
For private files stored in S3, configure the distribution so viewers use CloudFront and cannot simply bypass its authorization rules by opening the S3 origin directly. Use an origin access control or the relevant supported origin configuration. Then decide whether individual signed URLs or signed cookies better match your use case.
CloudFront signed URLs are useful when each file needs a distinct bearer link. Signed cookies can work better when a user should access multiple restricted files without changing every URL. The security team should define the validity window, who may issue signatures, and how keys are rotated.
CloudFront does not remove the need to protect issued links. Anyone holding a valid bearer URL can generally use it subject to its policy conditions. For highly sensitive or revocable access, pair the distribution design with appropriate application authorization and short expiration periods.
| Need | S3 presigned URL | CloudFront signed URL |
|---|---|---|
| Short-lived object download | Simple direct S3 GET | Useful but extra distribution setup |
| One-week link from Lambda role | May fail when role credentials expire | Independent CloudFront signing design |
| Long-lived private distribution | Seven-day SigV4 ceiling | Supports longer custom deadlines |
| Restrict many related files | Each S3 URL is object/request-specific | Signed cookies may suit multiple files |
| Immediate individual-link revocation | No dedicated link-delete API | Requires deliberate signing/access design |
Build customer links without exposing AWS credentials
When you create presigned URLs, treat the result as a bearer secret rather than an ordinary public product URL. The signed query string is sufficient to authorize the relevant operation while the signature and permissions remain valid. A copy in application logs, browser history, email forwarding, or third-party analytics may expose access to someone you did not intend.
Keep the bucket private, use least-privilege signing roles, and scope authorization checks to the customer and object. A link generator should not allow an untrusted user to submit an arbitrary bucket or key and have your role sign it. Instead, resolve the object from an application record that the authenticated user is permitted to access.
Choose expirations based on actual use. A browser download started from a customer portal may need only a few minutes; a delivery process with delays may need somewhat longer. Whatever duration you choose, account for a slower customer network and the possibility that a retry after expiration will need a fresh URL.
A presigned URL does not override an explicit deny or grant more access than its signer is permitted to exercise. If the signer loses authorization, the URL may stop working. This property is useful for defense in depth but is another reason not to promise a hard deadline based only on X-Amz-Expires.
If your team must troubleshoot links, redact the signature, security token, and other sensitive query values before placing samples in a public issue tracker. Retain the date, expiry, Region, error code, and non-sensitive request identifiers. Those are usually enough to reason about timing without giving someone a reusable download credential.
When you generate URLs on several servers, make sure every instance uses synchronized UTC time and the same Region configuration. A queue consumer that signs a URL today and sends it in an email tomorrow can lose one day of usable lifetime before the customer sees it. Track both the creation time and the delivery time, because they are not interchangeable. If an email workflow introduces a substantial delay, sign at delivery or let the customer request a fresh link instead.
Jake: "What if my email service waits until the morning to send the report?"
Ethan: "Then the link starts aging before the customer receives it. Generate it as close to download time as your workflow allows."
Another subtle failure appears when a download manager sends multiple range requests. One successful range GET does not authorize future range GETs after the link's deadline. That may make a large file look partially available when the connection is interrupted near expiration. Rather than extending every link for days, provide a way to request a replacement URL when the client reconnects, and make sure the same object key is used across attempts.
Jake: "Can the customer resume a half-downloaded file with the old link?"
Ethan: "Only if the new HTTP request is still authorized. A refreshed URL is safer than assuming the interrupted connection's old signature will be accepted."
What to gather when the link still fails
If you have generated a fresh URL with valid credentials and it still fails, collect the precise HTTP response rather than continuing to adjust expiry values. Record the S3 error code, message, request ID, bucket Region, object key, operation, and approximate UTC time. Keep the full sensitive URL out of ordinary support attachments.
Check whether the same authorized identity can issue and use a URL for a harmless test object in the same bucket. If the test object works but one report fails, the problem may be object-specific, such as the key, encryption permissions, policy conditions, or object version. If both fail, focus on the signer and common configuration.
For customer reports, confirm your application is not sending an old cached URL. Mail templates, CDNs, frontend caches, and database rows can accidentally preserve a previously generated link. A timestamp tied to the application-generated delivery record helps distinguish that from an AWS expiration issue.
When asking AWS Support for help, include sanitized examples and the exact S3 request identifiers if available. Explain whether the link fails before its visible nominal deadline and whether the signer uses temporary credentials. Ask them to help isolate the specific rejection without requesting that general bucket security be weakened.
If the issue has a clear business impact, switch customers to your authenticated download portal while the deeper cause is investigated. A newly issued short-lived link is often a safe operational workaround. Keep duplicate access records and audit trails so you know which customer was authorized to receive each object.
Jake: "What should I tell the customer if the report link has already expired?"
Ethan: "Give them a secure place to request a fresh download. A dead link should be a recoverable step, not the end of their access to a report they are authorized to see."
A production checklist for links that expire predictably
You can prevent most surprises by standardizing how links are generated and delivered. Choose one backend implementation that sets an explicit expiry, chooses the correct bucket Region, and signs only authorized object keys. Then make the frontend and email workflow use that backend rather than inventing separate signing code.
For Lambda, assume that execution-role credentials can be shorter-lived than a requested multi-day URL. Design the long-term customer entitlement separately from the temporary download credential. Store the customer-to-document relationship, not a static presigned URL that your application cannot renew.
Validate the output in tests by parsing X-Amz-Date and X-Amz-Expires, then fetching a real test object with the exact generated URL. Include tests for expired sessions, unavailable keys, bad Regions, and headers if uploads are supported. A link that merely looks properly encoded is not sufficient evidence that downloads work.
Use application-level observability that distinguishes authorization failure, token expiration, nominal URL expiration, and object-not-found. Collapsing them all into a generic error saying the file is gone hides the one clue you need. Useful customer-facing messages should offer a renewal path when the entitlement is still valid.
Finally align the customer promise with the actual mechanism. If a document must remain downloadable for thirty days, preserve permission to request a new URL for thirty days. That is different from—and more reliable than—trying to keep the same S3 URL valid for thirty days.
- Authorize the customer for one canonical bucket/key record.
- Sign with currently valid role credentials and an explicit short lifetime.
- Verify Region, operation, query parameters, and signed headers.
- Apply least-privilege IAM permissions and review
s3:signatureAgecontrols. - Provide a reissue path rather than storing a week-old Lambda URL.
- Keep complete URLs and session tokens out of logs and public support systems.
Frequently asked questions about expired S3 presigned URLs
What is the maximum expiry for an S3 presigned URL?
A SigV4 S3 presigned URL generated through the CLI or an SDK can request up to 604800 seconds, or seven days. The S3 console offers durations from one minute to twelve hours. Temporary credentials and applicable policies may make the usable period shorter.
Why does my S3 presigned URL expire before X-Amz-Expires?
The credentials that signed the URL can expire first, particularly when an assumed role or Lambda execution role is used. A bucket policy using s3:signatureAge may also reject an older signature. Read the actual error and inspect the signer before increasing the URL lifetime.
What does X-Amz-Date mean in a presigned URL?
X-Amz-Date is the UTC timestamp used when the SigV4 request was signed. Combine it with X-Amz-Expires, which is measured in seconds, to calculate the nominal expiry. That calculation cannot reveal an earlier temporary-credential expiration.
How do I check when an S3 presigned URL expires?
Parse X-Amz-Date and add the number of seconds in X-Amz-Expires. Compare that timestamp with current UTC time. Remember that credential validity and policy restrictions may cause an earlier denial.
Why does a seven-day presigned URL from Lambda last one hour?
Lambda signs with temporary execution-role credentials, whose validity can end before the requested seven-day window. An illustrative one-hour failure is possible, but the exact role-session lifetime must not be assumed. Generate fresh URLs when the authorized customer requests them.
Can an expired S3 presigned URL be renewed?
You cannot extend an existing SigV4 URL by editing X-Amz-Expires because the signature would no longer match. Use valid credentials to generate a new link. A customer download portal can automate renewal after checking authorization.
Can an S3 presigned URL be revoked before it expires?
There is no dedicated delete-this-presigned-URL API. Revoking signing credentials or changing relevant permissions and policies can invalidate access, but those actions may affect other requests. Use short expiry and an authenticated reissue flow when granular control matters.
Does an expired presigned URL mean the S3 object was deleted?
No. URL expiration affects a signed request, not the stored object itself. Verify the actual key with an authorized S3 operation and investigate NoSuchKey separately.
Why does my presigned URL work for me but not someone else?
You may be using a fresh link or your console identity while the customer has an older URL. Network-based policies, missing signed headers, or URL rewriting can also cause differences. Compare the exact URL and HTTP error returned to each client.
How do I generate a presigned URL with AWS CLI for one hour?
Run aws s3 presign with the correct S3 URI, Region, and --expires-in 3600. Confirm the CLI profile is the intended signing identity. If the profile uses temporary credentials, the link can stop working sooner.
How do I set ExpiresIn in boto3?
Call s3.generate_presigned_url with ClientMethod set to get_object, Params containing Bucket and Key, and ExpiresIn set to a number of seconds. Configure SigV4 signing and the correct bucket Region. The credentials behind the client still have to remain valid.
How do I set expiresIn with AWS SDK for JavaScript v3?
Use getSignedUrl with an S3Client, a GetObjectCommand, and an options object such as { expiresIn: 900 }. The value is in seconds. The returned link inherits the validity constraints of the signing credentials and policies.
Can clock skew make an S3 presigned URL expire immediately?
Yes. A signing machine with an incorrect UTC clock can create a timestamp outside the permitted request window. Synchronize the host clock and generate a new URL rather than editing the old signature.
What is the difference between Request has expired and NoSuchKey?
Request has expired indicates a problem with the signed request time window. NoSuchKey refers to a requested object key that is not present. A lack of ListBucket permission may produce AccessDenied for a missing key, so verify the object using suitable permissions.
Can I use CloudFront signed URLs instead of S3 presigned URLs?
Yes. CloudFront signed URLs use CloudFront trusted signing keys and can support longer validity periods than S3 SigV4 URLs. Configure private-content delivery and prevent unintended direct access to the S3 origin.
Will an S3 download stop in the middle when its presigned URL expires?
S3 checks the URL expiration when the HTTP request starts, so a download already in progress may continue after the expiry time. A new or resumed HTTP request after expiration may be rejected. A renewal path helps customers restart interrupted downloads.
Jake can now send customers to a stable, authenticated report page rather than relying on one fragile week-old signature. When a link dies, you can explain whether its nominal expiry, signing credentials, or a policy restriction ended the download. Give the customer a fresh authorized link, keep the S3 bucket private, and make the renewal path part of the product instead of a manual support ticket.
📌 If you keep one line from this page
An S3 presigned URL lasts only as long as its shortest applicable expiry, credential lifetime, or access restriction permits.
Revision note. Written October 15, 2026, with the seven-day SigV4 ceiling and the twelve-hour console range in view. Time and tide wait for no one; a presigned link waits for the credentials behind it, and those leave first.