Signed URL Expiry Calculator
Compute signed URL expiry considering duration and clock skew.
CDNs and object storage such as CloudFront, S3, and Cloudflare hand out signed URLs (or presigned URLs) that embed an expiry time to grant temporary access. Enter a start time and a validity duration, and this calculator converts it into the exact expiry as a UTC ISO 8601 string, a local time, and a Unix epoch (seconds) all at once.
When the signer's clock and the verifier's clock drift apart, a URL that should still be valid can be rejected as expired, or one that should be dead can linger. Provide a clock skew allowance and the tool also computes the effective expiry you can safely rely on. Everything runs in your browser; no value is ever sent anywhere.
How signed URL expiry is computed
The formula is simple: expiry = start time + duration. Most signing schemes encode the expiry as an absolute time (Unix epoch seconds) in the URL. CloudFront uses the policy DateLessThan value; an S3 presigned URL uses X-Amz-Expires (seconds) relative to when it was signed.
- Start time: usually "now" when you sign the URL, but you can pre-issue with a future start.
- Duration: entered in seconds, minutes, hours, or days. S3 presigned URLs cap at 7 days (604800s).
- Expiry: after this, the gateway rejects the request with
403.
Always account for clock skew
The clock that verifies the signature is never perfectly in sync with the one that created the URL. If the verifier's clock runs slightly fast, a URL that the signer still considers valid may already look expired to the verifier. So treat the effective expiry conservatively.
- Effective expiry = expiry − skew allowance. Design requests to arrive before this moment.
- Even with NTP, a few seconds of drift is common. A 1–5 second buffer is a safe starting point.
- Conversely, if your signature has a future start (NotBefore), a slow verifier clock may reject it as "not yet valid," so add the same kind of buffer there too.
Operational tips
- Keep expiries short. If a URL leaks, a small window limits the damage.
- For long downloads/uploads, the duration must comfortably exceed the full transfer time (whether expiry is checked only at connection start or continuously varies by service).
- Store times as UTC epoch in logs and databases; convert to local time only for display. To turn a stored epoch back into a human-readable time, the log timestamp converter is handy.
To also design CDN cache freshness (TTL) and background-refresh policy, see the stale-while-revalidate planner.
Expiry parameters and maximum validity by provider
Each service expresses expiry with a different query-string or policy field, and they cap the maximum validity differently. Some encode expiry as an absolute time (epoch), others as a relative duration (seconds) measured from when the URL was signed — which is an easy thing to mix up.
| Provider / scheme | Expiry field | Meaning | Max validity |
|---|---|---|---|
| S3 presigned (SigV4) | X-Amz-Expires | Seconds relative to X-Amz-Date (sign time) | 7 days (604800s) |
| CloudFront canned policy | Expires | Absolute epoch seconds (DateLessThan) | No cap (you set it) |
| CloudFront custom policy | Policy (base64 JSON) | DateLessThan/DateGreaterThan absolute epoch | No cap (you set it) |
| Cloudflare signed URL | exp (+ verify) | Absolute epoch seconds | No cap (you set it) |
| GCS signed URL (V4) | X-Goog-Expires | Seconds relative to X-Goog-Date | 7 days (604800s) |
| Azure Blob SAS | se (+ st) | ISO 8601 absolute times (start/expiry) | No cap (you set it) |
This calculator derives expiry from "start + duration" and shows both the epoch and the UTC ISO string, so whichever style your provider wants — a relative duration (X-Amz-Expires) or an absolute epoch (Expires, exp) — you can copy the right value directly.
A worked example
Say you enter a start time of 2026-06-27T09:00:00Z (epoch 1782032400), a duration of 2 hours, and a clock skew allowance of 5 seconds. The results are:
- Expiry (UTC):
2026-06-27T11:00:00Z— start epoch 1782032400 + 7200s =1782039600 - Effective expiry (conservative):
1782039600 − 5 = 1782039595→2026-06-27T10:59:55Z - What you put in S3:
X-Amz-Expires=7200(the duration in seconds). For CloudFront/Cloudflare,Expires=1782039600orexp=1782039600(the expiry epoch).
So for the very same expiry, the number you paste might be 7200 or 1782039600 depending on the provider. The calculator's "duration (seconds)" maps to the former, its "expiry epoch" to the latter.
Common pitfall
- Milliseconds vs. seconds: JavaScript's
Date.now()returns milliseconds, but most signing libraries expect epoch seconds. Forgetting to divide by 1000 pushes the expiry ~50,000 years out, producing an effectively immortal URL. - Putting a relative duration in an absolute-epoch field: an expiry epoch (e.g. 1782039600) in
X-Amz-Expiresyields a "50,000-year URL," while a 7200 inExpiresis read as 1970 and rejects instantly. - Mistaking the local time for the epoch: the on-screen local time is display only. Always sign with the UTC epoch or the UTC ISO value, never the localized string.