До основного вмісту

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 сервер відхилить.

Чи була стаття корисною?
← Назад до бази знань Поставити питання підтримці