# Signed URLs explained: what they are and when to use them

Last updated: 2026-11-10

A signed URL is a link to one file in a storage bucket that carries its own permission: your server signs it with a secret key, and anyone holding the link can do one thing, such as download or upload that file, until it expires. Use one when a private file has to reach a browser or another client that has no storage credentials: a user's invoice, a paid download, or a large upload that shouldn't pass through your app. For files everyone may see, a public URL is simpler, and for anything that changes data beyond a single upload, let your server make the request itself.

## Signed URLs at a glance

| Platform | Longest lifetime | Operations | Worth knowing |
|---|---|---|---|
| AWS S3 | 7 days with Signature Version 4; 12 hours from the console | GET, PUT, HEAD and more | Ends early when the signing credentials expire |
| Google Cloud Storage | 7 days | Read, write, delete | Stops working when the signing key is rotated |
| Cloudflare R2 | 7 days | GET, HEAD, PUT, DELETE | Not usable on custom domains; HTML form POST not supported |
| OVHcloud | Not stated | Download links in its guide | Suggests path-style URLs for temporary access |
| Exoscale | Not stated | GET and PUT from its CLI | `exo storage presign` takes durations like 30s or 1h45m |
| IONOS | Not stated | Shared links from the console or the AWS CLI | Expiry set in seconds, minutes, hours or days |
| Eurobase | Not documented | Private files use signed access | Access is set per bucket; public files get a Eurobase URL |

Lifetimes and operations come from each provider's own documentation as of 10 November 2026.

## How a signed URL works

Your server knows the access key and secret key for the bucket. When a user asks for a private file, your code checks that this user may have it, then signs a string built from the bucket name, the file's key, the HTTP method and an expiry time. The signature and those values go into the query string, in parameters such as `X-Amz-Expires` and `X-Amz-Signature` on S3-compatible stores, or `X-Goog-Expires` on Google Cloud Storage.

The storage service recomputes the signature when the request arrives. If someone changes the file name, the method or the expiry, the signatures no longer match and the request fails with a 403. Signing happens entirely on your side: Cloudflare points out that generating an R2 presigned URL needs no call to R2 at all, only your credentials and the Signature Version 4 algorithm.

Two consequences follow. The link works like a bearer token: AWS and Cloudflare use that term, and Google Cloud says anyone in possession of the link can use it, account or not. And it can be used more than once until it expires. AWS also checks the expiry only when a request starts, so a download that begins a second before the deadline finishes, but a retry after it fails.

## When to use a signed URL

- **Private downloads.** Invoices, exports, purchased files, medical or HR documents. The app decides who may see a file; the bucket serves the bytes.
- **Direct uploads from the browser.** Your server signs a PUT for one object key and returns it; the browser sends the file straight to the bucket. Large files don't count against your app's request size or memory, and your keys never leave the server.
- **Sharing with someone outside your app.** A one-off link for a support case or a partner, valid for an hour or a day.
- **Handing a file to another service.** A converter, an email provider or an AI tool can fetch one object without getting a key for the whole bucket.

## When not to use one

- **Public assets.** Logos, product images and static files everyone sees belong in a public bucket or on a CDN with long cache headers. Signing them only adds a step, and each new signature changes the URL so caches see a new file. The [HTTP caching guide](/i/http-caching-for-web-apps) covers those headers.
- **Many files behind one login.** For every segment of a video or every file in a members' area, AWS points to CloudFront signed cookies instead, because one cookie covers many files and your URLs stay the same.
- **Copying, deleting or editing metadata.** Google's guidance is direct: for these, handing a signed URL to someone else is an extra step, and your server should simply make the request.
- **Long-lived links.** Seven days is the ceiling on AWS, Google Cloud and R2. If a link has to work for months, it's a public file or a route in your app that checks access on every request.

## Settings that keep signed URLs safe

- **Short expiry.** Minutes for downloads that open right away, an hour or less for uploads. Generate a fresh link each time the page loads instead of storing one.
- **Check the user first.** The signature proves your server issued the link, not that the person using it is allowed. Do the permission check before you sign.
- **Pin the content type on uploads.** Cloudflare's R2 docs recommend signing the `Content-Type` so an upload with a different type is rejected. Generate the object key on the server too, so a user can't overwrite someone else's file; on S3, a PUT to an existing key replaces the object.
- **Set CORS for browser use.** A bucket that receives uploads from a browser needs a CORS rule for your domain.
- **Watch the signing credentials.** On AWS, a URL signed with temporary role credentials dies when the role session ends, which is 1 hour by default for an STS AssumeRole session, even if you asked for 7 days. Rotating a key on Google Cloud also invalidates links signed with it. Keep the keys in [environment variables or secrets](/i/environment-variables-and-secrets), never in client code.
- **Treat links as secrets.** A signed URL in a chat, a ticket or a log line works for whoever reads it until it expires.

## Where Eurobase fits

Eurobase hosts the app that checks the user and signs the link, on European infrastructure run by Scaleway, a French provider. Your server code keeps the storage keys in environment variables and hands the browser a presigned URL from any S3-compatible store.

[Eurobase Storage](/products/storage) is a separate, smaller piece: buckets of project files, each set to public or private. Public files have a Eurobase URL and private files use signed access. Eurobase doesn't document storage credentials for your app, so for per-user links like invoices or browser uploads, pair your app with an S3-compatible provider from the [European object storage comparison](/i/european-object-storage). The [user uploads guide](/i/user-uploads-object-storage) walks through that flow, and the [Vercel Blob alternatives guide](/i/vercel-blob-alternatives-europe) covers moving off Vercel Blob.

Storage comes inside the flat plan: Free includes 500 MiB of file storage and 25 GiB of transfer a month, Starter at €10 a month includes 5 GiB and 100 GiB, and Pro at €20 a month includes 20 GiB and 500 GiB, all + VAT. Nothing is billed by usage today: an action past a limit is refused, apps on V8 isolates and Functions stop until the monthly reset, and containers and static sites keep serving. See the [plan limits](/docs/plans#limits).

## Frequently asked questions

### What's the difference between a signed URL and a presigned URL?

None in practice. AWS and Cloudflare say presigned URL, Google Cloud says signed URL, and both mean a link that carries a signature and an expiry in its query string. CloudFront signed URLs are a related idea: they're signed for the CDN in front of the bucket rather than for the bucket itself.

### How long should a signed URL last?

As short as the task allows. For a download the user opens right away, a few minutes is enough; Google's own example uses 15 minutes. For uploads, cover the time a slow connection needs for your largest file. The maximum on AWS, Google Cloud and Cloudflare R2 is 7 days.

### Can I revoke a signed URL before it expires?

Not one link on its own. On AWS a link stops working when the credentials that signed it are revoked, deleted or deactivated, and on Google Cloud when the signing key is rotated, but that cuts off every link signed with the same key. That's the main reason to keep lifetimes short.

### Are signed URLs secure enough for private user files?

Yes, when they're short-lived, issued only after your app checks the user, and scoped to one file and one method. They're weaker when links last for days or get pasted into places other people can read, because anyone with the link has the same access as the intended user.

### Can I use a signed URL with my own domain?

It depends on the provider. Cloudflare R2 presigned URLs work only on the R2 S3 API domain, not on custom domains, and Cloudflare suggests its WAF HMAC validation for authenticated access through a custom domain. With S3 you can put CloudFront in front and use CloudFront signed URLs on your domain.

## Sources

- [AWS: Download and upload objects with presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html)
- [AWS: Decide to use signed URLs or signed cookies in CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-choosing-signed-urls-cookies.html)
- [Google Cloud: Signed URLs](https://cloud.google.com/storage/docs/access-control/signed-urls)
- [Cloudflare R2: Presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/) and [Configure CORS](https://developers.cloudflare.com/r2/buckets/cors/)
- [OVHcloud: How to share an object or file externally](https://docs.ovhcloud.com/en/guides/storage-and-backup/object-storage/s3-share-object-externally)
- [Exoscale: exo storage presign](https://community.exoscale.com/reference/cli/exo/storage/presign/)
- [IONOS: Share objects with pre-signed URLs](https://docs.ionos.com/cloud/backup-and-storage/ionos-object-storage/how-tos/share-objects-pre-signed-urls)
- [Eurobase Storage](/products/storage) and [plan limits](/docs/plans#limits)
