gotchajavascriptMajor
S3 presigned URL expires before client uses it due to clock skew
Viewed 0 times
@aws-sdk/s3-request-presigner v3.x
presigned urls3 uploadexpiryclock skewgetSignedUrls3-request-presignertemporary credentials
Error Messages
Problem
Presigned URLs generated server-side expire before the client can use them, returning 403 Request has expired. This happens when the server clock differs from AWS time or when the expiry window is too short for large uploads.
Solution
Generate presigned URLs with a generous expiry window (e.g., 15 minutes for uploads, 1 hour for downloads). Ensure your server time is synced via NTP. In AWS SDK v3, use getSignedUrl from @aws-sdk/s3-request-presigner.
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const client = new S3Client({ region: 'us-east-1' });
const command = new PutObjectCommand({ Bucket: 'my-bucket', Key: 'uploads/file.jpg' });
const url = await getSignedUrl(client, command, { expiresIn: 900 }); // 15 minutesWhy
Presigned URLs embed a timestamp and expiry in the signature. If the server generating the URL has clock drift, AWS rejects the URL immediately. Short expiry windows also cause race conditions with slow networks or retries.
Gotchas
- expiresIn is in seconds, not milliseconds
- The maximum expiry for presigned URLs using temporary credentials (IAM role, assumed role) is 12 hours — not 7 days
- Presigned POST policies are separate from presigned GET/PUT URLs — use createPresignedPost from @aws-sdk/s3-presigned-post for browser uploads
- Presigned URLs inherit the permissions of the signing credentials — if the role expires, the URL becomes invalid even if not expired
Code Snippets
Generating a presigned download URL with SDK v3
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const client = new S3Client({ region: 'us-east-1' });
async function getDownloadUrl(bucket, key, expiresInSeconds = 3600) {
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
return getSignedUrl(client, command, { expiresIn: expiresInSeconds });
}Context
Generating presigned URLs for direct browser-to-S3 uploads or time-limited downloads
Revisions (0)
No revisions yet.