MAATRIX / Блог / Хотлинк и кража контента: как увидеть в логах и перекрыть

Хотлинк и кража контента: как увидеть в логах и перекрыть

MAATRIX

Однажды исходящий трафик за месяц оказывается в разы больше обычного, а посещаемость сайта не выросла ни на процент. Причина часто банальна: кто-то вставил <img src="https://ваш-домен/foto.jpg"> прямо в свой форум, паблик или маркетплейс, и теперь каждая загрузка этой страницы качает картинку с вашего сервера — на вашей полосе, за ваш счёт, без единого визита на ваш сайт. Это хотлинкинг, и он виден в access-логах почти сразу, если знать, на что смотреть. Ниже — как его найти, отличить от легитимных случаев и перекрыть, не отрубив при этом реальных посетителей.

Что такое хотлинкинг и почему это не мелочь

Хотлинкинг (hotlinking, leeching, inline linking) — это когда чужая страница ссылается напрямую на файл на вашем сервере вместо того, чтобы скопировать его к себе. Классика — <img> на фото, но так же уводят PDF, видео, шрифты, JS-библиотеки — всё, что отдаётся статикой по прямому URL.

Для сайта с большим объёмом медиаконтента (галереи, каталоги товаров, блоги с картинками, архивы фото) это не абстрактная проблема:

  • Вы платите за чужой трафик. Каждый показ хотлинкнутой картинки — это исходящий трафик с вашего сервера или VPS, который тарифицируется так же, как трафик к вашим настоящим посетителям. При включённом в тариф лимите вы просто быстрее его выбираете; при оплате за гигабайт сверх пакета — платите напрямую за чужой контент на чужом сайте.
  • Вы не получаете ничего взамен. В отличие от обычного реферального трафика, хотлинк не приводит посетителей — человек смотрит картинку на чужой странице и не переходит к вам. Это чистый расход без единого шанса на конверсию.
  • Нагрузка на диск и CPU. Если картинки отдаются не напрямую nginx, а проходят через бэкенд (генерация превью на лету, watermark-сервис, PHP-скрипт с readfile()), массовый хотлинк создаёт реальную нагрузку, неотличимую от обычного трафика по логам, пока вы не проверите Referer.
  • Могут воровать не только трафик. Иногда хотлинкнутый контент выдают за собственный — это ещё и вопрос авторства и репутации, если под вашей картинкой стоит чужой бренд.

Расход трафика от такой утечки редко бывает драматичным в моменте — счёт растёт постепенно, и его часто замечают только при разборе месячного биллинга. О том, как вообще устроен счёт за исходящий трафик и почему о перерасходе узнают с опозданием, — в статье про статью счёта за egress-трафик.

Как найти хотлинк в access-логах

Стандартный combined формат логов nginx уже содержит всё нужное — поле $http_referer пишется в каждой строке:

203.0.113.10 - - [28/Aug/2026:10:14:02 +0000] "GET /uploads/2024/kartinka.jpg HTTP/1.1" 200 184320 "https://forum-chuzhoy-sайт.ru/topic/4521" "Mozilla/5.0"

Здесь Referer https://forum-chuzhoy-sайт.ru/... — это адрес страницы, с которой браузер запросил картинку. Если это не ваш домен, запрос пришёл не с вашего сайта, а с чужой страницы, которая просто использует ваш файл как источник изображения.

Базовый разбор — вытащить поле Referer из строк, где запрашивается статика, и посчитать домены:

grep -E '\.(jpe?g|png|gif|webp|svg|pdf)(\?|$| )' /var/log/nginx/access.log \
  | awk -F'"' '{print $4}' \
  | awk -F/ '{print $3}' \
  | sort | uniq -c | sort -rn | head -30

Разбор команды: первый grep отбирает строки с запросами к медиафайлам, awk -F'"' берёт четвёртое поле между кавычками (это Referer в стандартном формате логов), второй awk -F/ вытаскивает из URL только хост. На выходе — список доменов-источников по убыванию частоты. Свой домен и - (пустой Referer) в этом списке — норма, их отсекаем на следующем шаге.

Отфильтровать всё, кроме чужих доменов, и посмотреть конкретные страницы, которые тянут ваш контент:

grep -E '\.(jpe?g|png|gif|webp)(\?|$| )' /var/log/nginx/access.log \
  | awk -F'"' '{print $4}' \
  | grep -v -E '^-$|maatrix\.io' \
  | sort | uniq -c | sort -rn | head -20

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

Полезно также прикинуть объём трафика на конкретный хотлинк — через grep по домену-источнику с суммированием $body_bytes_sent:

grep 'chuzhoy-sайт.ru' /var/log/nginx/access.log \
  | awk '{sum += $10} END {print sum/1024/1024 " MB"}'

Число условное — зависит от размера картинок и частоты запросов на конкретном сайте, но за сутки-двое уже видно порядок величины и стоит ли вообще тратить время на защиту от конкретного источника.

Общий подход к чтению логов — сначала сузить временное окно, потом искать конкретный паттерн, потом строить гипотезу — не специфичен для хотлинка и подробно разобран в статье как читать логи и находить причину сбоя. Похожая логика fильтрации по полю запроса применяется и при разборе других аномалий в access-логе, например при поиске следов SQL-инъекций — тот же приём «выдели поле, посчитай частоту, отдели шум от системного паттерна».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Защита через referrer-check в nginx: valid_referers

У nginx есть штатный модуль ngx_http_referer_module именно под эту задачу — директива valid_referers проверяет заголовок Referer против списка разрешённых доменов и выставляет переменную $invalid_referer в 1, если Referer не совпал ни с одним правилом.

Базовый вариант в блоке server или location для статики:

location ~* \.(jpe?g|png|gif|webp|svg)$ {
    valid_referers none blocked server_names
                   maatrix.io *.maatrix.io
                   yandex.ru *.yandex.ru
                   ~\.google\.;

    if ($invalid_referer) {
        return 403;
    }

    root /var/www/maatrix/uploads;
}

Что означают ключевые слова в списке:

ЗначениеЧто разрешает
noneReferer отсутствует вообще (пустой заголовок) — прямой заход, приложение, некоторые клиенты
blockedReferer присутствует, но искажён прокси/файрволом до значения без протокола (например, просто blocked)
server_namesReferer совпадает с любым server_name из конфигурации этого сервера — удобно, чтобы не дублировать домен вручную
maatrix.io *.maatrix.ioЯвные домены и их поддомены
~\.google\.Regex-вариант для нестандартных случаев (например, поисковые превью)

Директива if внутри location в nginx в общем случае считается антипаттерном («if is evil») из-за побочных эффектов при использовании с rewrite и try_files, но проверка $invalid_referer с одним return — как раз тот редкий случай, который в документации nginx явно приводится как штатный пример использования valid_referers, и проблем с непредсказуемым поведением здесь не возникает.

Проверить работу можно локально curl с заголовком Referer:

# без Referer — должно пройти (none в списке)
curl -I https://maatrix.io/uploads/photo.jpg

# с чужим Referer — должно вернуть 403
curl -I -e "https://chuzhoy-sайт.ru/" https://maatrix.io/uploads/photo.jpg

# со своим доменом — должно пройти
curl -I -e "https://maatrix.io/gallery/" https://maatrix.io/uploads/photo.jpg

Если статика отдаётся не самим nginx, а проксируется на бэкенд (например, картинки генерируются или водятся через приложение), тот же принцип применяется на уровне location, который матчит путь к файлам, до proxy_pass — тогда невалидный Referer отсекается ещё на веб-сервере, не долетая до приложения и не тратя его ресурсы.

Пустой Referer: не блокировать агрессивно

Самая частая ошибка при настройке защиты от хотлинка — забыть none в списке valid_referers или добавить его, но потом переусердствовать где-то ещё в цепочке правил. Пустой или отсутствующий Referer — не признак хотлинка, это совершенно рядовая ситуация:

  • Прямой переход по адресу — пользователь вставил ссылку на картинку прямо в адресную строку, скачал файл через wget/curl без явного указания Referer, открыл сохранённую закладку.
  • Мобильные приложения и десктопные клиенты, которые обращаются к вашим файлам напрямую (RSS/Atom-ридеры, почтовые клиенты, мессенджеры, генерирующие превью ссылок) — многие из них вообще не отправляют Referer или отправляют его непредсказуемо.
  • Политика Referrer-Policy у самого браузера посетителя. Начиная с широкого перехода на strict-origin-when-cross-origin как значение по умолчанию в современных браузерах, при переходе с HTTPS на HTTPS другого домена браузер может отправлять только origin без пути, а в ряде конфигураций конфиденциальности (расширения, режим инкогнито, некоторые мобильные браузеры) Referer обрезается до пустого специально.
  • Кэширующие прокси и корпоративные файрволы, которые вычищают заголовок Referer при пересылке запроса — для них и существует ключевое слово blocked в valid_referers.
  • Поисковые боты и легитимные краулеры, индексирующие изображения (Yandex Images, Google Images) — часть из них не присылает Referer вовсе.

Если заблокировать пустой Referer наравне с чужими доменами, под раздачу попадут не хотлинкеры, а обычные посетители и полезные боты — а это прямая потеря видимости в поиске картинок и жалобы «у вас картинки не открываются». Правило простое: в valid_referers всегда держите none и blocked, а ужесточаете список только за счёт добавления известных вредных доменов, а не за счёт удаления этих двух ключевых слов.

Альтернатива: водяные знаки и подмена контента вместо блокировки

Жёсткий 403 — не единственный и не всегда лучший ответ на хотлинк. У него есть два минуса: во-первых, легитимный Referer иногда определяется неточно (сложные CDN-цепочки, прокси, о которых вы не знаете), и 403 в этом случае бьёт по реальным посетителям без предупреждения; во-вторых, чистая блокировка не даёт вам вообще ничего — ни трафика, ни узнаваемости, только пустое место на чужой странице.

Рабочая альтернатива — вместо ошибки подменять контент. Для картинок это либо водяной знак с вашим доменом (переиспользует утечку как бесплатную рекламу — на чужой странице всё равно виден ваш бренд), либо заглушка «изображение недоступно, смотрите оригинал на maatrix.io».

Через nginx это делается связкой error_page и именованного location:

location ~* \.(jpe?g|png|gif|webp)$ {
    valid_referers none blocked server_names maatrix.io *.maatrix.io;

    if ($invalid_referer) {
        return 403;
    }

    error_page 403 = @hotlink_stub;
    root /var/www/maatrix/uploads;
}

location @hotlink_stub {
    root /var/www/maatrix/stubs;
    try_files /hotlink-placeholder.jpg =404;
}

Здесь при невалидном Referer вместо честного 403 отдаётся один и тот же лёгкий файл-заглушка — например, картинка с надписью «оригинал на maatrix.io» и ссылкой в подписи (текст на самой картинке, ссылку в HTML чужой страницы вы не контролируете). Заглушка весит в разы меньше оригинала, поэтому даже при массовом хотлинке экономия трафика ощутима, а хотлинкер получает не битую иконку на своей странице, а рабочую картинку с вашим брендом — что снижает шанс, что он вообще заметит и станет обходить защиту.

Для по-настоящему ценного контента (платные статьи, эксклюзивные фото, видео) Referer-проверка и подмена — недостаточная защита: оба метода обходятся подменой заголовков. Там нужен другой уровень — временные подписанные URL с истекающим сроком действия (secure_link модуль nginx или аналогичная логика на бэкенде), при которой прямая ссылка без валидной подписи вообще не отдаёт файл. Это отдельная, более тяжёлая настройка — имеет смысл вводить её только там, где риск утечки ощутимо дороже, чем сложность реализации.

Мониторинг и постоянный контроль

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

Практичный минимум:

  • Периодический топ рефереров. Раз в неделю прогонять тот же awk-разбор по свежему логу и смотреть, не появился ли новый систематический источник хотлинка среди чужих доменов.
  • GoAccess в реальном времени — интерактивный анализатор access.log с разбивкой по Referer «из коробки», удобен, чтобы увидеть аномальный всплеск запросов к конкретной картинке без ручного grep.
  • Алерт на резкий рост трафика к конкретному URL — если один и тот же файл внезапно даёт непропорционально много запросов относительно остального сайта, это чаще хотлинк, чем органический рост популярности.
  • Оптимизация самих файлов как защита по умолчанию. Даже если источник хотлинка не поймать мгновенно, меньший вес картинок снижает цену каждой утечки. Это отдельная тема, разобранная в статье про оптимизацию картинок как способ сэкономить на трафике — правильно сжатые и правильно отданные (WebP/AVIF, ленивая загрузка, кэш-заголовки) файлы уменьшают убыток от каждого хотлинкнутого показа независимо от того, нашли вы источник или нет.

Если файлов много и они лежат на нескольких серверах, поддерживать список valid_referers вручную в каждом конфиге неудобно — логичнее вынести правило в общий map-конфиг и переиспользовать его во всех location, либо перенести проверку на уровень CDN/прокси перед серверами, если он есть в цепочке.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Правда ли, что хотлинкинг можно полностью исключить настройками сервера?

Нет. Referer — это заголовок, который отправляет клиент, и его можно подделать вручную (например, curl -e) или через расширение браузера. valid_referers останавливает подавляющее большинство автоматического и «ленивого» хотлинка — прямые <img>-ссылки из чужих шаблонов, — но не защищает от целенаправленного обхода. Для контента, где это критично, нужны подписанные URL, а не только проверка Referer.

Стоит ли блокировать сразу все внешние домены, кроме своего?

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

Что делать, если Referer правильный, но запросы всё равно похожи на хотлинк — с одного IP и очень часто?

Это уже не задача valid_referers, а вопрос лимита частоты запросов — limit_req в nginx по IP или User-Agent, отдельно от проверки источника ссылки. Referer-проверка и rate-limiting решают разные проблемы и обычно применяются вместе.

Может ли заглушка навредить SEO своих же картинок?

Нет, если условие построено правильно: подмена срабатывает только при невалидном Referer, а запросы без Referer (в том числе от поисковых ботов) получают оригинал. Проверьте это через curl с пустым Referer перед включением правила на проде.

Нужно ли что-то менять в самих файлах, кроме конфига сервера?

Видимый водяной знак прямо в изображении — единственный способ сохранить брендинг и в тех редких случаях, когда серверная защита обойдена (кэш браузера, перезалитая на другой сервер копия файла). Серверная защита и водяной знак дополняют друг друга, а не заменяют.

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

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →