Укрепление безопасности Nginx на Ubuntu и Debian
Усильте Nginx за пределами стандартной конфигурации: заголовки безопасности, TLS 1.3, HSTS, ограничение HTTP-методов и контроль доступа. Каждая директива привязана к конкретной атаке, которую она предотвращает.
Стандартная установка Nginx раздаёт трафик. Она его не защищает. Конфигурация по умолчанию раскрывает версию сервера, принимает любые HTTP-методы, не отправляет заголовки безопасности и использует настройки TLS, предоставляемые OpenSSL.
Это руководство укрепляет Nginx на Debian 12 и Ubuntu 24.04. Каждый раздел сначала называет угрозу, затем показывает директиву, которая её блокирует. Изменения можно применить на работающем сервере без простоя.
Предварительные требования:
- Nginx установлен и обслуживает хотя бы один сайт по HTTPS (Настройка Let's Encrypt SSL/TLS для Nginx на Debian 12 и Ubuntu 24.04)
- Доступ root или sudo
- Базовое знакомство со структурой конфигурации Nginx (Структура конфигурационных файлов Nginx)
Все директивы укрепления хранятся в одном include-файле. Это поддерживает серверные блоки чистыми и упрощает аудит:
sudo touch /etc/nginx/snippets/security-hardening.conf
Каждый серверный блок, нуждающийся в укреплении, получает одну строку:
include /etc/nginx/snippets/security-hardening.conf;
После каждого изменения в этом руководстве тестируем и перезагружаем:
sudo nginx -t && sudo systemctl reload nginx
Как скрыть версию сервера Nginx?
Добавьте server_tokens off; в блок http в /etc/nginx/nginx.conf. Это убирает номер версии из заголовка ответа Server и со стандартных страниц ошибок. Атакующие сканируют конкретные версии с известными CVE. Скрытие версии не исправляет уязвимости, но повышает стоимость целевых атак.
Добавьте в /etc/nginx/nginx.conf внутри блока http:
server_tokens off;
Это идёт в основной конфиг, а не в snippet, потому что применяется глобально.
После перезагрузки проверьте заголовок ответа:
curl -sI https://your-domain.com | grep -i server
Server: nginx
Номера версии нет. До этого изменения там было что-то вроде Server: nginx/1.24.0.
Как заблокировать запросы к неизвестным хостам?
Когда кто-то обращается напрямую по IP-адресу сервера или использует нераспознанное имя хоста, Nginx отдаёт первый найденный серверный блок. Это открывает дверь для атак DNS rebinding и позволяет сканерам определять ваши сервисы.
Серверный блок по умолчанию, отклоняющий все непривязанные запросы, решает проблему. Добавьте его как первый серверный блок, загружаемый Nginx:
sudo nano /etc/nginx/sites-available/00-default-deny.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
# Self-signed or snakeoil cert just to complete the TLS handshake
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
return 444;
}
Код состояния 444 специфичен для Nginx. Он немедленно закрывает соединение без отправки ответа.
Включите его:
sudo ln -s /etc/nginx/sites-available/00-default-deny.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
На Debian 12 и Ubuntu 24.04 установите пакет ssl-cert, если сертификат snakeoil отсутствует:
sudo apt install ssl-cert
Проверьте, обратившись напрямую по IP:
curl -sI -k https://YOUR_SERVER_IP
Соединение закрывается с пустым ответом или ошибкой curl (код выхода 52). Контент не отдаётся.
Какую конфигурацию TLS использовать для Nginx в 2026 году?
Используйте профиль Intermediate от Mozilla: TLS 1.2 и 1.3 с наборами шифров с прямой секретностью (forward secrecy). Это покрывает клиентов начиная с Firefox 27 и Android 4.4.2, при этом отбрасывая небезопасные протоколы. TLS 1.0 и 1.1 уязвимы к POODLE и BEAST и считаются устаревшими с RFC 8996 (2021). Если все ваши клиенты поддерживают TLS 1.3, используйте профиль Modern (ssl_protocols TLSv1.3;).
Укрепление TLS стало более срочным в начале 2026. CVE-2026-1642 (CVSS 5.9) продемонстрировала, что обработка TLS upstream в Nginx имела состояние гонки, позволявшее MitM-инъекцию до завершения рукопожатия. Исправление вышло в Nginx 1.28.2 и 1.29.5. Проверьте версию командой nginx -v и обновите при необходимости.
| Профиль | Протоколы | Старейший клиент | Сценарий использования |
|---|---|---|---|
| Modern | Только TLS 1.3 | Firefox 63, Chrome 70 | API, современные веб-приложения |
| Intermediate | TLS 1.2 + 1.3 | Firefox 27, Android 4.4 | Серверы общего назначения |
| Old | TLS 1.0 + 1.1 + 1.2 + 1.3 | IE 8 на XP | Только legacy-совместимость |
Сгенерируйте параметры DH для наборов шифров DHE в профиле Intermediate. Это займёт несколько минут:
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
sudo chmod 644 /etc/nginx/dhparam.pem
Добавьте укрепление TLS в /etc/nginx/snippets/security-hardening.conf:
# TLS protocols - Mozilla Intermediate profile
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites - Mozilla Intermediate profile (version 5.7)
# TLS 1.3 suites are configured automatically by OpenSSL
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
Почему ssl_prefer_server_ciphers off? При наличии только сильных шифров в списке предпочтение клиента даёт лучшую производительность. Клиенты выбирают шифр, который их оборудование ускоряет лучше всего.
Почему ssl_session_tickets off? Сессионные тикеты используют общесерверный ключ. Если этот ключ утечёт, атакующий сможет расшифровать все записанные сессии. Без тикетов общий кеш сессий обеспечивает возобновление только на том же сервере, что подходит для односерверных конфигураций.
Примечание об OCSP stapling: Let's Encrypt прекратил работу OCSP-сервиса в августе 2025. Если вы используете сертификаты Let's Encrypt, удалите все директивы ssl_stapling. Они будут вызывать предупреждения в логах. Let's Encrypt теперь публикует информацию об отзыве исключительно через CRL.
Перезагрузите и протестируйте с локальной машины:
openssl s_client -connect your-domain.com:443 -tls1_2 </dev/null 2>/dev/null | grep "Protocol\|Cipher"
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Убедитесь, что TLS 1.1 отклоняется:
openssl s_client -connect your-domain.com:443 -tls1_1 </dev/null 2>&1 | head -5
Рукопожатие не удаётся. Для полного аудита отправьте домен на SSL Labs. Цель — оценка A или A+.
Как включить HSTS в Nginx?
HSTS (HTTP Strict Transport Security) указывает браузерам подключаться только по HTTPS в течение заданного периода. Без него MitM-атакующий может перехватить начальный HTTP-запрос и понизить соединение. После того как браузер увидел заголовок HSTS, он отказывается от незашифрованных HTTP-соединений к вашему домену до истечения max-age.
Добавьте в /etc/nginx/snippets/security-hardening.conf:
# HSTS - 2 years, include subdomains
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
Флаг always гарантирует отправку заголовка и при ответах с ошибками (4xx, 5xx). Без always ответ 403 или 500 не будет содержать заголовок, оставляя окно для downgrade-атак.
Перед добавлением includeSubDomains убедитесь, что все ваши поддомены поддерживают HTTPS. Эта директива распространяется на каждый поддомен. Поддомен без валидного сертификата станет недоступным.
Предзагрузка HSTS: Добавление preload в заголовок и отправка домена на hstspreload.org жёстко прописывает принудительное HTTPS в браузерах. Это необратимо. Удаление занимает месяцы. Добавляйте preload только когда уверены, что каждый поддомен всегда будет отдавать HTTPS.
После перезагрузки:
curl -sI https://your-domain.com | grep -i strict
strict-transport-security: max-age=63072000; includeSubDomains
Какие заголовки безопасности должен иметь каждый сервер Nginx?
Пять заголовков ответа защищают от распространённых атак на стороне браузера: MIME-снифинг, кликджекинг, межсайтовый скриптинг, утечка referrer и злоупотребление API браузера. Добавьте все пять с флагом always, чтобы они применялись и к ответам с ошибками. Устаревший заголовок X-XSS-Protection включать не нужно. Современные браузеры удалили свои XSS-аудиторы, а сам заголовок в некоторых случаях создавал уязвимости.
Добавьте в /etc/nginx/snippets/security-hardening.conf:
# Prevent MIME type sniffing - stops browsers from interpreting files as a
# different content type than declared, blocking drive-by download attacks
add_header X-Content-Type-Options "nosniff" always;
# Clickjacking protection - prevents your pages from being embedded in
# iframes on other sites
add_header X-Frame-Options "SAMEORIGIN" always;
# Content Security Policy - controls which sources can load scripts, styles,
# images, and other resources. Start restrictive, loosen as needed.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
# Referrer Policy - controls how much URL information the browser sends
# when navigating away from your site
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions Policy - disables browser features you don't use, preventing
# compromised scripts from accessing camera, microphone, etc.
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
interest-cohort=() встречался в старых руководствах для блокировки Google FLoC. FLoC был свёрнут в 2023. Современные браузеры не распознают эту директиву и выводят предупреждения в консоли, если её включить.
| Заголовок | Значение | Предотвращаемая угроза |
|---|---|---|
| X-Content-Type-Options | nosniff |
Атаки на основе MIME-путаницы, drive-by загрузки |
| X-Frame-Options | SAMEORIGIN |
Кликджекинг через встраивание в iframe |
| Content-Security-Policy | default-src 'self' |
XSS, инъекция данных, несанкционированная загрузка ресурсов |
| Referrer-Policy | strict-origin-when-cross-origin |
Утечка URL третьим сторонам |
| Permissions-Policy | camera=(), microphone=()... |
Злоупотребление API браузера внедрёнными скриптами |
Настройка CSP: Пример выше строгий. Большинству приложений нужны корректировки. Если сайт загружает скрипты с CDN, добавьте его: script-src 'self' https://cdn.example.com;. Используйте консоль разработчика браузера для поиска нарушений CSP и ослабляйте политику постепенно. Не используйте unsafe-eval, не понимая рисков.
Ловушка наследования add_header
Nginx отбрасывает все директивы add_header из родительских блоков, когда дочерний блок location определяет собственный add_header. Если вы добавите заголовок внутри блока location, все заголовки безопасности уровня server или http исчезнут из ответов этого location.
Два способа справиться с этим:
- Nginx 1.29.3+: Используйте
add_header_inherit merge;в дочернем блоке для сохранения родительских заголовков. - Более старые версии: Повторите строку
include /etc/nginx/snippets/security-hardening.conf;в каждом блоке location, который добавляет собственные заголовки.
Перезагрузите и проверьте:
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://your-domain.com | grep -iE "x-content-type|x-frame|content-security|referrer-policy|permissions-policy"
Все пять заголовков видны в выводе. Проверьте также конкретный путь location, не только корень, чтобы обнаружить проблемы наследования.
Как отключить небезопасные HTTP-методы в Nginx?
Большинству сайтов нужны только GET, HEAD и POST. Методы вроде DELETE, PUT, TRACE и OPTIONS (когда не используются) расширяют поверхность атаки. TRACE может раскрыть куки и токены аутентификации через XST-атаки (cross-site tracing).
Самый чистый подход в стандартном Nginx — limit_except, специально созданный для ограничения методов. Размещайте его в блоках location:
location / {
limit_except GET POST {
deny all;
}
# ... your existing config
}
limit_except GET неявно разрешает HEAD (HEAD — это GET без тела по спецификации HTTP). Любой неперечисленный метод возвращает 403 Forbidden.
Для глобального подхода с map (добавьте в nginx.conf в блок http):
map $request_method $method_not_allowed {
default 1;
GET 0;
HEAD 0;
POST 0;
}
Затем в серверном блоке:
if ($method_not_allowed) {
return 405;
}
Подход limit_except предпочтителен, когда нужен контроль на уровне location. Подход map подходит для единой политики на весь сервер.
Проверьте заблокированным методом:
curl -sI -X DELETE https://your-domain.com
Возвращает HTTP/1.1 403 Forbidden или HTTP/1.1 405 Not Allowed.
curl -sI -X GET https://your-domain.com
Возвращает HTTP/1.1 200 OK (или ваш обычный код ответа).
Как ограничить доступ к административным путям по IP-адресу?
Панели администрирования, дашборды мониторинга и статусные эндпоинты не должны быть доступны из публичного интернета. Директивы Nginx allow и deny ограничивают доступ по IP-адресу на уровне location.
location /admin {
allow 203.0.113.10; # Your office IP
allow 198.51.100.0/24; # Your VPN range
deny all;
# ... proxy_pass or other directives
}
location /nginx-status {
stub_status;
allow 127.0.0.1;
allow ::1;
deny all;
}
Порядок важен. Nginx проверяет allow и deny сверху вниз и останавливается на первом совпадении. Ставьте deny all последним.
Если IP меняется часто, рассмотрите ограничение доступа через файрвол или используйте VPN. IP-ACL в Nginx — это второй уровень защиты, не замена аутентификации.
С разрешённого IP:
curl -sI https://your-domain.com/admin
Возвращает обычный ответ (200, 302 и т.д.).
С другого IP (или через прокси) тот же запрос возвращает HTTP/1.1 403 Forbidden.
Проверьте лог доступа на отклонённые запросы:
sudo tail -5 /var/log/nginx/access.log | grep admin
Какие лимиты буферов и таймаутов предотвращают DoS-атаки в Nginx?
Стандартные размеры буферов и таймауты щедрые. Атакующий может злоупотребить этим, отправляя большие заголовки, медленные запросы или увеличенные тела для занятия worker-соединений. Ужесточение этих значений ограничивает ущерб от одного соединения.
Добавьте в /etc/nginx/snippets/security-hardening.conf:
# Maximum allowed request body size - reject uploads larger than 10MB
client_max_body_size 10m;
# Buffer for reading client request body
client_body_buffer_size 16k;
# Buffer for reading large client headers
large_client_header_buffers 4 16k;
# Timeouts - how long Nginx waits for client data
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
keepalive_timeout 65s;
| Директива | Значение | Слишком высокое | Слишком низкое |
|---|---|---|---|
client_max_body_size |
10m |
Позволяет огромные загрузки, заполняющие диск | Ломает формы загрузки файлов |
client_body_buffer_size |
16k |
Расход памяти на соединение | Принудительные temp-файлы для малых POST |
large_client_header_buffers |
4 16k |
Расход памяти при атаках заголовками | Ломает приложения с большими куки/URL |
client_body_timeout |
30s |
Slow loris-соединения остаются открытыми | Отключает пользователей на медленных сетях |
client_header_timeout |
30s |
Аналогично | Аналогично |
send_timeout |
30s |
Занимает соединения для медленных клиентов | Обрывает большие загрузки |
keepalive_timeout |
65s |
Исчерпание пула соединений | Дополнительные TLS-рукопожатия |
Настройте client_max_body_size под ваше приложение. Если обрабатываете загрузку файлов, установите значение, ожидаемое приложением. 10 МБ по умолчанию — разумно для большинства веб-приложений.
Перезагрузите и проверьте лимит размера тела:
dd if=/dev/zero bs=1M count=11 2>/dev/null | curl -s -X POST --data-binary @- -o /dev/null -w "%{http_code}" https://your-domain.com/
Ожидается: 413 (Request Entity Too Large).
Для rate limiting и более глубокой защиты от DDoS смотрите Rate limiting в Nginx и защита от DDoS.
Как проверить полную конфигурацию укрепления?
Когда все изменения применены, перезагрузите и пройдитесь по ключевым проверкам. Начните с теста конфигурации и перезагрузки:
sudo nginx -t && sudo systemctl reload nginx
Проверьте заголовки и информацию о версии одним запросом:
curl -sI https://your-domain.com
Заголовок Server должен показывать nginx без номера версии. Все шесть заголовков безопасности (HSTS, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Referrer-Policy, Permissions-Policy) должны присутствовать в ответе.
Для TLS подтвердите протокол и шифр с локальной машины:
openssl s_client -connect your-domain.com:443 </dev/null 2>/dev/null | grep -E "Protocol|Cipher"
Нужен TLSv1.2 или TLSv1.3 с GCM или CHACHA20 шифром.
Обратитесь напрямую к IP сервера, чтобы убедиться в работе блока отклонения по умолчанию:
curl -sk https://YOUR_SERVER_IP -o /dev/null -w "%{http_code}"
Код ответа 000 означает, что соединение закрыто без ответа — это правильно.
Попробуйте заблокированный HTTP-метод:
curl -sI -X TRACE https://your-domain.com -o /dev/null -w "%{http_code}"
Должно вернуть 403 или 405.
Для внешнего аудита отправьте домен на SSL Labs (цель — оценка A или A+) и securityheaders.com для отчёта по заголовкам.
Полный snippet укрепления
Полный файл /etc/nginx/snippets/security-hardening.conf со всем из этого руководства:
# /etc/nginx/snippets/security-hardening.conf
# Nginx security hardening - include in each server block
# --- TLS Hardening (Mozilla Intermediate profile v5.7) ---
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;
# TLS session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# --- HSTS ---
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# --- Security Headers ---
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
# --- Buffer Limits ---
client_max_body_size 10m;
client_body_buffer_size 16k;
large_client_header_buffers 4 16k;
# --- Timeouts ---
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
keepalive_timeout 65s;
Не забудьте также установить server_tokens off; в блоке http файла /etc/nginx/nginx.conf и создать серверный блок отклонения по умолчанию отдельно.
Устранение неполадок
Nginx не перезагружается после изменений:
sudo nginx -t
Прочитайте ошибку. Она указывает файл и номер строки. Частые причины: пропущенные точки с запятой, опечатки в именах директив, ссылка на несуществующий файл DH-параметров.
Заголовки отсутствуют на некоторых путях:
Ловушка наследования add_header. Если блок location имеет собственный add_header, все родительские заголовки отбрасываются. Включите snippet в этот блок location или используйте add_header_inherit merge; на Nginx 1.29.3+.
CSP блокирует легитимные ресурсы:
Откройте консоль разработчика браузера (F12). Ищите ошибки Refused to load. Они называют заблокированный ресурс и ответственную директиву CSP. Добавляйте источник в политику постепенно.
Ошибки TLS-рукопожатия:
journalctl -u nginx -f
Следите за SSL-ошибками при подключении. Убедитесь, что цепочка сертификатов полная:
openssl s_client -connect your-domain.com:443 -servername your-domain.com </dev/null 2>/dev/null | grep "Verify return code"
Ожидается: Verify return code: 0 (ok).
Расположение логов:
# Error log
sudo tail -20 /var/log/nginx/error.log
# Real-time monitoring
sudo journalctl -u nginx -f
Дальнейшие шаги: После укрепления сервера настройте Rate limiting в Nginx и защита от DDoS для обработки аномальных паттернов трафика. Для настроек конкретных сайтов смотрите Nginx Server Blocks: несколько доменов на одном VPS.
По теме: Администрирование Nginx на VPS | Структура конфигурационных файлов Nginx | Настройка Let's Encrypt SSL/TLS для Nginx на Debian 12 и Ubuntu 24.04
Готовы попробовать?
Разверните свой сервер за секунды. Linux, Windows или FreeBSD. →