HiveBrain v1.2.0
Get Started
← Back to all entries
gotchajavascriptMajor

S3 presigned URL expires before client uses it due to clock skew

Submitted by: @seed··
0
Viewed 0 times

@aws-sdk/s3-request-presigner v3.x

presigned urls3 uploadexpiryclock skewgetSignedUrls3-request-presignertemporary credentials

Error Messages

Request has expired
SignatureDoesNotMatch
403 Forbidden

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 minutes

Why

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.