nginx: шпаргалка с разбором — от listen до балансировки
Полный разбор типовых конфигов nginx: порты, домены, статика, редиректы, reverse proxy, балансировка, SSL и security headers. Каждый блок — с объяснением, зачем это нужно.
nginx — один из самых распространённых веб-серверов. Его ставят как фронтенд перед приложениями, раздают статику, терминируют SSL, балансируют нагрузку. Конфиги внешне похожи, но один неверный параметр — и сервис ведёт себя непредсказуемо.
Ниже — разбор типовых конфигов: что делает каждая директива, зачем она нужна и как её применять.
Структура конфига
Главный файл: /etc/nginx/nginx.conf. Конфиги сайтов лежат в /etc/nginx/sites-available/, подключаются симлинком в /etc/nginx/sites-enabled/.
# Проверить синтаксис конфига nginx -t # Перечитать конфиг без рестарта (graceful reload) nginx -s reload # или через systemd: systemctl reload nginx # Полный рестарт (обрывает соединения) systemctl restart nginx # Статус и последние ошибки systemctl status nginx journalctl -u nginx -n 50 --no-pager
1. listen — порты и протоколы
server {
listen 80; # IPv4, порт 80 (HTTP)
listen [::]:80; # IPv6, порт 80
listen 443 ssl; # IPv4, порт 443 (HTTPS)
listen [::]:443 ssl; # IPv6, порт 443
# Если нужен только IPv6:
listen [::]:80 ipv6only=on;
}
Как читать: каждый listen — это отдельный сокет, который nginx открывает. Можно слушать несколько портов в одном server {}-блоке. Параметр ssl после порта говорит: все соединения на этот порт обрабатывать как HTTPS.
Типичная ошибка: listen 443 без ssl — nginx примет соединение, но не сможет его расшифровать и вернёт ошибку клиенту.
# Проверить, какие порты реально слушает nginx ss -tlnp | grep nginx # или netstat -tlnp | grep nginx
2. server_name — домены и поддомены
server {
# Один домен
server_name example.com;
# Домен и www
server_name example.com www.example.com;
# Все поддомены (wildcard)
server_name *.example.com;
# Любой хост (default server)
server_name _;
}
Как читать: nginx смотрит на заголовок Host входящего запроса и ищет server {}-блок с совпадающим server_name. Если совпадений нет — отдаёт запрос default_server (первый блок или тот, у кого явно прописан этот флаг).
Wildcard *.example.com покрывает sub.example.com, но не сам example.com — для него нужна отдельная строка.
server_name _ — условный «мусорный» сервер, который ловит всё, на что не нашлось явного блока. Полезно для заглушки или редиректа неизвестных хостов.
# Посмотреть все server_name в конфиге nginx -T 2>/dev/null | grep server_name
3. access_log — логирование запросов
http {
# Формат лога (определяется один раз в http {})
log_format main '$remote_addr [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
# JSON-формат для ELK / Loki
log_format json escape=json
'{"time":"$time_iso8601",'
'"ip":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"bytes":$body_bytes_sent}';
}
server {
# Логировать в файл с форматом main
access_log /var/log/nginx/example.access.log main;
# Буферизация: пишем на диск пачками, не каждую строку
access_log /var/log/nginx/example.access.log main buffer=16k flush=10s;
# Отключить логирование для healthcheck
location /health {
access_log off;
return 200 "ok";
}
}
Как читать: без явного log_format nginx пишет в формате combined (Apache-совместимый). JSON-формат удобен, если логи собирает Filebeat или Promtail.
buffer + flush — копит строки в памяти и сбрасывает пачкой. Снижает нагрузку на диск при высоком трафике.
# Следить за логом в реальном времени
tail -f /var/log/nginx/example.access.log
# Топ URL по количеству запросов
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# Топ IP по количеству запросов
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# Все ошибки 5xx за последние 100 строк
tail -100 /var/log/nginx/access.log | awk '$9 >= 500'
4. Раздача статики
server {
listen 80;
server_name example.com;
root /var/www/example;
index index.html index.htm;
location / {
# Ищем файл → директорию → 404
try_files $uri $uri/ =404;
# Для SPA (React/Vue) вместо =404:
# try_files $uri $uri/ /index.html;
}
# Кэширование картинок на 30 дней
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# CSS и JS — 7 дней
location ~* \.(css|js)$ {
expires 7d;
add_header Cache-Control "public";
}
}
# Сжатие gzip (добавить в http {})
http {
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 256;
gzip_comp_level 6;
gzip_vary on;
}
try_files — ключевая директива. Проверяет файл по $uri, потом директорию $uri/, затем возвращает 404. Для SPA-приложений вместо 404 подставляют /index.html — клиентский роутер сам разберётся с URL.
gzip_comp_level 6 — баланс между степенью сжатия и нагрузкой на CPU. Уровень 9 даёт незначительно лучшее сжатие, но заметно дороже. Уровень 1 почти не сжимает. 6 — оптимум для большинства случаев.
# Проверить, что gzip работает curl -H "Accept-Encoding: gzip" -I https://example.com | grep -i encoding
5. Редиректы
# Редирект с www на без www (301 — постоянный)
server {
listen 80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}
# Редирект с HTTP на HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# Редирект конкретного URL
location /old-page {
return 301 /new-page;
}
# Временный редирект (поисковик не обновляет индекс)
location /promo {
return 302 /promo-2026;
}
# Редирект по регулярному выражению
rewrite ^/blog/([0-9]+)$ /articles/$1 permanent; # 301
rewrite ^/blog/([0-9]+)$ /articles/$1 redirect; # 302
301 vs 302: 301 — постоянный, поисковик переносит вес страницы на новый URL. 302 — временный, старый адрес остаётся в индексе. Ошибка здесь стоит дорого: неверный 301 кэшируется браузером и поисковиком надолго.
return vs rewrite: return быстрее — nginx сразу отвечает клиенту, не продолжая обработку. rewrite меняет URI внутри nginx и передаёт дальше по цепочке location-ов. Используйте return везде, где не нужна внутренняя переработка URI.
6. Reverse proxy — обратный прокси
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
# Передать реальный IP клиента в приложение
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
# Таймауты
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Буферизация ответа бэкенда
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 16 8k;
}
}
# Для WebSocket — дополнительные заголовки
location /ws {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# Через Unix-сокет (быстрее TCP loopback)
location /api {
proxy_pass http://unix:/run/myapp/app.sock;
proxy_set_header Host $host;
}
proxy_set_header X-Real-IP — без этого заголовка приложение видит IP самого nginx (127.0.0.1), а не реального клиента. Это ломает логи, геолокацию и rate-limiting на уровне приложения.
X-Forwarded-Proto — приложение знает, по какому протоколу пришёл клиент. Нужно для корректного формирования ссылок: без него приложение за HTTPS-nginx будет генерировать http:// ссылки.
proxy_buffering off нужен для Server-Sent Events и длинных потоков — иначе nginx будет буферизовать данные и клиент не получит их в реальном времени.
# Проверить, что заголовки доходят до приложения curl -H "Host: example.com" http://127.0.0.1:3000/debug-headers
7. Балансировка нагрузки
upstream app_servers {
# Round-robin (по умолчанию) — запросы по очереди
server 10.0.0.1:3000;
server 10.0.0.2:3000;
# weight: сервер с weight=3 получает втрое больше запросов
server 10.0.0.1:3000 weight=3;
server 10.0.0.2:3000 weight=1;
# backup: используется только если основные недоступны
server 10.0.0.3:3000 backup;
# down: временно вывести из ротации (без удаления строки)
server 10.0.0.4:3000 down;
# Пул постоянных соединений к бэкенду
keepalive 32;
}
# ip_hash: один клиент всегда попадает на один бэкенд
upstream app_sticky {
ip_hash;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
# least_conn: запрос идёт к серверу с наименьшим числом активных соединений
upstream app_smart {
least_conn;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Для keepalive обязательно:
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Round-robin подходит, когда все запросы примерно одинаковой длительности и серверы равнозначны. least_conn умнее: если один запрос занимает 5 секунд, а другой — 50 мс, round-robin всё равно распределит следующий запрос «по очереди». least_conn учитывает реальную загрузку.
ip_hash нужен, когда приложение хранит сессию в памяти процесса (не в Redis). Минус: при падении сервера его клиенты «переедут» и потеряют сессию. Лучше вынести сессии во внешнее хранилище.
keepalive + proxy_http_version 1.1 — без этих двух директив keepalive не работает. HTTP/1.0 закрывает соединение после каждого запроса.
8. SSL/TLS
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Только TLS 1.2 и 1.3 (1.0 и 1.1 уязвимы и отключены в браузерах)
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;
ssl_prefer_server_ciphers off;
# Кэш сессий: повторное TLS-рукопожатие не нужно
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling: сервер сам проверяет отзыв сертификата
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 1.1.1.1 valid=300s;
# HSTS: браузер запоминает HTTPS на год, не делает HTTP-запрос вообще
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
# HTTP → HTTPS редирект
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
ssl_prefer_server_ciphers off — в TLS 1.3 клиент выбирает шифр, сервер это не контролирует. Оставить off — правильно для TLS 1.3. Для совместимости со старыми клиентами иногда ставят on.
OCSP Stapling — без него браузер при каждом соединении обращается к серверу сертификационного центра, чтобы проверить, не отозван ли сертификат. Это добавляет задержку. Со stapling nginx сам периодически получает OCSP-ответ и прикладывает его к TLS-рукопожатию.
# Получить сертификат Let's Encrypt certbot --nginx -d example.com -d www.example.com # Проверить срок действия сертификата echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates # Автообновление сертификатов systemctl status certbot.timer
9. Security headers — заголовки безопасности
server {
# Скрыть версию nginx из заголовков и страниц ошибок
server_tokens off;
# Запрет показа сайта во фрейме на чужих сайтах (clickjacking)
add_header X-Frame-Options "SAMEORIGIN" always;
# Браузер не пытается угадать тип файла по содержимому
add_header X-Content-Type-Options "nosniff" always;
# Не передавать полный URL при переходе на другой домен
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Политика загрузки ресурсов (CSP)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" always;
# Запрет определения местоположения, камеры и микрофона
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
}
server_tokens off — по умолчанию nginx отдаёт версию в заголовке Server: nginx/1.26.0. Зная версию, атакующий ищет соответствующие CVE. Отключаем, заодно убирается версия и со страниц ошибок.
X-Frame-Options SAMEORIGIN — сайт можно открыть во <iframe> только с того же домена. Защита от clickjacking: когда злоумышленник встраивает невидимый фрейм с вашим сайтом поверх своего и заставляет пользователя кликать «вслепую».
CSP — самый мощный заголовок. Запрещает браузеру загружать скрипты, стили, изображения с доменов, которых нет в политике. Эффективная защита от XSS. Но требует тщательной настройки под конкретный сайт.
# Проверить все заголовки ответа curl -sI https://example.com # Проверить оценку конфига SSL # https://www.ssllabs.com/ssltest/ # Проверить security headers # https://securityheaders.com/
10. Rate limiting — ограничение запросов
http {
# Зона памяти: 10 МБ, до 10 запросов/сек на один IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# Отдельная зона для формы входа: не более 5 попыток/мин
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
}
server {
# API: разрешаем всплески до 20, остальное — 429
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
}
# Форма входа: строгое ограничение
location /login {
limit_req zone=login burst=3;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
}
}
$binary_remote_addr — IP в бинарном виде (4 байта для IPv4). Занимает меньше памяти, чем строковый адрес. 10 МБ хватает примерно для 160 000 уникальных IP.
burst — допустимый всплеск сверх лимита. Запросы из burst ставятся в очередь. nodelay — очередь обрабатывается немедленно, без искусственных задержек. Без nodelay запросы из burst «размазываются» по времени, что создаёт неожиданные задержки для клиента.
Rate limiting защищает от брутфорса, credential stuffing и агрессивных ботов даже до того, как запрос дойдёт до приложения.
Итог: быстрая диагностика
# Проверить синтаксис конфига (всегда перед reload) nginx -t # Посмотреть итоговый конфиг со всеми include nginx -T # Текущие соединения к nginx ss -s # Ошибки в реальном времени tail -f /var/log/nginx/error.log # Найти блок, обрабатывающий домен nginx -T 2>/dev/null | grep -B5 "server_name.*example.com" # Сколько воркеров запущено ps aux | grep nginx
