К основному содержимому

Hardening Nginx: безопасная конфигурация веб-сервера

Безопасность · 19.04.2026 · 6 мин чтения
Иллюстрация к статье «Hardening Nginx: безопасная конфигурация веб-сервера»

Стандартная установка Nginx раскрывает версию сервера и не включает защитные HTTP-заголовки. Hardening занимает 20 минут и закрывает целый класс уязвимостей.

nginx.conf — базовые настройки

http {
    server_tokens off;  # Скрыть версию
    client_max_body_size 10m;
    client_body_timeout 12;
    client_header_timeout 12;
    keepalive_timeout 15;
    large_client_header_buffers 2 1k;
}

Защитные HTTP-заголовки

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

Rate Limiting

http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
}
server {
    location /api/ { limit_req zone=api burst=20 nodelay; }
    location /wp-login.php { limit_req zone=login burst=3; }
}

Запрет скрытых и опасных файлов

location ~ /\. { deny all; access_log off; log_not_found off; }
location ~* \.(bak|conf|sql|log|sh|ini)$ { deny all; }

SSL/TLS конфигурация

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;

Ограничение HTTP-методов

location / {
    limit_except GET HEAD POST { deny all; }
}
Используйте securityheaders.com для анализа HTTP-заголовков. Цель — оценка A или A+.
CSP (Content Security Policy) — мощнейший заголовок — требует тщательной настройки. Неправильный CSP сломает сайт. Начните с Content-Security-Policy-Report-Only перед боевым применением.

Разбор типичных ошибок после hardening

nginx: [emerg] invalid number of arguments in "add_header" directive

nginx: [warn] conflicting server name "example.com" on 0.0.0.0:443

403 Forbidden после включения limit_except

nginx: [emerg] "ssl_ciphers" directive is duplicate
СообщениеПричинаРешение
invalid number of arguments in "add_header"В значении заголовка пропущены кавычки, и Nginx разбил строку на несколько аргументов по пробеламОборачивать значение заголовка в двойные кавычки целиком, включая директивы вроде max-age=31536000
conflicting server name на 443Один и тот же server_name объявлен в двух server-блоках, слушающих один портПроверить nginx -T | grep -A2 server_name, оставить объявление только в одном файле
403 Forbidden после limit_exceptМетод запроса (часто PUT или PATCH от API-клиента) не входит в список разрешённыхДобавить нужный метод в limit_except GET HEAD POST только там, где он реально используется
"ssl_ciphers" directive is duplicateДиректива SSL задана и в http {}, и повторно в конкретном server {}Оставить директиву в одном месте — обычно на уровне http, если сертификаты и требования одинаковы для всех сайтов

Сравнение уровней hardening

МераРиск, который закрываетСложность внедрения
server_tokens off и защитные заголовкиУтечка версии сервера, clickjacking, MIME-sniffingНизкая, безопасно для любого сайта
Rate Limiting на логин и APIБрутфорс паролей, перебор эндпоинтовСредняя, нужно подобрать лимиты под реальный трафик
Запрет скрытых и служебных файловУтечка .env, .git, бэкапов .sql через прямой URLНизкая, но легко забыть новое расширение при добавлении сервисов
Content-Security-PolicyXSS через инжект стороннего JSВысокая, требует инвентаризации всех внешних скриптов сайта

Когда этот hardening недостаточен

  • Атака идёт через уязвимость в самом приложении (SQL-инъекция, небезопасная десериализация) — заголовки Nginx её не закрывают, нужен патч кода или WAF на уровне приложения
  • Нужна защита от распределённого перебора с тысяч IP — rate limiting по одному адресу здесь бессилен, требуется анализ поведения на уровне CDN/прокси перед сервером
  • Сайт использует сторонние встраиваемые виджеты (чаты, аналитика, платёжные формы) — строгий CSP без Report-Only периода почти гарантированно сломает часть из них при первом включении
  • Нужна защита данных при передаче между внутренними сервисами за пределами HTTP — hardening из этой статьи касается только публичного веб-трафика, а не внутренней сети

Как проверить, что защита реально работает

# Синтаксис конфигурации до применения на боевом сервере
sudo nginx -t

# Заголовки ответа сервера
curl -sI https://yourdomain.com/ | grep -Ei 'x-frame|x-content|strict-transport|referrer'

# Версия сервера не палится в заголовке и в служебных страницах
curl -sI https://yourdomain.com/ | grep -i server
  1. Выполните nginx -t перед каждым reload — синтаксическая ошибка в проде роняет весь сайт, а не только применяемую директиву
  2. Запросите заголовки через curl -I и убедитесь, что X-Frame-Options, X-Content-Type-Options и Strict-Transport-Security реально присутствуют в ответе
  3. Проверьте, что заголовок Server не выдаёт версию Nginx — подтверждение, что server_tokens off применился
  4. Прогоните сайт через внешний сервис проверки заголовков (например, тот, что упомянут в совете выше) и добейтесь оценки не ниже A, начиная с CSP в режиме Report-Only

Отдельный разбор Rate Limiting и лимитов подключений — в статье Fail2ban для SSH и Nginx, а базовая настройка брандмауэра перед веб-сервером — в материале UFW на VPS.

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

Что делать, если после Rate Limiting на /api/ начали массово падать легитимные запросы фронтенда?

Проверьте, не стоит ли лимит ниже реального количества AJAX-запросов одной страницы — зона rate=10r/s с burst=20 может быть тесной для SPA, которая шлёт десятки запросов разом при загрузке. Поднимайте burst или добавляйте nodelay только после анализа логов, а не вслепую, иначе рискуете просто ослабить защиту без пользы.

Почему страница с CSP в режиме Report-Only всё ещё ломается у части посетителей сайта?

Report-Only только логирует нарушения политики, но не блокирует загрузку ресурсов, поэтому сама по себе она ничего сломать не может. Поломка почти всегда связана не с CSP, а с другим заголовком или кешем браузера либо CDN, который отдаёт старую версию страницы. Сначала очистите кеш и сверьте заголовки через curl, прежде чем менять директивы CSP.

Нужно ли повторять все заголовки безопасности для каждого виртуального хоста вручную?

Нет, директивы add_header и ssl_ciphers логичнее задать один раз в общем конфиге и подключать через include во все server-блоки. Повторение одних и тех же строк в каждом файле хоста быстро приводит к рассинхронизации: один сайт обновили, про остальные забыли, и уровень защиты расползается без единого места правки.

Как узнать, что ограничение HTTP-методов не сломало API, который использует PUT или DELETE?

Проверьте логи на 403 сразу после применения limit_except и повторите реальные запросы клиента через curl с нужным методом. Если API на других путях легитимно использует PUT, PATCH или DELETE, ограничение стоит задавать точечно для конкретных location, а не глобально для всего сайта — иначе любое отклонение от GET, HEAD, POST будет отклонено сервером.

Статья помогла?
← Назад в базу знаний Задать вопрос поддержке