Uma URL pré-assinada do S3 é um link temporário para um objeto de um bucket privado. Ela leva uma assinatura na query string, então quem a tiver pode baixar (ou enviar) esse objeto sem credenciais da AWS até o link expirar, e o bucket nunca se torna público. Você cria a URL com as suas próprias chaves, no seu código ou pela AWS CLI, e ela pode durar de 1 segundo a 7 dias. Este guia explica como ela funciona, o que significa cada parâmetro X-Amz-*, quanto tempo ela pode durar, código para downloads e uploads e os erros que você tem mais chance de encontrar. Os limites vêm da documentação da AWS e da Cloudflare em outubro de 2026.
URLs pré-assinadas do S3 em resumo
| Pergunta | Resposta |
|---|---|
| O que ela permite? | Uma operação (GET, PUT, HEAD, DELETE…) sobre um objeto |
| Expiração máxima | 7 dias (604.800 segundos) com SigV4 e chaves de usuário IAM |
| Expiração com chaves temporárias | Termina quando acaba a sessão da função ou as credenciais STS, o que vier antes |
| Intervalo no console do S3 | De 1 minuto a 12 horas |
| Expiração padrão | 3.600 s na AWS CLI e no boto3, 900 s no presigner de JavaScript v3 |
| Pode ser usada várias vezes? | Sim, quantas vezes se quiser até expirar |
| Dá para revogar uma só URL? | Não, só revogando as credenciais que assinam ou negando-a em uma política |
| Criar uma URL faz chamadas ao S3? | Não, a assinatura é calculada localmente |
Como funciona uma URL pré-assinada
- O seu código (ou a CLI) recebe o bucket, a chave do objeto, o método HTTP e um tempo de expiração.
- O SDK assina esses valores com a sua secret access key usando AWS Signature Version 4. Nessa etapa nada é enviado ao S3.
- Você entrega a URL resultante a alguém: um navegador, um colega, um app para celular.
- Quando ela é usada, o S3 recalcula a assinatura com o segredo que pertence à chave de acesso presente na URL. Se coincidir, se a URL não tiver expirado e se quem assinou puder fazer essa operação, a requisição é aceita.
A URL carrega as permissões de quem a assinou. A AWS é explícita nesse ponto: se o usuário ou a função IAM que gerou a URL não puder ler o objeto, a URL retorna 403 por mais válida que seja a assinatura.
O que significam os parâmetros X-Amz
Uma URL pré-assinada SigV4 tem esta cara (com quebras de linha acrescentadas):
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
| Parâmetro | Significado |
|---|---|
X-Amz-Algorithm | O algoritmo de assinatura: AWS4-HMAC-SHA256 para SigV4 |
X-Amz-Credential | ID da chave de acesso mais o escopo: <key-id>/<YYYYMMDD>/<region>/s3/aws4_request |
X-Amz-Date | Momento em que a URL foi assinada, em UTC, yyyyMMddTHHmmssZ |
X-Amz-Expires | Por quantos segundos a URL é válida depois de X-Amz-Date, de 1 a 604800 |
X-Amz-SignedHeaders | Cabeçalhos cobertos pela assinatura, sempre pelo menos host |
X-Amz-Signature | A assinatura, 64 caracteres hexadecimais |
X-Amz-Security-Token | Só aparece quando a URL foi assinada com credenciais temporárias (STS, funções, EC2, ECS, SSO) |
Se você alterar qualquer parte assinada (a chave, o método, a expiração ou um cabeçalho assinado), a assinatura deixa de coincidir. Repare também no que a URL revela em texto puro: o nome do bucket, a chave completa do objeto e o ID da sua chave de acesso. A chave secreta nunca está na URL.
Quanto tempo dura uma URL pré-assinada
O limite depende das credenciais que assinam:
| Assinada com | Validade máxima |
|---|---|
| Chaves de acesso de usuário IAM (SigV4) | 7 dias, porque a própria chave de assinatura SigV4 é válida por 7 dias |
| Sessão de função IAM (STS AssumeRole) | Até a sessão terminar, 1 hora por padrão |
| Perfil de instância EC2 | Até as credenciais da função serem rotacionadas, no máximo cerca de 6 horas |
| Função de tarefa ECS | Até as credenciais da tarefa serem rotacionadas, normalmente a cada 1 a 6 horas |
| Credenciais revogadas ou excluídas | Para de funcionar na hora, diga o que disser X-Amz-Expires |
| Console do S3 | Você escolhe entre 1 minuto e 12 horas |
Isso explica a surpresa mais comum: você pede 7 dias a partir de uma função Lambda ou de uma instância EC2 e o link morre depois de uma hora ou de poucas horas. A URL só dura tanto quanto as credenciais por trás dela. Se você precisa de links longos, assine com um usuário IAM dedicado que só tenha acesso de leitura a esse bucket.
O S3 verifica a expiração quando uma requisição começa. Um download grande que comece um segundo antes do limite termina normalmente, mas uma nova tentativa depois do limite falha com AccessDenied e a mensagem “Request has expired”. Pedir mais de 604.800 segundos é rejeitado logo de cara.
Como gerar uma URL pré-assinada
AWS CLI
aws s3 presign s3://my-bucket/reports/q3.pdf --expires-in 3600
O comando assina uma URL de download (GET). O padrão é de 3.600 segundos e o máximo de 604.800. As versões recentes da CLI usam sempre SigV4, então garanta que você tem uma região configurada. Para URLs de upload você precisa de um SDK.
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,
)
Defina signature_version='s3v4' de forma explícita. A referência do boto3 avisa que, por compatibilidade com versões anteriores, ele pode assinar com a antiga Signature Version 2 nas regiões que ainda a aceitam, o que descarta silenciosamente alguns parâmetros e cabeçalhos, como Range. ResponseContentDisposition é opcional: faz o navegador salvar o arquivo com um nome limpo em vez de abri-lo diretamente.
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 }
)
Se você omitir expiresIn, o presigner de JavaScript usa 900 segundos (15 minutos).
URLs pré-assinadas de upload (PUT)
A mesma ideia funciona no sentido inverso. Você assina um PutObject para uma chave e quem tiver a URL pode gravar esse objeto. O arquivo vai direto do dispositivo dessa pessoa para o bucket e o seu servidor nunca toca nos bytes. Se já existir um objeto com essa chave, ele é substituído.
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,
})
Acrescentar content-type a signableHeaders vincula a URL a esse tipo, então um upload com outro Content-Type é rejeitado. No boto3, o equivalente é generate_presigned_url('put_object', Params={'Bucket': ..., 'Key': ..., 'ContentType': 'application/pdf'}), e o cliente precisa enviar o mesmo cabeçalho.
Três coisas a conferir antes de fazer upload a partir de um navegador:
- CORS. O bucket precisa de uma regra CORS que permita a sua origem e o método
PUT. Para uploads multipart, ela também precisa expor o cabeçalhoETag, porque o navegador tem que lê-lo em cada parte. - Chave fixa. Gere uma chave única no servidor (por exemplo, com um prefixo UUID) para que um visitante não possa substituir o arquivo de outra pessoa.
- Checksums. Os SDKs atuais da AWS acrescentam por padrão um checksum CRC aos uploads. Se os uploads pré-assinados falharem em um provedor compatível com S3 com erros de assinatura ou de checksum, defina
requestChecksumCalculation: 'WHEN_REQUIRED'no cliente.
O POST pré-assinado é um mecanismo à parte para formulários HTML simples, com uma política que pode limitar o tamanho e o tipo de conteúdo. Nem todos os provedores compatíveis com S3 o suportam (o R2 não), então o PUT pré-assinado é a opção mais portátil.
Arquivos grandes: uploads multipart pré-assinados
Um único PUT para o S3 está limitado a 5 GB e a AWS sugere multipart a partir de cerca de 100 MB. O multipart divide o arquivo em até 10.000 partes, cada uma entre 5 MiB e 5 GiB exceto a última, que são enviadas de forma independente, em paralelo e com novas tentativas por parte. A pré-assinatura funciona por parte: o servidor inicia o upload, assina uma URL UploadPart para cada parte, o navegador envia cada bloco e devolve o respectivo ETag, e o servidor conclui o upload.
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 }] },
})
)
Acrescente uma regra de ciclo de vida que cancele os uploads multipart incompletos depois de alguns dias, ou as partes abandonadas continuam ocupando armazenamento cobrado.
URLs pré-assinadas no R2, B2, Spaces e Wasabi
Os provedores que implementam a API do S3 com SigV4 aceitam o mesmo código: só mudam o endpoint, a região e as chaves. Os detalhes variam, então consulte a documentação do provedor:
- O Cloudflare R2 suporta
GET,HEAD,PUTeDELETEpré-assinados, com uma expiração entre 1 segundo e 7 dias. Ele não suportaPOSTpré-assinado e as URLs pré-assinadas só funcionam no domínio da API do S3 (<ACCOUNT_ID>.r2.cloudflarestorage.com), não em domínios personalizados. Useregion: 'auto'. - A Backblaze informa que a API compatível com S3 do B2 suporta URLs pré-assinadas geradas com a AWS CLI ou com os SDKs.
- O DigitalOcean Spaces e o Wasabi funcionam com as mesmas chamadas do SDK apontadas para os respectivos endpoints.
Para os preços de cada provedor, confira a comparação entre S3, R2, Spaces, Wasabi e B2.
Erros comuns das URLs pré-assinadas e como resolvê-los
| Erro | Causa mais comum | Solução |
|---|---|---|
SignatureDoesNotMatch | Método, cabeçalhos ou query diferentes do que foi assinado; relógio dessincronizado | Envie o mesmo Content-Type e os mesmos cabeçalhos; sincronize o relógio com NTP |
AccessDenied “Request has expired” | A URL já passou de X-Amz-Date + X-Amz-Expires | Gere uma URL nova; reduza o intervalo entre a assinatura e o uso |
| A URL expira antes da requisição | Assinada com credenciais temporárias (função, EC2, ECS, Lambda) | Assine com credenciais que durem tanto quanto o link precisa |
ExpiredToken | A sessão STS por trás de X-Amz-Security-Token terminou | Renove as credenciais antes de assinar |
403 AccessDenied em uma URL recém-gerada | Quem assina não tem s3:GetObject ou s3:PutObject, ou uma política nega | Dê a permissão à identidade que assina; revise as políticas do bucket |
AuthorizationQueryParametersError | Expiração acima de 604.800 segundos ou escopo de credencial malformado | Mantenha X-Amz-Expires em 7 dias ou menos; assine na região do bucket |
| Navegador bloqueado por CORS | Não há regra CORS para a sua origem e o seu método no bucket | Acrescente uma regra CORS com a sua origem, GET/PUT e ETag exposto |
AccessDenied com HeadersNotSigned | Range foi assinado, mas If-Range foi enviado sem assinatura | Acrescente If-Range aos cabeçalhos assinados |
Os proxies corporativos que reescrevem cabeçalhos ou query strings também quebram as assinaturas. Se uma URL funciona em uma rede e não em outra, teste sem o proxy.
Boas práticas de segurança
A AWS chama as URLs pré-assinadas de bearer tokens e recomenda protegê-las. Na prática:
- Mantenha-as curtas. Minutos para uploads e arquivos sensíveis, horas para downloads comuns. Gere uma nova quando alguém precisar do arquivo de novo.
- Assine com uma identidade restrita. Um usuário ou função IAM dedicado que só possa ler (ou só gravar) o prefixo em questão limita o estrago se um link vazar.
- Acrescente salvaguardas na política do bucket. A condição
s3:signatureAgerejeita assinaturas mais antigas do que uma determinada idade eaws:SourceIprestringe as redes que podem usá-las. - Não as registre em logs. URLs pré-assinadas completas em logs do app, analytics ou histórico de chat são credenciais válidas até expirarem.
- Conte com o que falta a elas. Não há senha, contagem de downloads nem revogação por link, então coloque um app na frente quando precisar disso.
Para a parte do compartilhamento em detalhe, com opções que acrescentam esses controles, confira como compartilhar arquivos do S3 sem tornar o bucket público.
Como o Nubbo usa as URLs pré-assinadas
O Nubbo se apoia nesse padrão. Quando você faz upload no explorador, o servidor assina um PUT (ou, a partir de 100 MB, um upload multipart com três partes em paralelo) e o seu navegador envia os bytes diretamente para o seu bucket. Os downloads de um só arquivo usam URLs GET pré-assinadas válidas por uma hora. As suas chaves de acesso são armazenadas criptografadas com AES-256-GCM e só são usadas no servidor para assinar.
Os links de compartilhamento acrescentam o que falta às URLs pré-assinadas simples: uma senha opcional, uma data de expiração e um limite de downloads que o Nubbo verifica antes de emitir uma URL de curta duração, e você pode desativar um link a qualquer momento. Os downloads ZIP de vários arquivos são montados no seu navegador, que baixa cada arquivo diretamente do seu bucket: o Nubbo nunca vê o conteúdo. Uma exceção honesta: as miniaturas são gravadas no seu próprio bucket em .nubbo-thumbs/. Funciona do mesmo jeito no Amazon S3, no Cloudflare R2, no DigitalOcean Spaces, no Wasabi, no Backblaze B2 e em endpoints públicos compatíveis com S3.
Perguntas frequentes
O que é uma URL pré-assinada no S3?
É um link HTTPS normal para um objeto de um bucket privado, com uma assinatura acrescentada à query string. Quem tiver o link pode fazer a única operação para a qual ele foi assinado, normalmente um download (GET) ou um upload (PUT), até ele expirar. O bucket continua privado e quem usa o link não precisa de credenciais da AWS.
Qual é a expiração máxima de uma URL pré-assinada do S3?
Sete dias (604.800 segundos) quando você assina com Signature Version 4 e chaves de longa duração de um usuário IAM. Uma URL assinada com credenciais temporárias, como uma função assumida ou um perfil de instância EC2, para de funcionar quando essas credenciais expiram, mesmo que você tenha pedido mais tempo. No console do S3 você pode escolher entre 1 minuto e 12 horas.
O que significam X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders e X-Amz-Signature?
São os parâmetros de autenticação SigV4. Algorithm é o método de assinatura (AWS4-HMAC-SHA256), Credential é o ID da sua chave de acesso com o escopo de data, região e serviço, Date é o momento em que a URL foi assinada, Expires é o número de segundos durante os quais ela é válida, SignedHeaders lista os cabeçalhos cobertos pela assinatura e Signature é a própria assinatura. X-Amz-Security-Token só aparece quando a URL foi assinada com credenciais temporárias.
Posso revogar uma URL pré-assinada do S3 antes de ela expirar?
Não uma por uma. Uma URL pré-assinada só para de funcionar antes do tempo quando as credenciais que a assinaram são revogadas, excluídas ou desativadas, ou quando uma política do bucket nega a requisição. Se você precisa cancelar links individuais, coloque na frente um app que só entregue URLs de curta duração depois das suas próprias verificações.
As URLs pré-assinadas do S3 são seguras?
A assinatura não pode ser falsificada nem alterada e o bucket continua privado. Mas a AWS descreve as URLs pré-assinadas como bearer tokens: qualquer pessoa que obtenha o link pode usá-lo, quantas vezes quiser, até ele expirar. Mantenha tempos de expiração curtos, use HTTPS e evite publicar os links onde possam ser copiados.
As URLs pré-assinadas funcionam com Cloudflare R2, Backblaze B2 ou Wasabi?
Os provedores que implementam a API do S3 com Signature Version 4 em geral as suportam, e você usa as mesmas chamadas do SDK com o endpoint do provedor. O Cloudflare R2, por exemplo, suporta GET, HEAD, PUT e DELETE pré-assinados com uma expiração entre 1 segundo e 7 dias, mas não uploads por formulário POST nem domínios personalizados.
Por que minha URL pré-assinada retorna SignatureDoesNotMatch?
A AWS aponta três causas comuns: um relógio do computador dessincronizado, um proxy que altera os cabeçalhos ou a query string e uma requisição cujo método, cabeçalhos ou parâmetros são diferentes dos que foram assinados. Nos uploads, o culpado de sempre é enviar um Content-Type diferente do usado ao gerar a URL.
Fontes
Os preços e limites vêm das páginas oficiais abaixo. Os provedores mudam esses valores, então confirme antes de fechar qualquer compromisso.
- Amazon S3: baixar e enviar objetos com URLs pré-assinadas
- AWS IAM: métodos de autenticação (parâmetros de query SigV4)
- API do Amazon S3: autenticar requisições com parâmetros de query (SigV4)
- Referência da AWS CLI: s3 presign
- Referência do Boto3: generate_presigned_url
- AWS SDK for JavaScript v3: @aws-sdk/s3-request-presigner
- Amazon S3: limites do upload multipart
- AWS SDKs and Tools: proteções de integridade de dados para o Amazon S3
- Cloudflare R2: URLs pré-assinadas
- Backblaze: a API compatível com S3 do B2 suporta URLs pré-assinadas?