Een presigned URL in S3 is een tijdelijke link naar één object in een privébucket. De link bevat een handtekening in de querystring, zodat wie hem heeft dat object kan downloaden (of uploaden) zonder AWS-gegevens totdat de link verloopt, en de bucket nooit openbaar wordt. Je maakt hem met je eigen sleutels, in je code of met de AWS CLI, en hij kan van 1 seconde tot 7 dagen geldig zijn. Deze gids legt uit hoe het werkt, wat elke X-Amz-*-parameter betekent, hoe lang een link geldig kan zijn, code voor downloads en uploads en de fouten die je het vaakst tegenkomt. De limieten komen uit de documentatie van AWS en Cloudflare, stand oktober 2026.
Presigned URL’s in S3 in één oogopslag
| Vraag | Antwoord |
|---|---|
| Wat geeft hij? | Eén bewerking (GET, PUT, HEAD, DELETE…) op één object |
| Maximale geldigheid | 7 dagen (604.800 seconden) met SigV4 en sleutels van een IAM-gebruiker |
| Geldigheid met tijdelijke sleutels | Eindigt als de rolsessie of de STS-gegevens eindigen, wat het eerst komt |
| Bereik in de S3-console | 1 minuut tot 12 uur |
| Standaardgeldigheid | 3.600 s in de AWS CLI en boto3, 900 s in de presigner van JavaScript v3 |
| Kan hij meerdere keren worden gebruikt? | Ja, zo vaak als iemand wil totdat hij verloopt |
| Kan één URL worden ingetrokken? | Nee, alleen door de ondertekenende gegevens in te trekken of hem in een beleid te weigeren |
| Roept het maken ervan S3 aan? | Nee, de handtekening wordt lokaal berekend |
Hoe een presigned URL werkt
- Je code (of de CLI) neemt de bucket, de objectsleutel, de HTTP-methode en een vervaltijd.
- De SDK ondertekent die waarden met je secret access key via AWS Signature Version 4. Op dit moment wordt er niets naar S3 gestuurd.
- Je geeft de URL die eruit komt aan iemand: een browser, een collega, een mobiele app.
- Als die hem gebruikt, berekent S3 de handtekening opnieuw met de secret die hoort bij de access key in de URL. Klopt die, is de URL niet verlopen en mag de ondertekenaar die bewerking uitvoeren, dan gaat het verzoek door.
De URL draagt de rechten van degene die hem heeft ondertekend. AWS is daar duidelijk over: als de IAM-gebruiker of -rol die de URL heeft gemaakt het object niet mag lezen, geeft de URL een 403, hoe geldig de handtekening ook is.
Wat de X-Amz-parameters betekenen
Een presigned URL met SigV4 ziet er zo uit (met regeleinden toegevoegd):
https://my-bucket.s3.eu-west-1.amazonaws.com/reports/q3.pdf
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20261017%2Feu-west-1%2Fs3%2Faws4_request
&X-Amz-Date=20261017T093000Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=5f2c...e91a
| Parameter | Betekenis |
|---|---|
X-Amz-Algorithm | Het ondertekeningsalgoritme: AWS4-HMAC-SHA256 voor SigV4 |
X-Amz-Credential | Access key ID plus scope: <key-id>/<YYYYMMDD>/<region>/s3/aws4_request |
X-Amz-Date | Wanneer de URL is ondertekend, in UTC, yyyyMMddTHHmmssZ |
X-Amz-Expires | Hoeveel seconden de URL geldig is na X-Amz-Date, van 1 tot 604800 |
X-Amz-SignedHeaders | Headers die onder de handtekening vallen, altijd minstens host |
X-Amz-Signature | De handtekening, 64 hexadecimale tekens |
X-Amz-Security-Token | Alleen aanwezig als de URL met tijdelijke gegevens is ondertekend (STS, rollen, EC2, ECS, SSO) |
Wijzig je een ondertekend onderdeel (de sleutel, de methode, de vervaltijd of een ondertekende header), dan klopt de handtekening niet meer. Let ook op wat de URL in leesbare tekst prijsgeeft: de bucketnaam, de volledige objectsleutel en je access key ID. De secret key staat nooit in de URL.
Hoe lang een presigned URL geldig is
De limiet hangt af van de gegevens waarmee hij wordt ondertekend:
| Ondertekend met | Maximale geldigheid |
|---|---|
| Access keys van een IAM-gebruiker (SigV4) | 7 dagen, omdat de ondertekeningssleutel van SigV4 zelf 7 dagen geldig is |
| IAM-rolsessie (STS AssumeRole) | Totdat de sessie eindigt, standaard 1 uur |
| EC2-instanceprofiel | Totdat de rolgegevens worden vernieuwd, hooguit ongeveer 6 uur |
| ECS-taakrol | Totdat de taakgegevens worden vernieuwd, meestal elke 1 tot 6 uur |
| Ingetrokken of verwijderde gegevens | Stopt meteen, wat X-Amz-Expires ook zegt |
| S3-console | Je kiest tussen 1 minuut en 12 uur |
Dit verklaart de meest voorkomende verrassing: je vraagt 7 dagen aan vanuit een Lambda-functie of een EC2-instance en de link werkt na een uur of een paar uur niet meer. De URL leeft maar zo lang als de gegevens erachter. Heb je lange links nodig, onderteken dan met een aparte IAM-gebruiker die alleen leestoegang tot die bucket heeft.
S3 controleert de vervaltijd wanneer een verzoek begint. Een grote download die één seconde voor de deadline start, loopt gewoon door, maar een nieuwe poging na de deadline mislukt met AccessDenied en de melding “Request has expired”. Meer dan 604.800 seconden aanvragen wordt direct geweigerd.
Een presigned URL genereren
AWS CLI
aws s3 presign s3://my-bucket/reports/q3.pdf --expires-in 3600
Het commando ondertekent een download-URL (GET). De standaard is 3.600 seconden en het maximum 604.800. Recente CLI-versies gebruiken altijd SigV4, dus zorg dat er een regio is ingesteld. Voor upload-URL’s heb je een SDK nodig.
Python (boto3)
import boto3
from botocore.config import Config
s3 = boto3.client('s3', region_name='eu-west-1', config=Config(signature_version='s3v4'))
url = s3.generate_presigned_url(
ClientMethod='get_object',
Params={
'Bucket': 'my-bucket',
'Key': 'reports/q3.pdf',
'ResponseContentDisposition': 'attachment; filename="q3.pdf"',
},
ExpiresIn=3600,
)
Stel signature_version='s3v4' expliciet in. De referentie van boto3 waarschuwt dat het anders, voor achterwaartse compatibiliteit, kan ondertekenen met de oudere Signature Version 2 in regio’s die die nog accepteren, waardoor sommige parameters en headers zoals Range stilletjes wegvallen. ResponseContentDisposition is optioneel: daarmee slaat de browser het bestand op met een nette naam in plaats van het inline te openen.
JavaScript (AWS SDK v3)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({ region: 'eu-west-1' })
const url = await getSignedUrl(
client,
new GetObjectCommand({ Bucket: 'my-bucket', Key: 'reports/q3.pdf' }),
{ expiresIn: 3600 }
)
Laat je expiresIn weg, dan gebruikt de JavaScript-presigner 900 seconden (15 minuten).
Presigned upload-URL’s (PUT)
Hetzelfde idee werkt ook de andere kant op. Je ondertekent een PutObject voor één sleutel en wie de URL heeft, kan dat object schrijven. Het bestand gaat rechtstreeks van het apparaat van die persoon naar de bucket en je server raakt de bytes nooit aan. Bestaat er al een object met die sleutel, dan wordt het vervangen.
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({ region: 'eu-west-1' })
const url = await getSignedUrl(
client,
new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'incoming/brief.pdf',
ContentType: 'application/pdf',
}),
{ expiresIn: 900, signableHeaders: new Set(['content-type']) }
)
await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/pdf' },
body: file,
})
Door content-type aan signableHeaders toe te voegen, koppel je de URL aan dat type, zodat een upload met een ander Content-Type wordt geweigerd. In boto3 is het equivalent generate_presigned_url('put_object', Params={'Bucket': ..., 'Key': ..., 'ContentType': 'application/pdf'}), en de client moet dezelfde header sturen.
Drie dingen om te controleren voordat je vanuit een browser uploadt:
- CORS. De bucket heeft een CORS-regel nodig die je origin en de methode
PUTtoestaat. Voor multipart-uploads moet hij ook de headerETagblootstellen, omdat de browser die van elk part moet lezen. - Vaste sleutel. Genereer een unieke sleutel op de server (bijvoorbeeld met een UUID als prefix), zodat een bezoeker het bestand van iemand anders niet kan overschrijven.
- Checksums. Huidige AWS SDK’s voegen standaard een CRC-checksum toe aan uploads. Mislukken presigned uploads bij een S3-compatibele provider met handtekening- of checksumfouten, stel dan
requestChecksumCalculation: 'WHEN_REQUIRED'in op de client.
Presigned POST is een apart mechanisme voor gewone HTML-formulieren, met een beleid dat grootte en contenttype kan beperken. Niet elke S3-compatibele provider ondersteunt het (R2 niet), dus presigned PUT is de beter overdraagbare keuze.
Grote bestanden: presigned multipart-uploads
Een losse PUT naar S3 is beperkt tot 5 GB, en AWS raadt multipart aan vanaf ongeveer 100 MB. Multipart splitst het bestand in maximaal 10.000 parts, elk van 5 MiB tot 5 GiB behalve de laatste, die los van elkaar worden geüpload, parallel en met nieuwe pogingen per part. Presignen werkt per part: de server start de upload, ondertekent één UploadPart-URL per part, de browser stuurt elk stuk en meldt de ETag terug, en de server rondt de upload af.
import {
CreateMultipartUploadCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
} from '@aws-sdk/client-s3'
const { UploadId } = await client.send(
new CreateMultipartUploadCommand({ Bucket: 'my-bucket', Key: 'video/raw.mov' })
)
const partUrl = await getSignedUrl(
client,
new UploadPartCommand({ Bucket: 'my-bucket', Key: 'video/raw.mov', UploadId, PartNumber: 1 }),
{ expiresIn: 3600 }
)
await client.send(
new CompleteMultipartUploadCommand({
Bucket: 'my-bucket',
Key: 'video/raw.mov',
UploadId,
MultipartUpload: { Parts: [{ PartNumber: 1, ETag: etagFromPartResponse }] },
})
)
Voeg een lifecycle-regel toe die onvoltooide multipart-uploads na een paar dagen afbreekt, anders blijven achtergelaten parts factureerbare opslag innemen.
Presigned URL’s bij R2, B2, Spaces en Wasabi
Providers die de S3 API met SigV4 implementeren, accepteren dezelfde code: je wijzigt alleen het endpoint, de regio en de sleutels. De details verschillen, dus controleer de documentatie van de provider:
- Cloudflare R2 ondersteunt presigned
GET,HEAD,PUTenDELETE, met een geldigheid van 1 seconde tot 7 dagen. PresignedPOSTwordt niet ondersteund, en presigned URL’s werken alleen op het domein van de S3 API (<ACCOUNT_ID>.r2.cloudflarestorage.com), niet op eigen domeinen. Gebruikregion: 'auto'. - Backblaze B2 geeft aan dat de S3-compatibele API presigned URL’s ondersteunt die met de AWS CLI of SDK’s zijn gegenereerd.
- DigitalOcean Spaces en Wasabi werken met dezelfde SDK-aanroepen, gericht op hun endpoints.
Voor de prijzen per provider, zie de vergelijking van S3, R2, Spaces, Wasabi en B2.
Veelvoorkomende fouten met presigned URL’s en hoe je ze oplost
| Fout | Gebruikelijke oorzaak | Oplossing |
|---|---|---|
SignatureDoesNotMatch | Methode, headers of query wijken af van wat is ondertekend; klok loopt niet gelijk | Stuur hetzelfde Content-Type en dezelfde headers; synchroniseer de klok met NTP |
AccessDenied “Request has expired” | De URL is voorbij X-Amz-Date + X-Amz-Expires | Genereer een nieuwe URL; verklein de tijd tussen ondertekenen en gebruik |
| URL verloopt eerder dan gevraagd | Ondertekend met tijdelijke gegevens (rol, EC2, ECS, Lambda) | Onderteken met gegevens die zo lang geldig zijn als de link moet werken |
ExpiredToken | De STS-sessie achter X-Amz-Security-Token is afgelopen | Vernieuw de gegevens voordat je ondertekent |
403 AccessDenied bij een nieuwe URL | De ondertekenaar mist s3:GetObject of s3:PutObject, of een beleid weigert het | Geef de ondertekenende principal die rechten; controleer bucketbeleid |
AuthorizationQueryParametersError | Geldigheid boven 604.800 seconden of een onjuiste credential scope | Houd X-Amz-Expires op 7 dagen of minder; onderteken in de regio van de bucket |
| Browser geblokkeerd door CORS | Geen CORS-regel voor je origin en methode op de bucket | Voeg een CORS-regel toe met je origin, GET/PUT en een blootgestelde ETag |
AccessDenied met HeadersNotSigned | Range is ondertekend, maar If-Range is niet-ondertekend meegestuurd | Voeg If-Range toe aan de ondertekende headers |
Bedrijfsproxy’s die headers of querystrings herschrijven, breken ook handtekeningen. Werkt een URL vanaf het ene netwerk wel en vanaf het andere niet, test dan zonder de proxy.
Best practices voor beveiliging
AWS noemt presigned URL’s bearer tokens en raadt aan ze te beschermen. In de praktijk:
- Houd ze kort. Minuten voor uploads en gevoelige bestanden, uren voor gewone downloads. Genereer een nieuwe als iemand het bestand opnieuw nodig heeft.
- Onderteken met een beperkte identiteit. Een aparte IAM-gebruiker of -rol die alleen het betreffende prefix kan lezen (of alleen kan schrijven), beperkt de schade als een link uitlekt.
- Bouw vangrails in het bucketbeleid. De voorwaarde
s3:signatureAgeweigert handtekeningen die ouder zijn dan een ingestelde leeftijd, enaws:SourceIpbeperkt vanaf welke netwerken ze kunnen worden gebruikt. - Log ze niet. Volledige presigned URL’s in applicatielogs, analytics of chatgeschiedenis zijn werkende inloggegevens totdat ze verlopen.
- Houd rekening met wat ze missen. Er is geen wachtwoord, geen downloadteller en geen intrekking per link, dus zet een applicatie ervoor als je dat nodig hebt.
Voor het delen zelf, met opties die die controles toevoegen, zie hoe je S3-bestanden deelt zonder de bucket openbaar te maken.
Hoe Nubbo presigned URL’s gebruikt
Nubbo is op dit patroon gebouwd. Als je in de verkenner uploadt, ondertekent de server een PUT (of, vanaf 100 MB, een multipart-upload met drie parts parallel) en stuurt je browser de bytes rechtstreeks naar je bucket. Downloads van losse bestanden gebruiken presigned GET-URL’s die een uur geldig zijn. Je access keys worden versleuteld opgeslagen met AES-256-GCM en worden alleen op de server gebruikt om te ondertekenen.
Deellinks voegen toe wat kale presigned URL’s missen: een optioneel wachtwoord, een vervaldatum en een downloadlimiet die Nubbo controleert voordat het een kortlopende URL uitgeeft, en je kunt een link op elk moment deactiveren. ZIP-downloads met meerdere bestanden worden in je browser samengesteld, die elk bestand rechtstreeks uit je bucket downloadt: Nubbo ziet de inhoud nooit. Eén eerlijke uitzondering: miniaturen worden in je eigen bucket geschreven onder .nubbo-thumbs/. Het werkt hetzelfde bij Amazon S3, Cloudflare R2, DigitalOcean Spaces, Wasabi, Backblaze B2 en openbare S3-compatibele endpoints.
Veelgestelde vragen
Wat is een presigned URL in S3?
Het is een gewone HTTPS-link naar één object in een privébucket, met een handtekening in de querystring. Wie de link heeft, kan de ene bewerking uitvoeren waarvoor hij is ondertekend, meestal een download (GET) of een upload (PUT), totdat hij verloopt. De bucket blijft privé en wie de link gebruikt, heeft geen AWS-gegevens nodig.
Wat is de maximale geldigheid van een presigned URL in S3?
Zeven dagen (604.800 seconden) als je ondertekent met Signature Version 4 en langlopende sleutels van een IAM-gebruiker. Een URL die is ondertekend met tijdelijke gegevens, zoals een aangenomen rol of een EC2-instanceprofiel, werkt niet meer zodra die gegevens verlopen, ook als je om een langere termijn vroeg. In de S3-console kies je tussen 1 minuut en 12 uur.
Wat betekenen X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders en X-Amz-Signature?
Het zijn de authenticatieparameters van SigV4. Algorithm is de ondertekeningsmethode (AWS4-HMAC-SHA256), Credential is je access key ID plus de scope met datum, regio en dienst, Date is het moment van ondertekenen, Expires is hoeveel seconden de URL geldig blijft, SignedHeaders somt de headers op die onder de handtekening vallen en Signature is de handtekening zelf. X-Amz-Security-Token verschijnt alleen als de URL met tijdelijke gegevens is ondertekend.
Kan ik een presigned URL in S3 intrekken voordat hij verloopt?
Niet per stuk. Een presigned URL werkt alleen eerder niet meer als de gegevens waarmee hij is ondertekend worden ingetrokken, verwijderd of gedeactiveerd, of als een bucketbeleid het verzoek weigert. Moet je losse links kunnen annuleren, zet dan een applicatie ervoor die pas na eigen controles kortlopende URL's uitgeeft.
Zijn presigned URL's in S3 veilig?
De handtekening kan niet worden vervalst of gewijzigd en de bucket blijft privé. Maar AWS omschrijft presigned URL's als bearer tokens: iedereen die de link krijgt, kan hem gebruiken, zo vaak als hij wil, totdat hij verloopt. Houd de geldigheid kort, gebruik HTTPS en plaats de links niet op plekken waar ze kunnen worden gekopieerd.
Werken presigned URL's met Cloudflare R2, Backblaze B2 of Wasabi?
Providers die de S3 API met Signature Version 4 implementeren, ondersteunen ze meestal, en je gebruikt dezelfde SDK-aanroepen met het endpoint van de provider. Cloudflare R2 ondersteunt bijvoorbeeld presigned GET, HEAD, PUT en DELETE met een geldigheid van 1 seconde tot 7 dagen, maar geen uploads via POST-formulieren en geen eigen domeinen.
Waarom geeft mijn presigned URL SignatureDoesNotMatch?
AWS noemt drie veelvoorkomende oorzaken: een computerklok die niet gelijkloopt, een proxy die headers of de querystring wijzigt, en een verzoek waarvan de methode, headers of parameters afwijken van wat is ondertekend. Bij uploads is de gebruikelijke boosdoener een ander Content-Type dan het type waarmee de URL is gegenereerd.
Bronnen
Prijzen en limieten komen van de officiële pagina's hieronder. Providers passen ze aan, dus controleer ze voordat je een keuze maakt.
- Amazon S3: objecten downloaden en uploaden met presigned URL's
- AWS IAM: authenticatiemethoden (SigV4-queryparameters)
- Amazon S3 API: verzoeken authenticeren met queryparameters (SigV4)
- AWS CLI-referentie: s3 presign
- Boto3-referentie: generate_presigned_url
- AWS SDK for JavaScript v3: @aws-sdk/s3-request-presigner
- Amazon S3: limieten van multipart-uploads
- AWS SDKs and Tools: bescherming van gegevensintegriteit voor Amazon S3
- Cloudflare R2: presigned URL's
- Backblaze: ondersteunt de S3-compatibele API van B2 presigned URL's?