Статья

Presigned URL в S3: что это, как они работают, лимиты срока действия и примеры кода

Что такое presigned URL в S3, что значит каждый параметр X-Amz, лимит в 7 дней, код загрузки и скачивания (CLI, boto3, JS) и решение частых ошибок.

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

  1. Ваш код (или CLI) берёт bucket, ключ объекта, HTTP-метод и срок действия.
  2. SDK подписывает эти значения вашим secret access key с помощью AWS Signature Version 4. На этом этапе в S3 ничего не отправляется.
  3. Вы передаёте полученный URL кому-то: браузеру, коллеге, мобильному приложению.
  4. Когда им пользуются, 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-CredentialID ключа доступа плюс область действия: <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 дней. Он не поддерживает presigned POST, а 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 с HeadersNotSignedRange был подписан, а 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.

Источники

Цены и лимиты взяты с официальных страниц ниже. Провайдеры их меняют, поэтому проверьте перед тем, как принимать решение.