Artikel

Presigned URL's in S3: wat ze zijn, hoe ze werken, vervallimieten en codevoorbeelden

Wat een presigned URL in S3 is, wat elke X-Amz-parameter betekent, de limiet van 7 dagen, code voor uploads en downloads (CLI, boto3, JS) en fouten.

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

VraagAntwoord
Wat geeft hij?Eén bewerking (GET, PUT, HEAD, DELETE…) op één object
Maximale geldigheid7 dagen (604.800 seconden) met SigV4 en sleutels van een IAM-gebruiker
Geldigheid met tijdelijke sleutelsEindigt als de rolsessie of de STS-gegevens eindigen, wat het eerst komt
Bereik in de S3-console1 minuut tot 12 uur
Standaardgeldigheid3.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

  1. Je code (of de CLI) neemt de bucket, de objectsleutel, de HTTP-methode en een vervaltijd.
  2. De SDK ondertekent die waarden met je secret access key via AWS Signature Version 4. Op dit moment wordt er niets naar S3 gestuurd.
  3. Je geeft de URL die eruit komt aan iemand: een browser, een collega, een mobiele app.
  4. 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
ParameterBetekenis
X-Amz-AlgorithmHet ondertekeningsalgoritme: AWS4-HMAC-SHA256 voor SigV4
X-Amz-CredentialAccess key ID plus scope: <key-id>/<YYYYMMDD>/<region>/s3/aws4_request
X-Amz-DateWanneer de URL is ondertekend, in UTC, yyyyMMddTHHmmssZ
X-Amz-ExpiresHoeveel seconden de URL geldig is na X-Amz-Date, van 1 tot 604800
X-Amz-SignedHeadersHeaders die onder de handtekening vallen, altijd minstens host
X-Amz-SignatureDe handtekening, 64 hexadecimale tekens
X-Amz-Security-TokenAlleen 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 metMaximale 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-instanceprofielTotdat de rolgegevens worden vernieuwd, hooguit ongeveer 6 uur
ECS-taakrolTotdat de taakgegevens worden vernieuwd, meestal elke 1 tot 6 uur
Ingetrokken of verwijderde gegevensStopt meteen, wat X-Amz-Expires ook zegt
S3-consoleJe 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 PUT toestaat. Voor multipart-uploads moet hij ook de header ETag blootstellen, 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, PUT en DELETE, met een geldigheid van 1 seconde tot 7 dagen. Presigned POST wordt niet ondersteund, en presigned URL’s werken alleen op het domein van de S3 API (<ACCOUNT_ID>.r2.cloudflarestorage.com), niet op eigen domeinen. Gebruik region: '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

FoutGebruikelijke oorzaakOplossing
SignatureDoesNotMatchMethode, headers of query wijken af van wat is ondertekend; klok loopt niet gelijkStuur hetzelfde Content-Type en dezelfde headers; synchroniseer de klok met NTP
AccessDenied “Request has expired”De URL is voorbij X-Amz-Date + X-Amz-ExpiresGenereer een nieuwe URL; verklein de tijd tussen ondertekenen en gebruik
URL verloopt eerder dan gevraagdOndertekend met tijdelijke gegevens (rol, EC2, ECS, Lambda)Onderteken met gegevens die zo lang geldig zijn als de link moet werken
ExpiredTokenDe STS-sessie achter X-Amz-Security-Token is afgelopenVernieuw de gegevens voordat je ondertekent
403 AccessDenied bij een nieuwe URLDe ondertekenaar mist s3:GetObject of s3:PutObject, of een beleid weigert hetGeef de ondertekenende principal die rechten; controleer bucketbeleid
AuthorizationQueryParametersErrorGeldigheid boven 604.800 seconden of een onjuiste credential scopeHoud X-Amz-Expires op 7 dagen of minder; onderteken in de regio van de bucket
Browser geblokkeerd door CORSGeen CORS-regel voor je origin en methode op de bucketVoeg een CORS-regel toe met je origin, GET/PUT en een blootgestelde ETag
AccessDenied met HeadersNotSignedRange is ondertekend, maar If-Range is niet-ondertekend meegestuurdVoeg 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:signatureAge weigert handtekeningen die ouder zijn dan een ingestelde leeftijd, en aws:SourceIp beperkt 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.