ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Nginx hotlink protection: referer, valid_referers и signed cookies

Хотлинк ворует трафик и ресурсы: изображения и видео грузятся с вашего сервера на чужих сайтах. Разберём защиту в Nginx через valid_referers и Referer, её слабые места и более надёжный вариант со signed cookies и TTL.
Nginx hotlink protection: referer, valid_referers и signed cookies

Что такое hotlink и почему «просто запретить» не всегда работает

Hotlink (хотлинк) — это когда чужой сайт вставляет ваши изображения или файлы по прямому URL, а трафик, CPU и дисковый I/O оплачиваете вы. В логах это выглядит как обычный запрос к .jpg/.png/.mp4, но источник часто палится заголовком Referer (или его отсутствием).

Типовой ущерб от хотлинка:

  • рост исходящего трафика и упирание в лимиты;
  • всплески RPS по статике, очереди на диске, рост времени ответа;
  • выгорание кешей (особенно на крупных «редких» файлах);
  • репутационные проблемы (ваш контент показывают на сомнительных площадках).

Самый частый путь защиты — проверка Referer (в Nginx это делается директивой valid_referers). Но важно понимать: Referer необязателен и легко подделывается клиентом. Поэтому правила по рефереру — это «срезать массовый хотлинк и снизить шум», а не криптографическая гарантия доступа.

Быстрый чек-лист перед настройкой защиты

Перед тем как резать запросы, проверьте, какие легитимные сценарии вы можете случайно сломать:

  • Мессенджеры, агрегаторы, почтовики часто ходят без Referer или с «обрезанным» реферером.
  • Privacy-режимы браузеров и политика Referrer-Policy могут отправлять только origin или пустой заголовок.
  • CDN/прокси/кеши иногда меняют набор заголовков и нюансы запроса.
  • Поддомены: cdn.example.com и www.example.com — разные хосты, их нужно учитывать явно.
  • Прямые скачивания (когда вы сами даёте URL) по определению часто будут «без реферера».

Практика: начинайте с режима «наблюдения» — логируйте подозрительные рефереры, но не блокируйте. Через 1–3 дня у вас будет список легитимных исключений.

Анализ логов Nginx для выявления хотлинка и подозрительных referer

Nginx hotlink protection через valid_referers: базовый рабочий вариант

В Nginx проверка referer делается директивой valid_referers. Она выставляет встроенную переменную $invalid_referer: если реферер не прошёл проверку — переменная будет непустой (обычно 1).

Пример: защищаем изображения и отдаём 403 всем, кто не пришёл с наших доменов. При этом разрешаем пустой referer (иначе вы сломаете часть прямых открытий и превью).

server {
    listen 80;
    server_name example.com www.example.com;

    location ~* \.(jpg|jpeg|png|gif|webp|avif)$ {
        valid_referers none blocked example.com *.example.com;

        if ($invalid_referer) {
            return 403;
        }

        expires 30d;
        add_header Cache-Control "public";
    }
}

Что здесь важно:

  • none — разрешает запросы без заголовка Referer.
  • blocked — разрешает «скрытый» referer (когда заголовок есть, но пустой/обрезанный в некоторых режимах).
  • example.com *.example.com — разрешаем основной домен и поддомены.
  • Регулярка в location — только статика по расширениям, а не весь сайт.

Если вы держите сайты на виртуальном хостинге, такой фильтр часто даёт быстрый эффект: меньше лишнего трафика и меньше нагрузки на дисковую подсистему без сложных доработок приложения.

Вместо 403: редирект на «заглушку»

Иногда удобнее не запрещать, а подменять картинку (например, «hotlinking is not allowed»). Тогда вместо 403 делаем редирект на локальный файл.

location ~* \.(jpg|jpeg|png|gif|webp|avif)$ {
    valid_referers none blocked example.com *.example.com;

    if ($invalid_referer) {
        return 302 /hotlink-placeholder.png;
    }
}

location = /hotlink-placeholder.png {
    expires 5m;
    add_header Cache-Control "public";
}

Замечание: «заглушка» тоже станет популярным объектом. Дайте ей отдельный короткий кеш и следите за RPS.

Несколько доменов и окружений (prod + staging)

Если у вас отдельные домены под маркетинг/документацию/статику, перечисляйте их явно. Пример для продакшена и стейджинга:

valid_referers none blocked example.com *.example.com example.net *.example.net staging.example.com;
Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Типичные проблемы valid_referers: почему хотлинк всё равно бывает

Проверка referer — это фильтр, а не «замок». Вот что обычно обходят или ломают такие правила:

  • Подмена заголовка: любой скрипт/бот может отправить нужный Referer.
  • Отсутствие referer: если вы запрещаете none, вы режете часть законных клиентов (превью, приложения, некоторые браузеры).
  • Ложные срабатывания при агрессивной Referrer-Policy: реферер бывает только origin без path — это нормально.
  • Кэширование: если вы отдаёте заглушку или 403 и это попало в промежуточный кеш, можно поймать «плавающие» эффекты у реальных пользователей.

Если вам нужно именно ограничение доступа к файлу (а не просто защита от массовых встраиваний), используйте токены/подписи: заголовок Referer не предназначен для контроля доступа.

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

Усиление: signed cookies в Nginx для реального контроля доступа к статике

Когда задача — выдавать статику только тем, кто «получил право» (после авторизации/оплаты/выдачи временного доступа), лучше использовать подпись. В экосистеме Nginx чаще всего встречаются:

  • подпись URL (secure link) — токен в query string;
  • signed cookies — токен в cookie (обычно удобнее для браузера и медиаплееров, меньше светится в URL и логах).

Ниже — пример логики с использованием механизма secure link, где подпись и срок жизни передаются в cookie. Nginx проверяет подпись и TTL и только после этого отдаёт файл из защищённой директории.

Схема доступа к защищённой статике через signed cookies и TTL

Схема signed cookies (общая логика)

  1. Пользователь обращается к приложению (backend) и проходит проверку (логин, право доступа, лимиты).
  2. Backend выставляет cookies: sl_sig (подпись) и sl_exp (время истечения).
  3. Пользователь запрашивает файл /protected/file.mp4.
  4. Nginx проверяет, что cookies валидны для этого URI и времени.
  5. Если валидны — отдаёт файл; если нет — 403 или 410.

Конфигурация Nginx: проверка подписи из cookie

В этом примере подпись считается как MD5 от «expires + uri + secret», затем кодируется в URL-safe Base64. Это распространённый формат для secure_link. Секрет храните на сервере (не в репозитории) и ротируйте.

server {
    listen 80;
    server_name example.com;

    location /protected/ {
        secure_link $cookie_sl_sig,$cookie_sl_exp;
        secure_link_md5 "$secure_link_expires$uri my_secret_string";

        if ($secure_link = "") {
            return 403;
        }

        if ($secure_link = "0") {
            return 410;
        }

        expires 10m;
        add_header Cache-Control "private";

        try_files $uri =404;
    }
}

Как это читать:

  • secure_link получает пару значений «подпись, expires» из cookies.
  • secure_link_md5 задаёт, как вычислять ожидаемую подпись.
  • $secure_link = "" — подпись не совпала или параметров нет.
  • $secure_link = "0" — подпись корректная, но TTL истёк (удобно возвращать 410 Gone).

Как backend должен выставить signed cookies (псевдологика)

Nginx сам cookies не «подписывает» — это делает ваше приложение. Алгоритм должен совпадать с secure_link_md5. Упрощённо:

  1. Берёте expires (unix timestamp), например «текущее время + 600 секунд».
  2. Берёте uri, например /protected/video.mp4.
  3. Считаете MD5 от строки expires + uri + secret.
  4. Кодируете результат в Base64 URL-safe без = (как ожидает Nginx secure_link).
  5. Ставите cookies sl_sig и sl_exp на ваш домен и нужный path (/protected/).

Критичные детали, на которых чаще всего «сыпется» внедрение:

  • URI должен совпадать побайтно с тем, что видит Nginx: регистр, слеши, кодирование.
  • Если у вас есть rewrite или нормализация URL, заранее решите, что именно подписываете: исходный запрос или конечный $uri.
  • При раздаче через CDN часто проще подписывать URL, а не cookie: не каждый CDN/прокси корректно «протащит» cookies до origin.

Для таких сценариев удобнее держать статику на отдельном Nginx-инстансе и масштабировать его независимо от приложения — обычно это проще сделать на VDS, чем на общем окружении.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Комбинация подходов: referer как «шумодав», cookies как контроль доступа

На практике хорошо работает двухслойная модель:

  • valid_referers — на публичных ассетах, где важно отсечь массовый хотлинк и снизить трафик (часто с none/blocked).
  • signed cookies — на приватной раздаче (платный контент, личные файлы, временный доступ), где нужна реальная авторизация на уровне Nginx.

Например, для /assets/ оставляете мягкую защиту по referer, а для /protected/ включаете строгую проверку подписи и запрет раздачи без cookie.

Отладка: как понять, почему запрос заблокирован

Для hotlink-защиты диагностика важнее «идеального правила». Удобный приём — отдельный лог на защищаемую локацию и формат с нужными полями.

log_format hotlink '$remote_addr $host "$request" $status ref="$http_referer" invref="$invalid_referer" sig="$cookie_sl_sig" exp="$cookie_sl_exp" ua="$http_user_agent"';

server {
    location ~* \.(jpg|jpeg|png|gif|webp|avif)$ {
        access_log /var/log/nginx/hotlink.log hotlink;
        valid_referers none blocked example.com *.example.com;
        if ($invalid_referer) { return 403; }
    }

    location /protected/ {
        access_log /var/log/nginx/protected.log hotlink;
        secure_link $cookie_sl_sig,$cookie_sl_exp;
        secure_link_md5 "$secure_link_expires$uri my_secret_string";
        if ($secure_link = "") { return 403; }
        if ($secure_link = "0") { return 410; }
    }
}

Что смотреть в логах:

  • реальный ref (мобильные приложения и превью часто удивляют);
  • invref — отрабатывает ли valid_referers как вы ожидаете;
  • наличие cookie sl_sig/sl_exp на запросах к /protected/;
  • цепочки редиректов: иногда реферер теряется именно на переходе.

Рекомендации по безопасности и эксплуатации

Про if внутри location

В примерах используется if внутри location осознанно: сценарий с return относится к безопасным и понятным. Если хотите полностью уйти от if, делайте через map и проверку переменной, но для защиты от хотлинка это часто лишняя сложность.

Ротация секретов для signed cookies

Секрет в secure_link_md5 стоит периодически менять. Минимально практичный подход — поддерживать «старый» и «новый» секрет на период миграции (например, 1–2 TTL), чтобы не выбивать активные сессии. Важно заранее спланировать окно ротации и критерии успешного перехода по логам.

Коды ответа: 403 vs 404 vs 410

  • 403 — «нет прав», удобно для диагностики.
  • 404 — скрывает факт существования файла (иногда полезно против сканирования).
  • 410 — отличный сигнал «ссылка/доступ были валидными, но истекли» для подписей с TTL.

Готовые минимальные рецепты

Рецепт 1: простая защита картинок по referer

location ~* \.(jpg|jpeg|png|gif|webp|avif)$ {
    valid_referers none blocked example.com *.example.com;
    if ($invalid_referer) { return 403; }
    expires 30d;
}

Рецепт 2: строгая раздача /protected/ только по signed cookies

location /protected/ {
    secure_link $cookie_sl_sig,$cookie_sl_exp;
    secure_link_md5 "$secure_link_expires$uri my_secret_string";

    if ($secure_link = "") { return 404; }
    if ($secure_link = "0") { return 410; }

    add_header Cache-Control "private";
    try_files $uri =404;
}

Итог

Если нужно быстро прикрыть утечки трафика от массового хотлинка — начинайте с valid_referers и аккуратно решите, разрешать ли none/blocked. Это дешёвый и понятный фильтр.

Если требуется контроль доступа «по-настоящему» (TTL, привязка к URI, управляемость на стороне приложения) — используйте signed cookies (или подпись URL) и проверяйте подпись на уровне Nginx. Такой подход лучше переживает подмену заголовков и даёт предсказуемый доступ к защищённой статике.

Поделиться статьей

Вам будет интересно

Fail2ban 2025: защита от SSH brute force и nginx basic auth, настройка bantime/ignoreip и отладка OpenAI Статья написана AI (GPT 5)

Fail2ban 2025: защита от SSH brute force и nginx basic auth, настройка bantime/ignoreip и отладка

Fail2ban в 2025 всё так же спасает от перебора паролей: читает логи, находит ошибки входа и банит IP через фаервол. В статье — нас ...
2FA для SSH и sudo на Linux: TOTP через pam_google_authenticator без лишней боли OpenAI Статья написана AI (GPT 5)

2FA для SSH и sudo на Linux: TOTP через pam_google_authenticator без лишней боли

Практический гайд по внедрению TOTP-2FA в Linux через pam_google_authenticator: установка, создание секрета, настройка PAM и OpenS ...
SLO-мониторинг с node_exporter и blackbox_exporter: latency, доступность и error budget OpenAI Статья написана AI (GPT 5)

SLO-мониторинг с node_exporter и blackbox_exporter: latency, доступность и error budget

Пошагово собираем SLO-мониторинг на Prometheus: node_exporter для диагностики хоста и blackbox_exporter для внешних проверок. Счит ...