
Как сократить поверхность атаки Nginx
Проверяем сборку и модули Nginx, закрываем неизвестные домены и служебные файлы, ограничиваем методы и права, тестируем конфигурацию без бесполезной маскировки версии.
Содержание
Поверхность атаки — всё, с чем может взаимодействовать внешний клиент: открытые порты, виртуальные хосты, маршруты, модули, методы, служебные файлы и приложение за Nginx. Чем меньше лишних точек входа, тем проще понимать и проверять поведение сервера.
Усиление не сводится к копированию большого «security config». Начните с инвентаризации работающей сборки и опубликованных путей, затем уберите ненужное. Каждое ограничение проверяйте на законном пользовательском сценарии и сохраняйте способ возврата.
Узнайте, какой Nginx работает
Проверьте путь к бинарному файлу, параметры сборки и источник пакета. Команда nginx -V выводит версию, криптографическую библиотеку и configure arguments в поток ошибок, поэтому используется перенаправление 2>&1:
command -v nginx
nginx -V 2>&1
systemctl status nginx --no-pager
В аргументах видны встроенные модули и путь к динамическим модулям. Наличие модуля само по себе не означает уязвимость, но ненужная функция увеличивает код и число возможных ошибок конфигурации. Удалять модуль из пакетной сборки вручную посреди работ не следует: выберите поддерживаемый пакет или отдельную проверенную сборку.
Убедитесь, что пакет получает security updates из доверенного репозитория. Скрытый номер версии не защищает устаревший бинарный файл.
Проверьте открытые порты и владельцев
Nginx обычно должен слушать только HTTP/HTTPS, а приложение — локальный адрес или Unix-сокет. Следующие команды показывают TCP-порты и привязанные процессы, а затем активные правила UFW:
sudo ss -ltnp
sudo ufw status verbose
Если внутреннее приложение слушает 0.0.0.0:3000, его можно обойти мимо Nginx при разрешающем firewall. Переведите его на 127.0.0.1:3000, проверьте локальный запрос и только затем закройте публичный порт. Не удаляйте правило SSH во время удалённой сессии.
Не показывайте production-сайт на неизвестном домене
Для каждой пары адреса и порта задайте явный default server. Следующий HTTP-блок возвращает 404 любому имени, которое не совпало с вашими виртуальными хостами:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 404;
}
Параметр default_server, а не символ _, назначает этот блок запасным. Для HTTPS нужен отдельный default server и решение о сертификате: TLS начинается до чтения HTTP Host. Не копируйте случайный сертификат реального сайта в обработчик чужих имён без понимания результата.
Проверьте неизвестный Host отдельно. Он не должен раскрывать главную страницу, редиректить на значение из входного заголовка или попадать в приложение.
Публикуйте только подготовленный каталог
В root должны лежать файлы, предназначенные для выдачи: HTML, стили, скрипты, изображения и публичные документы. Репозиторий .git, исходные .env, дампы базы, журналы и резервные копии храните вне этого дерева.
Такое разделение надёжнее бесконечного списка запрещённых расширений. Новый тип резервной копии не станет публичным только потому, что его забыли добавить в regex.
Для дополнительной защиты закройте скрытые файлы, сохранив доступ к каталогу ACME challenge. Правило располагается внутри нужного server и требует теста на вашей версии PCRE:
location ~ /\.(?!well-known/) {
deny all;
}
Отрицательная проверка исключает путь /.well-known/. Не полагайтесь на неё как на единственный барьер: .env всё равно не должен находиться в публичном каталоге. После изменения проверьте выдачу сертификата и запросы к нескольким скрытым именам.
Запретите листинг каталогов
Автоматический список файлов обычно выключен по умолчанию, но это стоит подтвердить в полной конфигурации. Для публичного статического маршрута задайте поведение явно:
location /downloads/ {
autoindex off;
try_files $uri =404;
}
При запросе каталога без index-файла посетитель не должен получить перечень содержимого. Если список загрузок нужен продукту, формируйте контролируемую страницу с разрешёнными файлами, а не открывайте всё дерево.
Ограничьте методы только там, где это верно
Статический каталог обычно обслуживает GET и HEAD. В нём можно отклонить другие методы через limit_except:
location /assets/ {
limit_except GET HEAD {
deny all;
}
try_files $uri =404;
}
Не переносите правило на весь сервер без проверки. Формам нужен POST, CORS preflight может использовать OPTIONS, а API — PUT или DELETE. Nginx ограничивает сетевой вход, но само приложение всё равно должно проверять метод, права и входные данные.
Уберите ненужные служебные endpoints
Страница stub_status, метрики, health-check и административный маршрут полезны для эксплуатации, но не обязаны быть доступны всему интернету. Привяжите их к локальному или внутреннему listener, ограничьте сеть и не выводите секреты.
Проверка здоровья должна сообщать минимум, необходимый балансировщику. Версии библиотек, переменные окружения, пути и stack trace оставляйте в защищённом журнале.
Server tokens — косметическая мера
Следующая директива в контексте http, server или location убирает номер версии Nginx из поля Server и стандартных страниц ошибок:
server_tokens off;
Это уменьшает лишнюю информацию, но не скрывает сам тип сервера надёжно и не мешает проверять известную уязвимость. Не отмечайте задачу выполненной, пока пакет не обновлён и лишние маршруты остаются открытыми.
Проверьте права на конфигурацию и ключи
Master-процессу нужны права читать конфигурацию и TLS-ключи, worker-процессу — читать публичные файлы и писать только в необходимые каталоги кеша или временных данных. Не делайте весь /var/www доступным на запись www-data.
Проверьте путь к главному файлу и ключу без изменения прав:
namei -l /etc/nginx/nginx.conf
sudo namei -l /etc/letsencrypt/live/example.com/privkey.pem
sudo -u www-data test -r /var/www/example/current/index.html
Последняя команда должна завершиться кодом 0 для публичного файла. Worker обычно не должен читать приватный ключ. Пользователя www-data и пути замените значениями из активной конфигурации.
Проведите отрицательные проверки
После nginx -t и reload проверьте не только главную страницу, но и то, что должно быть недоступно. --resolve направляет тестовое имя на выбранный IP без изменения DNS:
sudo nginx -t
sudo systemctl reload nginx
curl -I --resolve example.com:443:127.0.0.1 https://example.com/
curl -I -H 'Host: unknown.invalid' http://127.0.0.1/
curl -I https://example.com/.env
curl -I -X POST https://example.com/assets/app.css
Ожидайте успешный ответ настоящего сайта и контролируемые 4xx для неизвестного домена, скрытого файла и неподходящего метода. Проверка /.env не доказывает отсутствие других секретов; дополнительно просмотрите состав каталога публикации.
Откат и регулярный пересмотр
При поломке законного маршрута верните только последнее правило, снова выполните nginx -t и reload. Не откатывайте security update ради совместимости со старой директивой: сначала найдите поддерживаемый вариант конфигурации.
После обновления Nginx повторяйте инвентаризацию модулей, портов и полной конфигурации. Новый виртуальный хост, proxy или каталог загрузок меняет поверхность атаки и требует собственных отрицательных тестов.
Права файлов подробно разобраны в статье о доступе Nginx к каталогу сайта, TLS — в руководстве по конфигурации HTTPS, а лимиты — в материале об ограничении запросов.
Первоисточники
- Nginx: ngx_http_core_module —
server_tokens,limit_except,rootи обработка URI. - Nginx: request processing — выбор default server и виртуального хоста.
- Nginx: command-line parameters — ключи проверки конфигурации и вывода собранных модулей.
- Nginx: controlling Nginx — сигналы reload, quit и порядок применения конфигурации.
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Повышает ли server_tokens off безопасность Nginx?
Директива убирает точную версию из некоторых ответов, но не исправляет уязвимость и не скрывает тип сервера надёжно. Основная защита — поддерживаемые обновления, минимальная конфигурация и ограниченный доступ.
Нужно ли запрещать все HTTP-методы кроме GET и HEAD?
Только для маршрута, который действительно является статическим и не принимает другие методы. API, WebDAV, загрузки и формы могут требовать POST, PUT или OPTIONS; ограничение проектируют по функции.
Почему каталог сайта не должен содержать репозиторий и резервные копии?
Ошибка location или MIME-настройки способна отдать посетителю .git, конфигурацию, дамп либо старую копию. Публичный root должен содержать только подготовленные к публикации файлы.


