Presigned URL в S3 это временная ссылка на один объект в приватном bucket. В строке запроса у неё есть подпись, поэтому её владелец может скачать (или загрузить) этот объект без учётных данных AWS, пока ссылка не истечёт, а bucket при этом никогда не становится публичным. Вы создаёте её своими ключами, в коде или через AWS CLI, и она может действовать от 1 секунды до 7 дней. В этом руководстве разобрано, как она работает, что означает каждый параметр X-Amz-*, сколько она может действовать, код для скачивания и загрузки и ошибки, с которыми вы вероятнее всего столкнётесь. Лимиты взяты из документации AWS и Cloudflare на октябрь 2026 года.
Presigned URL в S3 вкратце
| Вопрос | Ответ |
|---|---|
| Что он разрешает? | Одну операцию (GET, PUT, HEAD, DELETE…) над одним объектом |
| Максимальный срок | 7 дней (604 800 секунд) с SigV4 и ключами пользователя IAM |
| Срок с временными ключами | Заканчивается вместе с сессией роли или учётными данными STS, что наступит раньше |
| Диапазон в консоли S3 | От 1 минуты до 12 часов |
| Срок по умолчанию | 3 600 с в AWS CLI и boto3, 900 с в presigner для JavaScript v3 |
| Можно ли использовать несколько раз? | Да, сколько угодно раз, пока не истечёт срок |
| Можно ли отозвать один URL? | Нет, только отозвав учётные данные подписи или запретив его в политике |
| Обращается ли создание к S3? | Нет, подпись вычисляется локально |
Как работает presigned URL
- Ваш код (или CLI) берёт bucket, ключ объекта, HTTP-метод и срок действия.
- SDK подписывает эти значения вашим secret access key с помощью AWS Signature Version 4. На этом этапе в S3 ничего не отправляется.
- Вы передаёте полученный URL кому-то: браузеру, коллеге, мобильному приложению.
- Когда им пользуются, S3 заново вычисляет подпись с secret, принадлежащим ключу доступа из URL. Если подпись совпадает, срок URL не истёк и подписавшему разрешена эта операция, запрос выполняется.
URL несёт права того, кто его подписал. AWS прямо об этом говорит: если пользователь или роль IAM, создавшие URL, не могут читать объект, URL вернёт 403, какой бы действительной ни была подпись.
Что означают параметры X-Amz
Presigned URL с SigV4 выглядит так (переносы строк добавлены):
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
| Параметр | Значение |
|---|---|
X-Amz-Algorithm | Алгоритм подписи: AWS4-HMAC-SHA256 для SigV4 |
X-Amz-Credential | ID ключа доступа плюс область действия: <key-id>/<YYYYMMDD>/<region>/s3/aws4_request |
X-Amz-Date | Момент подписи URL в UTC, yyyyMMddTHHmmssZ |
X-Amz-Expires | Сколько секунд URL действует после X-Amz-Date, от 1 до 604800 |
X-Amz-SignedHeaders | Заголовки, охваченные подписью, всегда как минимум host |
X-Amz-Signature | Подпись, 64 шестнадцатеричных символа |
X-Amz-Security-Token | Есть только если URL подписан временными учётными данными (STS, роли, EC2, ECS, SSO) |
Измените любую подписанную часть (ключ, метод, срок или подписанный заголовок), и подпись перестанет совпадать. Обратите внимание и на то, что URL раскрывает открытым текстом: имя bucket, полный ключ объекта и ID вашего ключа доступа. Секретного ключа в URL никогда нет.
Сколько действует presigned URL
Лимит зависит от учётных данных, которыми он подписан:
| Чем подписан | Максимальный срок |
|---|---|
| Ключи доступа пользователя IAM (SigV4) | 7 дней, потому что сам ключ подписи SigV4 действует 7 дней |
| Сессия роли IAM (STS AssumeRole) | До конца сессии, по умолчанию 1 час |
| Профиль инстанса EC2 | До ротации учётных данных роли, максимум около 6 часов |
| Роль задачи ECS | До ротации учётных данных задачи, обычно раз в период от 1 до 6 часов |
| Любые отозванные или удалённые учётные данные | Перестаёт работать сразу, что бы ни говорил X-Amz-Expires |
| Консоль S3 | Вы выбираете от 1 минуты до 12 часов |
Это объясняет самый частый сюрприз: вы просите 7 дней из функции Lambda или инстанса EC2, а ссылка умирает через час или несколько часов. URL живёт ровно столько, сколько стоящие за ним учётные данные. Если нужны долгие ссылки, подписывайте их отдельным пользователем IAM, у которого есть доступ на чтение только к этому bucket.
S3 проверяет срок в момент начала запроса. Большое скачивание, начатое за секунду до крайнего срока, завершится нормально, но повторная попытка после него завершится ошибкой AccessDenied с сообщением «Request has expired». Запрос больше 604 800 секунд сразу отклоняется.
Как создать presigned URL
AWS CLI
aws s3 presign s3://my-bucket/reports/q3.pdf --expires-in 3600
Команда подписывает URL для скачивания (GET). По умолчанию 3 600 секунд, максимум 604 800. Свежие версии CLI всегда используют SigV4, поэтому убедитесь, что регион настроен. Для URL загрузки нужен 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,
)
Укажите signature_version='s3v4' явно. Справочник boto3 предупреждает, что ради обратной совместимости иначе он может подписывать устаревшей Signature Version 2 в регионах, которые её ещё принимают, а она молча отбрасывает некоторые параметры и заголовки, например Range. ResponseContentDisposition необязателен: он заставляет браузер сохранить файл с аккуратным именем, а не открывать его во вкладке.
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 }
)
Если не указать expiresIn, presigner для JavaScript использует 900 секунд (15 минут).
Presigned URL для загрузки (PUT)
Та же идея работает и в обратную сторону. Вы подписываете PutObject для одного ключа, и владелец URL может записать этот объект. Файл идёт прямо с его устройства в bucket, а ваш сервер никогда не обрабатывает байты. Если объект с таким ключом уже существует, он заменяется.
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,
})
Добавление content-type в signableHeaders привязывает URL к этому типу, поэтому загрузка с другим Content-Type отклоняется. В boto3 аналог это generate_presigned_url('put_object', Params={'Bucket': ..., 'Key': ..., 'ContentType': 'application/pdf'}), и клиент должен отправить тот же заголовок.
Три вещи, которые стоит проверить перед загрузкой из браузера:
- CORS. В bucket нужно правило CORS, разрешающее ваш источник и метод
PUT. Для загрузок multipart оно также должно открывать заголовокETag, потому что браузеру нужно прочитать его из каждой части. - Фиксированный ключ. Генерируйте уникальный ключ на сервере (например, с префиксом UUID), чтобы посетитель не мог перезаписать чужой файл.
- Контрольные суммы. Современные AWS SDK по умолчанию добавляют к загрузкам контрольную сумму CRC. Если presigned-загрузки к S3-совместимому провайдеру падают с ошибками подписи или контрольной суммы, задайте в клиенте
requestChecksumCalculation: 'WHEN_REQUIRED'.
Presigned POST это отдельный механизм для обычных HTML-форм с политикой, которая может ограничивать размер и тип содержимого. Его поддерживают не все S3-совместимые провайдеры (R2 не поддерживает), поэтому presigned PUT более переносимый вариант.
Большие файлы: presigned-загрузки multipart
Одиночный PUT в S3 ограничен 5 ГБ, и AWS советует multipart примерно от 100 МБ. Multipart разбивает файл на части, до 10 000 штук, каждая от 5 МиБ до 5 ГиБ, кроме последней, и они загружаются независимо, параллельно и с повторами для каждой части. Подпись работает по частям: сервер начинает загрузку, подписывает один URL UploadPart на каждую часть, браузер отправляет каждый фрагмент и возвращает его ETag, а сервер завершает загрузку.
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 }] },
})
)
Добавьте правило жизненного цикла, которое отменяет незавершённые загрузки multipart через несколько дней, иначе брошенные части продолжат занимать оплачиваемое место.
Presigned URL в R2, B2, Spaces и Wasabi
Провайдеры, реализующие API S3 с SigV4, принимают тот же код: вы меняете только endpoint, регион и ключи. Детали различаются, поэтому сверяйтесь с документацией провайдера:
- Cloudflare R2 поддерживает presigned
GET,HEAD,PUTиDELETEсо сроком от 1 секунды до 7 дней. Он не поддерживает presignedPOST, а presigned URL работают только на домене API S3 (<ACCOUNT_ID>.r2.cloudflarestorage.com), а не на собственных доменах. Используйтеregion: 'auto'. - Backblaze B2 заявляет, что его S3-совместимый API поддерживает presigned URL, созданные через AWS CLI или SDK.
- DigitalOcean Spaces и Wasabi работают с теми же вызовами SDK, направленными на их endpoint.
Цены каждого провайдера смотрите в сравнении S3, R2, Spaces, Wasabi и B2.
Частые ошибки presigned URL и как их исправить
| Ошибка | Обычная причина | Решение |
|---|---|---|
SignatureDoesNotMatch | Метод, заголовки или строка запроса отличаются от подписанных; часы рассинхронизированы | Отправляйте тот же Content-Type и заголовки; синхронизируйте часы через NTP |
AccessDenied «Request has expired» | URL просрочен: прошло больше X-Amz-Date + X-Amz-Expires | Создайте новый URL; сократите промежуток между подписью и использованием |
| URL истекает раньше запрошенного | Подписан временными учётными данными (роль, EC2, ECS, Lambda) | Подписывайте учётными данными, которые живут столько, сколько нужна ссылка |
ExpiredToken | Сессия STS за X-Amz-Security-Token завершилась | Обновите учётные данные перед подписью |
403 AccessDenied на свежем URL | У подписавшего нет s3:GetObject или s3:PutObject, или политика запрещает | Выдайте право субъекту подписи; проверьте политики bucket |
AuthorizationQueryParametersError | Срок больше 604 800 секунд или неверная область действия учётных данных | Держите X-Amz-Expires не больше 7 дней; подписывайте в регионе bucket |
| Браузер блокирует из-за CORS | В bucket нет правила CORS для вашего источника и метода | Добавьте правило CORS с вашим источником, GET/PUT и открытым ETag |
AccessDenied с HeadersNotSigned | Range был подписан, а If-Range отправлен без подписи | Добавьте If-Range в подписанные заголовки |
Корпоративные прокси, которые переписывают заголовки или строки запроса, тоже ломают подписи. Если URL работает из одной сети и не работает из другой, проверьте без прокси.
Лучшие практики безопасности
AWS называет presigned URL токенами на предъявителя и рекомендует их защищать. На практике:
- Делайте их короткими. Минуты для загрузок и чувствительных файлов, часы для обычных скачиваний. Создавайте новый, когда файл снова кому-то понадобится.
- Подписывайте узкой учётной записью. Отдельный пользователь или роль IAM, которые могут только читать (или только писать) нужный префикс, ограничивают ущерб, если ссылка утечёт.
- Добавьте ограничения в политику bucket. Условие
s3:signatureAgeотклоняет подписи старше заданного возраста, аaws:SourceIpограничивает, из каких сетей ими можно пользоваться. - Не пишите их в логи. Полные presigned URL в логах приложения, аналитике или истории чатов это рабочие учётные данные, пока не истечёт их срок.
- Учитывайте, чего у них нет. Нет пароля, счётчика скачиваний и отзыва отдельной ссылки, поэтому, если всё это нужно, поставьте перед хранилищем приложение.
Подробнее о стороне обмена, с вариантами, которые добавляют эти механизмы контроля, читайте в статье как делиться файлами S3, не делая bucket публичным.
Как Nubbo использует presigned URL
Nubbo построен на этом принципе. Когда вы загружаете файл в проводнике, сервер подписывает PUT (или, начиная со 100 МБ, загрузку multipart с тремя частями параллельно), и ваш браузер отправляет байты прямо в ваш bucket. Скачивания отдельных файлов используют presigned URL GET, действующие один час. Ваши ключи доступа хранятся в зашифрованном виде (AES-256-GCM) и используются только на сервере для подписи.
Ссылки для доступа добавляют то, чего нет у голых presigned URL: пароль, срок действия и лимит скачиваний по желанию, которые Nubbo проверяет, прежде чем выдать короткоживущий URL, а ссылку можно отключить в любой момент. ZIP-архивы с несколькими файлами собираются в вашем браузере, который скачивает каждый файл прямо из вашего bucket: Nubbo никогда не видит их содержимого. Одно честное исключение: миниатюры записываются в ваш собственный bucket в .nubbo-thumbs/. Всё работает одинаково с Amazon S3, Cloudflare R2, DigitalOcean Spaces, Wasabi, Backblaze B2 и публичными S3-совместимыми endpoint.
Частые вопросы
Что такое presigned URL в S3?
Это обычная HTTPS-ссылка на один объект в приватном bucket с подписью, добавленной в строку запроса. Тот, у кого есть ссылка, может выполнить единственную операцию, для которой она подписана, обычно скачивание (GET) или загрузку (PUT), пока срок не истечёт. Bucket остаётся приватным, а тому, кто пользуется ссылкой, не нужны учётные данные AWS.
Какой максимальный срок действия presigned URL в S3?
Семь дней (604 800 секунд) при подписи Signature Version 4 с долгосрочными ключами пользователя IAM. URL, подписанный временными учётными данными, например принятой ролью или профилем инстанса EC2, перестаёт работать, когда истекают эти учётные данные, даже если вы просили больше. В консоли S3 можно выбрать от 1 минуты до 12 часов.
Что означают X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders и X-Amz-Signature?
Это параметры аутентификации SigV4. Algorithm это метод подписи (AWS4-HMAC-SHA256), Credential это ID ключа доступа плюс область действия по дате, региону и сервису, Date это момент подписи URL, Expires это сколько секунд он действует, SignedHeaders перечисляет заголовки, охваченные подписью, а Signature это сама подпись. X-Amz-Security-Token появляется, только если URL подписан временными учётными данными.
Можно ли отозвать presigned URL в S3 до истечения срока?
Не по отдельности. Presigned URL перестаёт работать досрочно, только если учётные данные, которыми он подписан, отозваны, удалены или деактивированы, или если политика bucket запрещает запрос. Если нужно отменять отдельные ссылки, поставьте перед хранилищем приложение, которое выдаёт короткоживущие URL только после собственных проверок.
Безопасны ли presigned URL в S3?
Подпись нельзя подделать или изменить, а bucket остаётся приватным. Но AWS называет presigned URL токенами на предъявителя (bearer tokens): любой, кто получит ссылку, может пользоваться ею сколько угодно раз, пока не истечёт срок. Делайте срок действия коротким, используйте HTTPS и не публикуйте ссылки там, где их можно скопировать.
Работают ли presigned URL с Cloudflare R2, Backblaze B2 или Wasabi?
Провайдеры, реализующие API S3 с Signature Version 4, как правило, их поддерживают, и вы используете те же вызовы SDK с endpoint провайдера. Cloudflare R2, например, поддерживает presigned GET, HEAD, PUT и DELETE со сроком от 1 секунды до 7 дней, но не загрузки через POST-формы и не собственные домены.
Почему мой presigned URL возвращает SignatureDoesNotMatch?
AWS называет три частые причины: рассинхронизированные часы компьютера, прокси, который меняет заголовки или строку запроса, и запрос, метод, заголовки или параметры которого отличаются от подписанных. При загрузках обычно виноват Content-Type, отличный от того, что использовался при создании URL.
Источники
Цены и лимиты взяты с официальных страниц ниже. Провайдеры их меняют, поэтому проверьте перед тем, как принимать решение.
- Amazon S3: скачивание и загрузка объектов через presigned URL
- AWS IAM: методы аутентификации (параметры запроса SigV4)
- API Amazon S3: аутентификация запросов через параметры запроса (SigV4)
- Справочник AWS CLI: s3 presign
- Справочник Boto3: generate_presigned_url
- AWS SDK for JavaScript v3: @aws-sdk/s3-request-presigner
- Amazon S3: лимиты загрузки multipart
- AWS SDKs and Tools: защита целостности данных для Amazon S3
- Cloudflare R2: presigned URL
- Backblaze: поддерживает ли S3-совместимый API B2 presigned URL?