Una URL prefirmada de S3 es un enlace temporal a un objeto de un bucket privado. Lleva una firma en la query string, así que quien la tiene puede descargar (o subir) ese objeto sin credenciales de AWS hasta que caduca, y el bucket nunca se hace público. La generas con tus propias claves, desde tu código o desde la AWS CLI, y puede durar desde 1 segundo hasta 7 días. En esta guía verás cómo funciona, qué significa cada parámetro X-Amz-*, cuánto puede durar, código para descargas y subidas y los errores que más te vas a encontrar. Los límites salen de la documentación de AWS y Cloudflare a octubre de 2026.
URLs prefirmadas de S3 en resumen
| Pregunta | Respuesta |
|---|---|
| ¿Qué permite? | Una operación (GET, PUT, HEAD, DELETE…) sobre un objeto |
| Caducidad máxima | 7 días (604.800 segundos) con SigV4 y claves de usuario IAM |
| Con credenciales temporales | Termina cuando acaba la sesión del rol o las credenciales STS |
| Rango en la consola de S3 | De 1 minuto a 12 horas |
| Caducidad por defecto | 3.600 s en la AWS CLI y boto3, 900 s en el presigner de JavaScript v3 |
| ¿Se puede usar varias veces? | Sí, todas las que se quiera hasta que caduca |
| ¿Se puede revocar una sola? | No, solo revocando las credenciales que firmaron o denegándola por política |
| ¿Crearla hace una llamada a S3? | No, la firma se calcula en local |
Cómo funciona una URL prefirmada
- Tu código (o la CLI) toma el bucket, la clave del objeto, el método HTTP y un tiempo de caducidad.
- El SDK firma esos valores con tu secret access key usando AWS Signature Version 4. En este paso no se envía nada a S3.
- Entregas la URL resultante a quien la necesite: un navegador, un compañero, una app móvil.
- Cuando la usan, S3 recalcula la firma con el secreto de la access key que aparece en la URL. Si coincide, no ha caducado y quien firmó tiene permiso para esa operación, la petición pasa.
La URL lleva los permisos de quien la firmó. AWS lo deja claro: si el usuario o rol IAM que generó la URL no puede leer el objeto, la URL devuelve 403 por muy válida que sea la firma.
Qué significan los parámetros X-Amz
Una URL prefirmada con SigV4 tiene este aspecto (con saltos de línea añadidos):
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 | El algoritmo de firma: AWS4-HMAC-SHA256 en SigV4 |
X-Amz-Credential | Access key ID más el ámbito: <key-id>/<AAAAMMDD>/<región>/s3/aws4_request |
X-Amz-Date | Cuándo se firmó la URL, en UTC, con formato yyyyMMddTHHmmssZ |
X-Amz-Expires | Cuántos segundos es válida desde X-Amz-Date, de 1 a 604800 |
X-Amz-SignedHeaders | Cabeceras incluidas en la firma, como mínimo host |
X-Amz-Signature | La firma, 64 caracteres hexadecimales |
X-Amz-Security-Token | Solo aparece si se firmó con credenciales temporales (STS, roles, EC2, ECS, SSO) |
Si cambias cualquier parte firmada (la clave, el método, la caducidad o una cabecera firmada), la firma deja de coincidir. Fíjate también en lo que la URL deja ver en claro: el nombre del bucket, la clave completa del objeto y tu access key ID. La clave secreta nunca va en la URL.
Cuánto dura una URL prefirmada
El límite depende de las credenciales con que la firmas:
| Firmada con | Validez máxima |
|---|---|
| Claves de un usuario IAM (SigV4) | 7 días, porque la clave de firma de SigV4 solo vale 7 días |
| Sesión de un rol IAM (STS AssumeRole) | Hasta que termina la sesión, 1 hora por defecto |
| Perfil de instancia EC2 | Hasta que rotan las credenciales del rol, como mucho unas 6 horas |
| Rol de tarea de ECS | Hasta que rotan las credenciales, normalmente cada 1 a 6 horas |
| Credenciales revocadas o borradas | Deja de funcionar al momento, diga lo que diga X-Amz-Expires |
| Consola de S3 | Eliges entre 1 minuto y 12 horas |
Esto explica la sorpresa más común: pides 7 días desde una función Lambda o una instancia EC2 y el enlace muere en una hora o unas pocas horas. La URL dura lo que duran las credenciales que hay detrás. Si necesitas enlaces largos, fírmalos con un usuario IAM dedicado que solo tenga acceso de lectura a ese bucket.
S3 comprueba la caducidad al empezar cada petición. Una descarga grande que arranca un segundo antes del límite termina con normalidad, pero un reintento después del límite falla con AccessDenied y el mensaje “Request has expired”. Si pides más de 604.800 segundos, la petición se rechaza directamente.
Cómo generar una URL prefirmada
AWS CLI
aws s3 presign s3://my-bucket/reports/q3.pdf --expires-in 3600
El comando firma una URL de descarga (GET). Por defecto dura 3.600 segundos y como máximo 604.800. Las versiones actuales de la CLI usan siempre SigV4, así que asegúrate de tener una región configurada. Para URLs de subida necesitas un 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,
)
Indica signature_version='s3v4' de forma explícita. La referencia de boto3 avisa de que, por compatibilidad, si no lo haces puede firmar con la antigua Signature Version 2 en las regiones que aún la aceptan, y eso descarta sin avisar algunos parámetros y cabeceras como Range. ResponseContentDisposition es opcional: hace que el navegador guarde el archivo con un nombre limpio en vez de abrirlo.
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 }
)
Si no pasas expiresIn, el presigner de JavaScript usa 900 segundos (15 minutos).
URLs prefirmadas de subida (PUT)
La misma idea funciona en sentido contrario. Firmas un PutObject para una clave y quien tenga la URL puede escribir ese objeto. El archivo va directo de su dispositivo al bucket y tu servidor nunca toca los bytes. Si ya existe un objeto con esa clave, se sustituye.
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,
})
Añadir content-type a signableHeaders ata la URL a ese tipo, así que una subida con otro Content-Type se rechaza. En boto3 el equivalente es generate_presigned_url('put_object', Params={'Bucket': ..., 'Key': ..., 'ContentType': 'application/pdf'}), y el cliente tiene que enviar la misma cabecera.
Tres cosas que revisar antes de subir desde un navegador:
- CORS. El bucket necesita una regla CORS que permita tu origen y el método
PUT. Para subidas multiparte también debe exponer la cabeceraETag, porque el navegador tiene que leerla de cada parte. - Clave fija. Genera en el servidor una clave única (por ejemplo, con un prefijo UUID) para que nadie pueda sobrescribir el archivo de otra persona.
- Checksums. Los SDK actuales de AWS añaden por defecto un checksum CRC a las subidas. Si las subidas prefirmadas fallan contra un proveedor compatible con S3 con errores de firma o de checksum, configura
requestChecksumCalculation: 'WHEN_REQUIRED'en el cliente.
El POST prefirmado es otro mecanismo, pensado para formularios HTML, con una política que puede limitar tamaño y tipo. No todos los proveedores compatibles con S3 lo admiten (R2 no), así que el PUT prefirmado es la opción más portable.
Archivos grandes: subidas multiparte prefirmadas
Un único PUT a S3 tiene un límite de 5 GB, y AWS recomienda multiparte a partir de unos 100 MB. La subida multiparte divide el archivo en hasta 10.000 partes, de 5 MiB a 5 GiB cada una salvo la última, que se suben por separado, en paralelo y con reintentos por parte. La firma va por parte: el servidor inicia la subida, firma una URL UploadPart por cada parte, el navegador envía cada trozo y devuelve su ETag, y el servidor completa la subida.
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 }] },
})
)
Añade una regla de ciclo de vida que aborte las subidas multiparte incompletas pasados unos días; si no, las partes abandonadas siguen ocupando almacenamiento facturable.
URLs prefirmadas en R2, B2, Spaces y Wasabi
Los proveedores que implementan la API de S3 con SigV4 aceptan el mismo código: solo cambias el endpoint, la región y las claves. Los detalles varían, así que revisa la documentación de cada uno:
- Cloudflare R2 admite
GET,HEAD,PUTyDELETEprefirmados, con una caducidad de 1 segundo a 7 días. No admitePOSTprefirmado, y las URLs prefirmadas solo funcionan en el dominio de la API S3 (<ACCOUNT_ID>.r2.cloudflarestorage.com), no en dominios personalizados. Usaregion: 'auto'. - Backblaze B2 indica que su API compatible con S3 admite URLs prefirmadas generadas con la AWS CLI o los SDK.
- DigitalOcean Spaces y Wasabi funcionan con las mismas llamadas del SDK apuntando a sus endpoints.
Para comparar lo que cuesta cada proveedor, mira la comparativa de S3, R2, Spaces, Wasabi y B2.
Errores comunes con URLs prefirmadas y cómo resolverlos
| Error | Causa habitual | Solución |
|---|---|---|
SignatureDoesNotMatch | Método, cabeceras o query distintos de lo firmado; reloj desincronizado | Envía el mismo Content-Type y cabeceras; sincroniza el reloj con NTP |
AccessDenied “Request has expired” | Ya pasó X-Amz-Date + X-Amz-Expires | Genera una URL nueva; acorta el tiempo entre firmarla y usarla |
| Caduca antes de lo pedido | Firmada con credenciales temporales (rol, EC2, ECS, Lambda) | Firma con credenciales que vivan lo que tiene que durar el enlace |
ExpiredToken | Terminó la sesión STS de X-Amz-Security-Token | Renueva las credenciales antes de firmar |
403 AccessDenied con una URL recién creada | Quien firma no tiene s3:GetObject o s3:PutObject, o una política lo deniega | Da el permiso a la identidad que firma; revisa las políticas del bucket |
AuthorizationQueryParametersError | Caducidad de más de 604.800 segundos o ámbito de credencial mal formado | Deja X-Amz-Expires en 7 días o menos; firma en la región del bucket |
| El navegador lo bloquea por CORS | El bucket no tiene regla CORS para tu origen y método | Añade una regla CORS con tu origen, GET/PUT y ETag expuesta |
AccessDenied con HeadersNotSigned | Se firmó Range pero se envió If-Range sin firmar | Añade If-Range a las cabeceras firmadas |
Los proxies corporativos que reescriben cabeceras o la query string también rompen las firmas. Si una URL funciona desde una red y no desde otra, prueba sin el proxy.
Buenas prácticas de seguridad
AWS llama a las URLs prefirmadas tokens al portador y recomienda protegerlas. En la práctica:
- Que duren poco. Minutos para subidas y archivos sensibles, horas para descargas normales. Genera una nueva cuando alguien vuelva a necesitar el archivo.
- Firma con una identidad acotada. Un usuario o rol IAM dedicado que solo pueda leer (o solo escribir) en el prefijo en cuestión limita el daño si se filtra un enlace.
- Añade límites en la política del bucket. La condición
s3:signatureAgerechaza firmas más antiguas de cierta edad yaws:SourceIprestringe desde qué redes se pueden usar. - No las guardes en logs. Una URL prefirmada completa en logs, analítica o un chat es una credencial válida hasta que caduca.
- Ten en cuenta lo que no tienen. No hay contraseña, ni contador de descargas, ni revocación por enlace, así que pon una aplicación delante si necesitas eso.
Para la parte de compartir en detalle, con opciones que añaden esos controles, lee cómo compartir archivos de S3 sin hacer público el bucket.
Cómo usa Nubbo las URLs prefirmadas
Nubbo se basa en este patrón. Cuando subes un archivo en el explorador, el servidor firma un PUT (o, a partir de 100 MB, una subida multiparte con tres partes en paralelo) y tu navegador envía los bytes directamente a tu bucket. Las descargas de un solo archivo usan URLs GET prefirmadas válidas durante una hora. Tus claves de acceso se guardan cifradas con AES-256-GCM y solo se usan en el servidor para firmar.
Los enlaces para compartir añaden lo que les falta a las URLs prefirmadas: contraseña, fecha de caducidad y límite de descargas opcionales que Nubbo comprueba antes de emitir una URL de vida corta, y puedes desactivar un enlace cuando quieras. Las descargas en ZIP de varios archivos las monta tu navegador, que descarga cada archivo directamente de tu bucket: Nubbo no ve el contenido. Una excepción, para ser claros: las miniaturas se guardan en tu propio bucket, en .nubbo-thumbs/. Funciona igual con Amazon S3, Cloudflare R2, DigitalOcean Spaces, Wasabi, Backblaze B2 y endpoints compatibles con S3 accesibles públicamente.
Preguntas frecuentes
¿Qué es una URL prefirmada de S3?
Es un enlace HTTPS normal a un objeto de un bucket privado con una firma añadida en la query string. Quien tiene el enlace puede hacer la única operación para la que se firmó, normalmente una descarga (GET) o una subida (PUT), hasta que caduca. El bucket sigue siendo privado y quien usa el enlace no necesita credenciales de AWS.
¿Cuánto puede durar como máximo una URL prefirmada de S3?
Siete días (604.800 segundos) si la firmas con Signature Version 4 y claves de un usuario IAM. Si la firmas con credenciales temporales, como un rol asumido o el perfil de una instancia EC2, deja de funcionar cuando caducan esas credenciales, aunque hayas pedido más tiempo. Desde la consola de S3 puedes elegir entre 1 minuto y 12 horas.
¿Qué significan X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders y X-Amz-Signature?
Son los parámetros de autenticación de SigV4. Algorithm es el método de firma (AWS4-HMAC-SHA256), Credential es tu access key ID con la fecha, la región y el servicio, Date es cuándo se firmó la URL, Expires cuántos segundos sigue siendo válida, SignedHeaders las cabeceras que cubre la firma y Signature la firma en sí. X-Amz-Security-Token solo aparece si se firmó con credenciales temporales.
¿Puedo revocar una URL prefirmada antes de que caduque?
No de una en una. Una URL prefirmada solo deja de funcionar antes de tiempo si revocas, borras o desactivas las credenciales que la firmaron, o si una política del bucket deniega la petición. Si necesitas anular enlaces concretos, pon delante una aplicación que entregue URLs de vida corta solo después de sus propias comprobaciones.
¿Son seguras las URLs prefirmadas de S3?
La firma no se puede falsificar ni modificar y el bucket sigue siendo privado. Pero AWS las describe como tokens al portador: cualquiera que consiga el enlace puede usarlo, tantas veces como quiera, hasta que caduque. Usa caducidades cortas, HTTPS y no publiques los enlaces donde se puedan copiar.
¿Funcionan las URLs prefirmadas con Cloudflare R2, Backblaze B2 o Wasabi?
Los proveedores que implementan la API de S3 con Signature Version 4 suelen admitirlas, con las mismas llamadas del SDK apuntando al endpoint del proveedor. Cloudflare R2, por ejemplo, admite GET, HEAD, PUT y DELETE prefirmados con una caducidad de 1 segundo a 7 días, pero no subidas POST de formulario ni dominios personalizados.
¿Por qué mi URL prefirmada devuelve SignatureDoesNotMatch?
AWS señala tres causas habituales: un reloj desincronizado, un proxy que modifica las cabeceras o la query string, y una petición cuyo método, cabeceras o parámetros no coinciden con los que se firmaron. En subidas, lo más habitual es enviar un Content-Type distinto del que se usó al generar la URL.
Fuentes
Los precios y límites salen de las páginas oficiales de abajo. Los proveedores los cambian, así que compruébalos antes de decidir.
- Amazon S3: Download and upload objects with presigned URLs
- AWS IAM: Authentication methods (SigV4 query parameters)
- Amazon S3 API: Authenticating requests with query parameters (SigV4)
- AWS CLI reference: s3 presign
- Boto3 reference: generate_presigned_url
- AWS SDK for JavaScript v3: @aws-sdk/s3-request-presigner
- Amazon S3: Multipart upload limits
- AWS SDKs and Tools: Data integrity protections for Amazon S3
- Cloudflare R2: Presigned URLs
- Backblaze: Does the B2 S3 Compatible API support presigned URLs?