Cloud Storage signed URL expired or invalid
Downloads via signed URL fail with 'The given URL is not valid' or 403
Focused incident mode — strips away everything but this runbook.
Symptoms
- Downloads via signed URL fail with 'The given URL is not valid' or 403
- App-generated links stop working after a time
- gsutil signurl works but browser download fails
- Expiration/clock skew errors in the client
Signed URLs are snapshots in time — short TTLs are safer for security but break long-held links. Use short TTLs for credential-like access and a signing helper if clients need to keep a URL for a while.
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
Exact signed-URL error
curl -i '<SIGNED_URL>'Signing credentials
gcloud auth list --filter=status:ACTIVE
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- curl error reports an expired signature
- URL's Expires= value is in the past
- Links generated with a short TTL broke for long-lived clients
Confirm before fixing
curl -s '<SIGNED_URL>' -o /dev/null -w '%{http_code}'Confirm the status code
Expected: 403
Fix
gsutil signurl -d 1h <SERVICE_ACCOUNT_KEYFILE> gs://<BUCKET>/<OBJECT>Generate a fresh signed URL with a longer TTL
Expected: A new valid signed URL
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
curl -i '<SIGNED_URL>'Read the exact error body
date -uCompare current time to the URL expiry
gsutil signurl -d <DURATION> <SERVICE_ACCOUNT_KEYFILE> gs://<BUCKET>/<OBJECT>Re-sign with an appropriate TTL
curl -I '<NEW_SIGNED_URL>'Verify the new URL serves the object
Expected: HTTP 200
Prevention
- Match signed URL TTL to how long the client will actually hold the link.
- Centralize signing behind a helper so all components share one SA + TTL policy.
- Set bucket lifecycle rules so objects referenced by long-lived URLs are not prematurely removed.
- Monitor 403s with the key words expired/signature across your bucket access logs.
Related runbooks
Same stack, same failure family — worth a skim.
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.