Защита от скачивания в Nginx: подписанные ссылки
Подписанная ссылка позволяет открыть файл только до заданного времени и не раскрывать публичный путь к нему. Разбираем secure_link в Nginx 1.27.x, генерацию подписи и ограничения схемы.

Публичный URL защищённого файла перестаёт быть публичным, если Nginx проверяет подпись и срок действия до начала отдачи. В этой схеме сервер приложения создаёт ссылку вроде /protected/video.mp4?md5=...&expires=..., а Nginx сравнивает подпись с локальным секретом. Ни база данных на каждый запрос, ни отдельный обработчик скачивания не нужны. Ниже — настройка для Nginx 1.27.x и генератора ссылок на Python 3.12; проверка займёт несколько минут, если модуль уже собран в вашей установке.
Подписанная ссылка ограничивает доступ к URL, но не может отозвать файл, который уже скачали или скопировали в кеш.
Что именно защищает secure_link
Встроенный модуль ngx_http_secure_link_module получает из запроса подпись и время окончания действия, затем пересчитывает контрольную сумму из срока, URI и секрета. Если значения не совпали, запрос отклоняется. Если срок истёк, Nginx возвращает отдельный статус.
Для этой схемы используйте Nginx 1.27.x и выше в рамках актуальной ветки вашей ОС. Модуль secure link не всегда собирается по умолчанию: проверьте вывод nginx -V. В параметрах сборки должен быть --with-http_secure_link_module. Если его нет, потребуется пакет или сборка Nginx с этим модулем; точное имя пакета зависит от дистрибутива.
1 Подписанная ссылка — URL с короткоживущим доказательством того, что его создал доверенный сервер. Это не шифрование: путь, срок действия и параметры ссылки остаются видимыми пользователю.
Какой вариант выбрать
У подписанной ссылки есть цена: секрет нужно хранить на сервере, а уже выданный URL нельзя выборочно отозвать без смены секрета или дополнительного слоя проверки. Перед настройкой сравните схему с альтернативами.
| Подход | Где проверяется доступ | Сильная сторона | Ограничение |
|---|---|---|---|
| Публичный URL | Нигде | Минимум настроек | Ссылку можно переслать без ограничений |
| Nginx secure_link | На входе в Nginx | Быстрая проверка без запроса к приложению | Нельзя отозвать одну ссылку после выдачи |
| Приложение и X-Accel-Redirect | В приложении, затем в Nginx | Можно учитывать пользователя, тариф и статус заказа | Каждое обращение сначала проходит через приложение |
| Подписанный URL CDN | На CDN | Подходит для больших файлов и распределённой отдачи | Правила, стоимость и формат подписи зависят от CDN |
Для простого временного доступа к архивам, изображениям или видео обычно достаточно secure_link. Если нужно заблокировать конкретного пользователя, вести аудит скачиваний или учитывать оплату в реальном времени, переносите решение в приложение либо используйте связку приложения с Nginx.
Конфигурация Nginx
Предположим, файлы лежат в каталоге /srv/files, а наружу публикуется только путь /protected/. Секрет ниже — заглушка. Не вставляйте в конфигурацию токен из примера и не храните настоящий секрет в публичном репозитории.
http {
server {
listen 443 ssl;
server_name files.example.test;
root /srv/files;
location /protected/ {
# md5 и expires приходят в query string подписанной ссылки
secure_link $arg_md5,$arg_expires;
# Формула должна полностью совпадать с формулой генератора
secure_link_md5 "$secure_link_expires$uri YOUR_SHARED_SECRET";
# Пустое значение означает неверную или неполную подпись
if ($secure_link = "") {
return 404;
}
# 0 означает, что срок действия ссылки закончился
if ($secure_link = "0") {
return 410;
}
try_files $uri =404;
}
}
}
Важен порядок частей. Nginx получает подпись из md5, срок из expires, а затем вычисляет MD5 от строки срок + URI + пробел + секрет. Переменная $uri должна соответствовать пути, который подписывает генератор. Если подписать /protected/file.pdf, а запросить другой путь, проверка не пройдёт.
Директива if с единственным действием return в таком месте безопасна для этой задачи: она не переписывает URI и не меняет обработчик файла. После изменения конфигурации сначала выполните проверку синтаксиса, затем перечитайте конфигурацию: nginx -t и штатная команда reload для вашего способа установки.
Генерация ссылки на Python
Генерировать подпись нужно там, где доступен секрет: в приложении, отдельном сервисе выдачи ссылок или административной команде. Для формулы выше подойдёт такой самостоятельный фрагмент на Python 3.12.
import base64
import hashlib
import time
def make_signed_url(path: str, lifetime_seconds: int, secret: str) -> str:
if not path.startswith("/protected/"):
raise ValueError("path must start with /protected/")
if "?" in path:
raise ValueError("path must not contain a query string")
if lifetime_seconds <= 0:
raise ValueError("lifetime_seconds must be positive")
expires = int(time.time()) + lifetime_seconds
value = f"{expires}{path} {secret}".encode("utf-8")
digest = hashlib.md5(value).digest()
signature = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
return f"{path}?md5={signature}&expires={expires}"
signed_url = make_signed_url(
"/protected/manual.pdf",
lifetime_seconds=900,
secret="YOUR_SHARED_SECRET",
)
print(signed_url)
В URL-параметре используется URL-safe Base64 без символов заполнения =. Это соответствует формату, который ожидает модуль. В реальном HTTP-ответе символ & передаётся как обычный амперсанд; в HTML-коде его нужно экранировать как &. В примере выше он экранирован только потому, что код находится внутри HTML.
Срок в примере равен 900 секундам, то есть 15 минутам. Выбирайте его по сценарию: для одноразовой кнопки скачивания — короче, для большого файла на медленном канале — дольше. Продление ссылки не продлевает уже начатую передачу и не удаляет старую копию.
Что ломается первым
Ссылка всегда возвращает 404
Проверьте, что модуль собран, параметры называются именно md5 и expires, а генератор подписывает тот же URI, который видит Nginx. Частая ошибка — подписать URL с доменом или полным протоколом, хотя в формулу входит только $uri.
Ссылка сразу возвращает 410
Значение expires уже меньше текущего времени Nginx. Убедитесь, что часы приложения и сервера синхронизированы через системную службу времени. Не округляйте срок в одну сторону вручную: используйте Unix timestamp в секундах.
Подпись отличается только иногда
Проверьте кодировку URL и нормализацию пути. Процентное кодирование, завершающий слеш, повторное декодирование прокси и изменение регистра могут дать другой URI. Подписывайте канонический путь в одном месте и не передавайте в него пользовательскую строку без нормализации.
Файл открывается без подписи
Значит, запрос попадает не в защищённый location. Это бывает, когда есть более специфичный блок, например отдельный location для расширения файла, или когда реальные файлы доступны через другой домен и другой серверный блок. Для проверки отправьте запрос к исходному URL без параметров и убедитесь, что он получает 404 или 410, а не файл.
Где схема ломается под нагрузкой и в проде
Проверка подписи дешёвая, но сама передача файла может быть дорогой. Nginx не знает, скачал ли клиент файл полностью, и не отменяет доступ по факту первой загрузки. Запрос с действующей ссылкой можно параллельно открыть с нескольких устройств.
- Кеши. CDN, reverse proxy или браузер могут сохранить ответ. Настройки кеширования должны соответствовать чувствительности файла; подписанный URL сам по себе не гарантирует отсутствие копии в кеше.
- Range-запросы. Клиент может скачивать файл частями. Если нужно ограничить число скачиваний, одной проверки срока недостаточно.
- Утечка URL. Ссылка может попасть в историю браузера, логи прокси, аналитику или заголовок Referer. Не добавляйте в URL лишние персональные данные.
- Ротация секрета. После смены секрета все ранее выданные ссылки перестанут работать. Планируйте короткий переходный период или отдельный слой проверки, если это неприемлемо.
- Публичное хранилище. Если файл доступен по другому пути, защита location бессмысленна. Закройте прямой путь и проверьте доступ к нему без подписи.
MD5 здесь используется как часть совместимого механизма secure_link, а не как хеширование паролей. Секрет должен оставаться на сервере и быть достаточно длинным случайным значением. Если требуется современная схема с HMAC, привязкой к пользователю или одноразовым использованием, реализуйте её в приложении или выберите CDN с соответствующим механизмом.
Что делать дальше
Сначала проверьте три запроса: действительная ссылка должна отдавать файл, ссылка с изменённым символом в md5 — 404, а ссылка с прошлым expires — 410. Затем добавьте метрики по статусам 404 и 410, размеру ответа и длительности скачивания. Отдельно проверьте CDN и прокси: они не должны превращать временный доступ в постоянный публичный кеш. Инвалидацию одной ссылки, учёт скачиваний и отзыв доступа конкретного пользователя рассматривайте как следующий слой, а не как задачу, которую решает secure_link.
Дефектный акт по фото: Осмотр по фотографиям: классификация дефектов, ссылки на нормы, требования к устранению. Готовый акт в Word.
Спросить за 80 ₽Источники: Nginx documentation: ngx_http_secure_link_module, Nginx documentation: ngx_http_core_module
Частые вопросы
Можно ли использовать подписанные ссылки без модуля secure_link?
Защищает ли ссылка от повторного скачивания?
Почему после изменения файла старая ссылка всё ещё работает?
Можно ли подписывать URL с query-параметрами?
Что делать, если файл уже доступен через CDN?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.