С чего начинается тюнинг
Nginx в дефолтной конфигурации — это как спортивный автомобиль с заводскими настройками для езды по бездорожью: едет, но не так быстро, как мог бы. Хорошая новость: большинство важных оптимизаций достигается правкой конфига, а не покупкой более мощного железа.
Плохая новость: многие "гайды по тюнингу" в интернете — копипаста десятилетней давности, без понимания что и зачем. Параметры для Nginx 1.8 на 2-ядерном сервере копируют на 32-ядерный продакшен под highload — и удивляются что не помогает или становится хуже.
Этот материал — о том, как думать о тюнинге Nginx: что делает каждый параметр, какие компромиссы он несёт, и как проверить что оптимизация действительно работает.
Версии в статье: Nginx 1.24+ / 1.25+ (mainline). Большинство конфигов работают с 1.18+.
Диагностика перед тюнингом: что измерять
Тюнинг без метрик — гадание на кофейной гуще. Сначала измеряем, потом меняем, потом снова измеряем.
Текущее состояние Nginx
# Версия и скомпилированные модули
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'version|with-|without-'
# Активная конфигурация (проверка синтаксиса)
nginx -t
# Рабочие процессы и их нагрузка
ps aux | grep nginx
top -p $(pgrep -d',' nginx)
# Открытые соединения
ss -s
ss -tnp | grep nginx | wc -l
# Статус (если включён stub_status)
curl -s http://127.0.0.1/nginx_status
# Active connections: 847
# server accepts handled requests
# 12340582 12340582 28473910
# Reading: 12 Writing: 84 Waiting: 751
# Лимиты файловых дескрипторов
cat /proc/$(pgrep -f 'nginx: master')/limits | grep 'open files'
ulimit -n
Нагрузочное тестирование
# wrk — современный HTTP benchmarker
# Установка: apt install wrk / brew install wrk
wrk -t12 -c400 -d30s --latency http://your-server/api/endpoint
# Параметры:
# -t12 — 12 потоков (= число ядер)
# -c400 — 400 конкурентных соединений
# -d30s — 30 секунд
# --latency — показать перцентили задержек
# Вывод:
# Running 30s test @ http://your-server/
# 12 threads and 400 connections
# Thread Stats Avg Stdev Max +/- Stdev
# Latency 23.45ms 8.12ms 890ms 92.34%
# Req/Sec 1.45k 312.45 2.10k 68.23%
# Latency Distribution
# 50% 21.23ms
# 75% 28.45ms
# 90% 35.67ms
# 99% 78.90ms ← 99-й перцентиль важнее среднего!
# 521245 requests in 30.00s, 2.34GB read
# Requests/sec: 17374.83
# Transfer/sec: 79.92MB
# ab (Apache Benchmark) — встроен везде, но хуже wrk
ab -n 10000 -c 100 http://your-server/
# hey — ещё один вариант (Go)
hey -n 50000 -c 200 http://your-server/
Мониторинг в реальном времени
# Топ запросов по времени ответа (из access_log)
awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -20
# Количество запросов в секунду (live)
tail -f /var/log/nginx/access.log | pv -l -i 1 > /dev/null
# Распределение кодов ответов за последний час
awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" \
'$4 ~ d {print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Топ IP по количеству запросов
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Уровень 1: Системные настройки Linux
Nginx ограничен операционной системой. Без правильной настройки Linux все оптимизации Nginx упрутся в системный потолок.
Файловые дескрипторы
# /etc/security/limits.conf
nginx soft nofile 65535
nginx hard nofile 65535
root soft nofile 65535
root hard nofile 65535
# Для systemd (приоритет над limits.conf):
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65535
sudo systemctl daemon-reload
sudo systemctl restart nginx
# Проверка:
cat /proc/$(cat /var/run/nginx.pid)/limits | grep 'open files'
Сетевой стек (sysctl)
# /etc/sysctl.d/99-nginx.conf
# ===== TCP буферы =====
# Увеличиваем буферы приёма/передачи
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# ===== Очередь соединений =====
# Размер очереди для accept() — важно при всплесках трафика
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# ===== TCP оптимизации =====
# Быстрое переиспользование TIME_WAIT соединений
net.ipv4.tcp_tw_reuse = 1
# Алгоритм управления перегрузкой
# BBR — лучший выбор для большинства продакшен-сценариев (ядро 4.9+)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# Уменьшаем время FIN_WAIT2 (2 минуты по умолчанию — слишком долго)
net.ipv4.tcp_fin_timeout = 15
# Максимальное число открытых TCP соединений
net.ipv4.tcp_max_tw_buckets = 1440000
# Разрешаем bind на порт без TIME_WAIT
net.ipv4.tcp_timestamps = 1
# ===== Очередь обработки пакетов =====
net.core.netdev_max_backlog = 65535
# ===== Локальный диапазон портов =====
# Для upstream keepalive нужно много эфемерных портов
net.ipv4.ip_local_port_range = 1024 65535
# Применить:
sudo sysctl -p /etc/sysctl.d/99-nginx.conf
# Проверить BBR:
sysctl net.ipv4.tcp_congestion_control
# должно быть: net.ipv4.tcp_congestion_control = bbr
Прозрачные hugepages и планировщик I/O
# Для высоконагруженных серверов — отключить transparent hugepages
# (могут вызывать latency spikes)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# В /etc/rc.local для постоянства:
echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.local
echo 'echo never > /sys/kernel/mm/transparent_hugepage/defrag' >> /etc/rc.local
# Планировщик I/O для SSD (none или mq-deadline быстрее cfq)
echo mq-deadline > /sys/block/sda/queue/scheduler
# Проверить: cat /sys/block/sda/queue/scheduler
Уровень 2: Базовый конфиг Nginx — Worker и Events
# /etc/nginx/nginx.conf
# ===== WORKER PROCESSES =====
# Правило: 1 воркер на 1 физическое ядро CPU
# auto — Nginx сам определяет количество ядер (рекомендуется)
worker_processes auto;
# Привязка воркеров к ядрам (CPU affinity)
# Уменьшает cache miss у процессора, повышает производительность ~5-10%
# Для 4 ядер:
# worker_cpu_affinity 0001 0010 0100 1000;
# Для auto (Nginx 1.9+):
worker_cpu_affinity auto;
# Приоритет процесса (от -20 до 20, меньше = выше приоритет)
# -5 даёт небольшое преимущество без ущерба системе
worker_priority -5;
# Файловые дескрипторы на воркер (должно совпадать с ulimit -n)
worker_rlimit_nofile 65535;
# PID файл
pid /var/run/nginx.pid;
# ===== ERROR LOG =====
# warn в продакшене (info/debug — слишком verbose для highload)
error_log /var/log/nginx/error.log warn;
# ===== EVENTS =====
events {
# Максимум соединений на воркер
# Итого соединений = worker_processes × worker_connections
# Не ставьте больше 65535 — ограничение Linux
# Реально для highload: 4096-16384
worker_connections 10240;
# epoll — единственный правильный выбор на Linux
# (Nginx выбирает автоматически, но явно лучше)
use epoll;
# Принимать все ожидающие соединения за один вызов accept()
# ОБЯЗАТЕЛЬНО для highload! Без этого воркер обрабатывает по 1 соединению
multi_accept on;
# Принимать мьютекс для accept() (устарело в современных ядрах,
# но оставляем для совместимости)
# accept_mutex off; # Можно отключить на ядрах 3.9+
}
Уровень 3: HTTP блок — основные оптимизации
http {
# ===== БАЗОВЫЕ MIME ТИПЫ =====
include /etc/nginx/mime.types;
default_type application/octet-stream;
# ===== SENDFILE: ZERO-COPY ПЕРЕДАЧА ФАЙЛОВ =====
# Передаёт файлы напрямую из файловой системы в сокет
# минуя user space — экономит копирование данных в памяти
# ОБЯЗАТЕЛЬНО для статики!
sendfile on;
# Отправлять заголовки и начало файла в одном TCP-пакете
# Работает только совместно с sendfile on
tcp_nopush on;
# Отключить алгоритм Nagle — не буферизировать маленькие пакеты
# Уменьшает latency для интерактивных запросов
# tcp_nopush и tcp_nodelay вместе: сначала накапливаем (nopush),
# потом сразу отправляем последний пакет (nodelay)
tcp_nodelay on;
# ===== KEEPALIVE =====
# Время жизни keepalive соединения с клиентом
# 65s стандарт, 75s — максимум до таймаута прокси (Cloudflare = 90s)
keepalive_timeout 65;
# Максимум запросов через одно keepalive соединение
# После этого — закрываем и открываем новое
# 1000 — хороший баланс между переиспользованием и памятью
keepalive_requests 1000;
# ===== БУФЕРЫ =====
# Эти настройки критически важны для производительности proxy!
# Размер хэш-таблицы имён серверов
server_names_hash_bucket_size 128;
server_names_hash_max_size 1024;
# Буфер для чтения заголовков запроса клиента
# 16k достаточно для большинства запросов, включая большие cookie
client_header_buffer_size 16k;
large_client_header_buffers 4 32k;
# Максимальный размер тела запроса (загрузка файлов)
# 0 — отключить ограничение (не рекомендуется!)
client_max_body_size 64m;
# Таймаут на чтение тела запроса
client_body_timeout 30s;
# Таймаут на чтение заголовков запроса
client_header_timeout 15s;
# Таймаут на отправку ответа клиенту
# (между двумя последовательными операциями send)
send_timeout 30s;
# Буфер для тела запроса в памяти (если больше — пишем на диск)
client_body_buffer_size 256k;
# ===== ТИПЫ ХЭШЕЙ =====
types_hash_max_size 4096;
types_hash_bucket_size 128;
# ===== БЕЗОПАСНОСТЬ: УБИРАЕМ ВЕРСИЮ NGINX =====
server_tokens off;
# ===== ЛОГИ =====
# Формат логов с временем обработки запроса — важно для анализа!
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';
# Расширенный формат для детальной диагностики:
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'rt=$request_time '
'urt="$upstream_response_time" '
'uct="$upstream_connect_time" '
'uht="$upstream_header_time" '
'cs=$upstream_cache_status '
'host=$host '
'xff="$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main buffer=64k flush=5s;
# buffer=64k — буферизация логов (не пишем на диск каждую строку)
# flush=5s — сбрасываем буфер каждые 5 секунд
# Для максимальной производительности — отключить access_log на статике
# (настраивается в location блоках)
# ===== ВКЛЮЧАЕМ ПОДКОНФИГИ =====
include /etc/nginx/conf.d/*.conf;
}
Уровень 4: Gzip и Brotli — сжатие ответов
http {
# ===== GZIP =====
gzip on;
# Не сжимать ответы для IE6 (исторический артефакт, можно убрать)
gzip_disable "msie6";
# Сжимать ответы для всех клиентов, в т.ч. через прокси
# any — сжимать независимо от заголовка Via
gzip_proxied any;
# Уровень сжатия: 1-9
# 1 — быстро, мало сжатие (~60%)
# 6 — баланс (рекомендуется для продакшена ~70%)
# 9 — максимум, но тратит значительно больше CPU (~72%, но в 3-5 раз медленнее 6)
gzip_comp_level 6;
# Минимальный размер для сжатия (не сжимать маленькие файлы — смысла нет)
gzip_min_length 1024;
# Буферы для сжатия
gzip_buffers 16 8k;
# HTTP версия (сжимать и для HTTP/1.0 клиентов — редко нужно)
gzip_http_version 1.1;
# Типы контента для сжатия
# text/html сжимается всегда (по умолчанию)
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
application/x-javascript
application/x-font-ttf
application/vnd.ms-fontobject
font/opentype
image/svg+xml
image/x-icon;
# Добавить заголовок Vary: Accept-Encoding
# Указывает прокси-серверам хранить сжатые и несжатые версии отдельно
gzip_vary on;
# ===== BROTLI (требует модуль ngx_brotli) =====
# Brotli на 15-25% эффективнее gzip при тех же CPU-затратах
# Поддерживается всеми современными браузерами
# Установка модуля (Ubuntu/Debian):
# apt install libnginx-mod-http-brotli-filter
# Или из исходников: https://github.com/google/ngx_brotli
# load_module modules/ngx_http_brotli_filter_module.so;
# load_module modules/ngx_http_brotli_static_module.so;
brotli on;
brotli_comp_level 6; # 0-11, 6 — хороший баланс
brotli_min_length 1024;
brotli_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
image/svg+xml;
# Brotli static — отдавать предкомпрессированные .br файлы
# Нужно сгенерировать: find /var/www -name "*.js" | xargs -I{} brotli {}
brotli_static on;
# ===== GZIP STATIC — предкомпрессированные .gz файлы =====
# Если файл app.js.gz существует — отдаём его без CPU на сжатие
gzip_static on;
}
Предварительное сжатие статики (экономит CPU)
#!/bin/bash
# Скрипт для предкомпрессии статических файлов
STATIC_DIR="/var/www/html"
find "$STATIC_DIR" \
\( -name "*.js" -o -name "*.css" -o -name "*.html" -o -name "*.json" \
-o -name "*.xml" -o -name "*.svg" \) \
-type f | while read file; do
# gzip (только если .gz не существует или файл новее)
if [ ! -f "${file}.gz" ] || [ "$file" -nt "${file}.gz" ]; then
gzip -9 -k "$file"
fi
# brotli
if command -v brotli &> /dev/null; then
if [ ! -f "${file}.br" ] || [ "$file" -nt "${file}.br" ]; then
brotli -q 11 -k "$file"
fi
fi
done
echo "Предкомпрессия завершена: $(find $STATIC_DIR -name '*.gz' | wc -l) gz файлов"
Уровень 5: Кэширование — proxy_cache и FastCGI cache
Proxy Cache (для проксирования на upstream)
http {
# ===== ЗОНА КЭША =====
# keys_zone=cache_name:10m — зона памяти для хранения ключей
# levels=1:2 — структура директорий (более эффективный поиск)
# inactive=60m — удалять неиспользуемые файлы через 60 минут
# max_size=10g — максимальный размер кэша на диске
# use_temp_path=off — не использовать временный путь (быстрее)
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=proxy_cache:50m
inactive=60m
max_size=10g
use_temp_path=off;
server {
location /api/ {
proxy_pass http://backend;
# ===== PROXY БУФЕРЫ =====
# Буферизировать ответ от upstream в памяти
# Важно: без буферизации Nginx держит соединение с upstream
# пока клиент не скачает весь ответ (медленные клиенты = занятые воркеры)
proxy_buffering on;
# Количество и размер буферов для тела ответа
# proxy_buffers × proxy_buffer_size = RAM на соединение
# 32 × 16k = 512k на соединение
proxy_buffers 32 16k;
proxy_buffer_size 16k; # Для заголовков ответа
# Если ответ не помещается в proxy_buffers — пишем во временный файл
proxy_max_temp_file_size 0; # 0 = отключить (пишем всё в память)
# или установить лимит: proxy_max_temp_file_size 1024m;
# Буфер для занятых соединений (busy = клиент читает медленно)
proxy_busy_buffers_size 64k;
# ===== PROXY ТАЙМАУТЫ =====
# Таймаут установки соединения с upstream
proxy_connect_timeout 5s;
# Таймаут между двумя последовательными операциями чтения от upstream
proxy_read_timeout 60s;
# Таймаут передачи данных к upstream
proxy_send_timeout 60s;
# ===== ЗАГОЛОВКИ К UPSTREAM =====
proxy_set_header Host $host;
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_http_version 1.1; # HTTP/1.1 для keepalive!
proxy_set_header Connection ""; # Убрать заголовок Connection для keepalive
# ===== КЭШИРОВАНИЕ =====
proxy_cache proxy_cache;
proxy_cache_valid 200 302 10m; # Кэшировать 200/302 на 10 минут
proxy_cache_valid 404 1m; # 404 — на 1 минуту
proxy_cache_valid any 30s; # Остальное — 30 секунд
# Ключ кэша (что уникально идентифицирует запрос)
proxy_cache_key "$scheme$request_method$host$request_uri";
# Методы для кэширования (по умолчанию только GET и HEAD)
proxy_cache_methods GET HEAD;
# Кэшировать ответы с заголовком Set-Cookie
# (осторожно — персональные данные!)
# proxy_ignore_headers Set-Cookie;
# Stale cache — отдавать устаревший кэш пока upstream недоступен
# Спасает от "пятистотки" при временных проблемах с бэкендом
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
# Блокировка одновременных запросов к upstream (coalescing)
# Один запрос идёт к upstream, остальные ждут результата в кэше
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_lock_age 5s;
# Фоновое обновление кэша (stale-while-revalidate)
# Отдаём устаревший кэш и одновременно запускаем фоновое обновление
proxy_cache_background_update on;
# Добавляем заголовок X-Cache-Status для отладки
add_header X-Cache-Status $upstream_cache_status always;
}
# ===== СТАТИКА: МАКСИМАЛЬНЫЙ КЭШ =====
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable, max-age=31536000";
access_log off; # Не логировать статику (экономит I/O)
tcp_nodelay off; # Для больших файлов nopush важнее nodelay
sendfile on;
aio on; # Асинхронный I/O для больших файлов
}
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable, max-age=31536000";
access_log off;
}
location ~* \.(html|htm)$ {
expires 1h;
add_header Cache-Control "public, max-age=3600, must-revalidate";
}
}
}
FastCGI Cache (для PHP-FPM)
http {
# Зона FastCGI кэша
fastcgi_cache_path /var/cache/nginx/fastcgi
levels=1:2
keys_zone=fastcgi_cache:20m
inactive=60m
max_size=5g
use_temp_path=off;
# Глобальная переменная для определения статуса кэша
map $request_method $no_cache_method {
default 0;
POST 1;
PUT 1;
DELETE 1;
PATCH 1;
}
server {
set $skip_cache 0;
# Не кэшировать авторизованных пользователей (например, WordPress)
if ($http_cookie ~* "wordpress_logged_in|woocommerce_cart") {
set $skip_cache 1;
}
# Не кэшировать POST запросы
if ($request_method = POST) {
set $skip_cache 1;
}
# Не кэшировать URL с query string (можно убрать если кэш по полному URL)
# if ($query_string != "") {
# set $skip_cache 1;
# }
# Не кэшировать admin/личный кабинет
if ($request_uri ~* "^/admin|^/wp-admin|^/login") {
set $skip_cache 1;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# ===== FastCGI БУФЕРЫ =====
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 64k;
# Таймауты
fastcgi_connect_timeout 5s;
fastcgi_read_timeout 60s;
fastcgi_send_timeout 60s;
# ===== КЭШ =====
fastcgi_cache fastcgi_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 5m;
fastcgi_cache_valid 301 302 1m;
fastcgi_cache_valid 404 30s;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating
http_500 http_503;
fastcgi_cache_lock on;
fastcgi_cache_background_update on;
add_header X-FastCGI-Cache $upstream_cache_status;
}
}
}
Инвалидация кэша
# Очистить весь кэш proxy
find /var/cache/nginx/proxy -type f -delete
# Очистить конкретный URL (через модуль ngx_cache_purge):
# location ~ /purge(/.*) {
# fastcgi_cache_purge fastcgi_cache "$scheme$request_method$host$1";
# }
# curl -X PURGE http://your-server/api/products/123
# Мониторинг кэша:
# X-Cache-Status значения:
# HIT — ответ из кэша ✅
# MISS — кэш не нашёл, запрос к upstream
# BYPASS — кэш пропущен (skip_cache = 1)
# EXPIRED — кэш устарел, запрошен свежий ответ
# STALE — отдан устаревший кэш (upstream недоступен)
# UPDATING — отдан устаревший кэш пока фоновое обновление
# REVALIDATED — кэш подтверждён (304 от upstream)
Уровень 6: Upstream Keepalive и балансировка
http {
# ===== UPSTREAM С KEEPALIVE =====
upstream backend {
# Алгоритм балансировки
# least_conn — меньше соединений = меньше задержка (лучше для long-poll)
# ip_hash — один клиент всегда на один сервер (сессии)
# По умолчанию: round-robin
least_conn;
server 10.0.0.10:8080 weight=3; # Тройной вес (мощнее)
server 10.0.0.11:8080 weight=1;
server 10.0.0.12:8080 weight=1 backup; # Резервный (включается при падении основных)
# Параметры health check (требует nginx plus или upstream_check_module):
# server 10.0.0.10:8080 max_fails=3 fail_timeout=30s;
# ===== KEEPALIVE ПУЛА К UPSTREAM =====
# Количество keepalive соединений в пуле (на воркер!)
# НЕ максимальное число соединений — это пул переиспользуемых!
# Правило: (RPS / worker_count) × avg_response_time_sec × 1.5
# При 10000 RPS, 4 воркерах, 20мс ответе: 10000/4 × 0.02 × 1.5 = 75
keepalive 128;
# Таймаут keepalive соединения с upstream
keepalive_timeout 60s;
# Максимум запросов через одно keepalive соединение к upstream
keepalive_requests 10000;
}
server {
location / {
proxy_pass http://backend;
# КРИТИЧЕСКИ ВАЖНО для upstream keepalive!
# HTTP/1.1 поддерживает keepalive (1.0 — нет)
proxy_http_version 1.1;
# Убираем заголовок Connection: close (стандарт для HTTP/1.0 прокси)
proxy_set_header Connection "";
}
}
}
Уровень 7: SSL/TLS — производительность без потери безопасности
http {
# ===== SSL СЕССИИ =====
# Кэш SSL сессий (повторное использование TLS handshake)
# 1m ≈ 4000 сессий. Для highload: 50m-100m
ssl_session_cache shared:SSL:50m;
# Время жизни кэшированной SSL сессии
ssl_session_timeout 1d; # 24 часа — максимум рекомендуемый
# SSL Session Tickets (альтернатива session cache, статeful у клиента)
# Для идеальной forward secrecy — отключить
# Для максимальной производительности — включить
ssl_session_tickets off; # Безопаснее, но чуть медленнее
# ===== ПРОТОКОЛЫ И ШИФРЫ =====
# Только TLS 1.2 и 1.3 (1.0 и 1.1 — уязвимы и устарели)
ssl_protocols TLSv1.2 TLSv1.3;
# Шифры (Mozilla Modern конфигурация)
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;
# Сервер выбирает шифр (не клиент) — важно для безопасности
ssl_prefer_server_ciphers off; # off для TLS 1.3 (там нет выбора шифра)
# ===== ECDH КРИВАЯ =====
ssl_ecdh_curve X25519:prime256v1:secp384r1;
# ===== STAPLING =====
# OCSP Stapling: сервер сам проверяет сертификат и включает ответ в TLS handshake
# Клиенту не нужно делать отдельный запрос к CA — быстрее!
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 1.1.1.1 valid=300s;
resolver_timeout 5s;
# ===== DH параметры (для DHE шифров) =====
# Генерировать: openssl dhparam -out /etc/nginx/dhparam.pem 2048
# ssl_dhparam /etc/nginx/dhparam.pem;
server {
listen 443 ssl;
# ===== HTTP/2 =====
# Мультиплексирование запросов — один TCP для множества запросов
# Сжатие заголовков (HPACK) — экономит трафик
# Server Push — отправка ресурсов до запроса (редко нужен)
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# ===== HSTS =====
# Браузер не будет делать HTTP запросы — сразу HTTPS
# Включать только когда уверены что HTTPS работает корректно!
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# ===== SECURITY ЗАГОЛОВКИ =====
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 Permissions-Policy "geolocation=(), microphone=()" always;
}
# Редирект HTTP → HTTPS
server {
listen 80;
server_name example.com www.example.com;
# 301 для всего кроме .well-known (Let's Encrypt)
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
}
Измерение времени TLS handshake
# Проверка SSL без кэша (первое соединение)
curl -w "\n=== Timing ===\nDNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
--no-keepalive -s -o /dev/null https://your-server/
# С session resumption (второе соединение)
curl -w "TLS Resumption Total: %{time_total}s\n" \
--no-keepalive -s -o /dev/null https://your-server/ \
--tls-session-info
# Проверка OCSP Stapling:
echo QUIT | openssl s_client -connect your-server:443 -status 2>/dev/null | \
grep -A 17 'OCSP response'
Уровень 8: Rate Limiting — защита и QoS
http {
# ===== ЗОНЫ RATE LIMITING =====
# Ограничение по IP адресу
# zone=name:10m — 10МБ памяти (~160 000 IP адресов)
# rate=100r/s — 100 запросов в секунду с одного IP
limit_req_zone $binary_remote_addr zone=api_limit:20m rate=100r/s;
# Ограничение для авторизации (жёстче!)
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m;
# Ограничение по URL + IP (для конкретных эндпоинтов)
limit_req_zone "$binary_remote_addr$uri" zone=upload_limit:10m rate=5r/m;
# Ограничение одновременных соединений
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# Лог уровень для rejected запросов (warn — не засорять error.log)
limit_req_log_level warn;
# Код ответа при превышении лимита (429 = Too Many Requests)
limit_req_status 429;
limit_conn_status 429;
server {
# ===== API ENDPOINT =====
location /api/ {
# burst=200 — разрешить всплески до 200 запросов сверх лимита
# nodelay — не задерживать burst запросы, обрабатывать немедленно
# (без nodelay — запросы ставятся в очередь и задерживаются)
limit_req zone=api_limit burst=200 nodelay;
# Максимум 100 одновременных соединений с одного IP
limit_conn conn_limit 100;
proxy_pass http://backend;
}
# ===== АВТОРИЗАЦИЯ: СТРОГИЙ ЛИМИТ =====
location /api/auth/ {
limit_req zone=auth_limit burst=10 nodelay;
limit_conn conn_limit 10;
proxy_pass http://backend;
}
# ===== ЗАГРУЗКА ФАЙЛОВ =====
location /upload/ {
limit_req zone=upload_limit burst=2 nodelay;
limit_conn conn_limit 5;
client_max_body_size 100m;
proxy_pass http://backend;
}
}
}
Белые списки для rate limiting
http {
# Карта: 0 = применять limit, 1 = пропустить
geo $limit {
default 1; # Ограничивать всех
10.0.0.0/8 0; # Доверенная внутренняя сеть — без ограничений
192.168.0.0/16 0; # Локальная сеть — без ограничений
1.2.3.4 0; # Конкретный IP (мониторинг, партнёры)
}
# Если $limit = 0 — пустой ключ, limit_req не применяется
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=api_limit:20m rate=100r/s;
}
Уровень 9: Open File Cache и другие детали
http {
# ===== OPEN FILE CACHE =====
# Кэшировать информацию об открытых файлах:
# файловые дескрипторы, размеры, время модификации, ошибки
# Особенно важно при большом количестве файлов статики!
# max=10000 — максимум 10000 записей в кэше
# inactive=30s — удалять если не обращались 30 секунд
open_file_cache max=10000 inactive=30s;
# Сколько раз файл должен быть запрошен за inactive период
# чтобы остаться в кэше
open_file_cache_min_uses 2;
# Проверять актуальность кэша каждые 60 секунд
open_file_cache_valid 60s;
# Кэшировать ошибки (файл не найден, нет прав)
open_file_cache_errors on;
# ===== SENDFILE + AIO для больших файлов =====
# Для файлов > 8MB — асинхронный I/O эффективнее
aio threads; # AIO через thread pool (Nginx 1.7.11+)
# или aio on; # POSIX AIO (старый вариант, хуже)
directio 8m; # Файлы > 8MB: читать напрямую, минуя page cache
# Полезно для больших видеофайлов которые не нужно кэшировать
# ===== OUTPUT BUFFERS =====
# Размер буфера вывода (используется с sendfile)
output_buffers 2 512k;
# ===== ПЕРЕМЕННЫЕ =====
# Кэш переменных (для complex map и geo директив)
variables_hash_max_size 2048;
variables_hash_bucket_size 128;
# ===== MAP HASH =====
map_hash_max_size 2048;
map_hash_bucket_size 128;
}
Уровень 10: Полный production конфиг сервера
# /etc/nginx/conf.d/example.com.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/html;
index index.html index.php;
# SSL
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Логи с детальным форматом
access_log /var/log/nginx/example.com.access.log detailed buffer=64k flush=5s;
error_log /var/log/nginx/example.com.error.log warn;
# Скрываем .git, .env и другие служебные файлы
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~* \.(env|log|sh|sql|conf|config|bak|backup|swp|tmp)$ {
deny all;
}
# ===== СТАТИКА: МАКСИМАЛЬНАЯ ОТДАЧА =====
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|avif|woff|woff2|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
access_log off;
log_not_found off;
gzip_static on;
brotli_static on;
}
location ~* \.(js|css|map)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
gzip_static on;
brotli_static on;
}
# ===== FAVICON И ROBOTS =====
location = /favicon.ico {
access_log off;
log_not_found off;
expires 1y;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
# ===== API =====
location /api/ {
limit_req zone=api_limit burst=200 nodelay;
limit_conn conn_limit 100;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
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_cache proxy_cache;
proxy_cache_valid 200 5m;
proxy_cache_valid 404 30s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
proxy_cache_background_update on;
add_header X-Cache-Status $upstream_cache_status always;
}
# ===== PHP =====
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_read_timeout 60s;
fastcgi_cache fastcgi_cache;
fastcgi_cache_valid 200 5m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_lock on;
add_header X-FastCGI-Cache $upstream_cache_status;
}
# ===== КОРЕНЬ =====
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
Диагностика и мониторинг в продакшене
Stub Status модуль
server {
listen 127.0.0.1:8080;
location /nginx_status {
stub_status;
allow 127.0.0.1;
allow 10.0.0.0/8; # Сеть мониторинга
deny all;
}
}
# Парсинг nginx_status:
curl -s http://127.0.0.1:8080/nginx_status
# Active connections: 1247
# server accepts handled requests
# 87354291 87354291 245912847
# Reading: 12 Writing: 847 Waiting: 388
# Интерпретация:
# Active = Reading + Writing + Waiting
# Waiting = keepalive соединения (ждут следующего запроса)
# Writing = активно пишем ответ клиенту
# Reading = читаем запрос от клиента
# Если Waiting >> Writing — много keepalive соединений, это нормально
# Если Reading >> 0 постоянно — клиенты медленно отправляют запросы
# Если Writing = worker_processes × worker_connections — всё занято!
# accepts == handled — нет dropped connections. Если differs — проблема!
Анализ логов
# Медленные запросы (> 1 секунды)
awk '$NF > 1' /var/log/nginx/access.log | \
awk '{print $NF, $7}' | sort -rn | head -20
# HIT rate кэша
grep -o 'cs=[A-Z]*' /var/log/nginx/access.log | \
sort | uniq -c | sort -rn
# 94521 cs=HIT ← 87% HIT rate — хорошо!
# 12305 cs=MISS
# 1823 cs=BYPASS
# 289 cs=EXPIRED
# Ошибки upstream
grep 'upstream timed out\|connect() failed\|upstream prematurely' \
/var/log/nginx/error.log | tail -50
# Топ 5xx ошибок
awk '$9 ~ /^5/' /var/log/nginx/access.log | \
awk '{print $9, $7}' | sort | uniq -c | sort -rn | head -20
Чеклист тюнинга: финальная проверка
#!/bin/bash
# Быстрая проверка ключевых параметров
echo "=== NGINX PERFORMANCE CHECKLIST ==="
# 1. Worker processes
WP=$(nginx -T 2>/dev/null | grep 'worker_processes' | tail -1 | awk '{print $2}')
CORES=$(nproc)
echo "Worker processes: $WP (cores: $CORES)"
# 2. Worker connections
WC=$(nginx -T 2>/dev/null | grep 'worker_connections' | tail -1 | awk '{print $2}')
echo "Worker connections: $WC"
# 3. File descriptors
FD=$(cat /proc/$(cat /var/run/nginx.pid 2>/dev/null || echo 1)/limits \
2>/dev/null | grep 'open files' | awk '{print $4}')
echo "File descriptors limit: $FD"
# 4. sendfile
SF=$(nginx -T 2>/dev/null | grep -E '^\s+sendfile' | tail -1)
echo "Sendfile: $SF"
# 5. gzip
GZ=$(nginx -T 2>/dev/null | grep -E '^\s+gzip ' | tail -1)
echo "Gzip: $GZ"
# 6. SSL session cache
SSL=$(nginx -T 2>/dev/null | grep 'ssl_session_cache' | tail -1)
echo "SSL session cache: $SSL"
# 7. BBR
echo "TCP congestion: $(sysctl -n net.ipv4.tcp_congestion_control)"
# 8. Тест конфигурации
nginx -t && echo "Config: OK" || echo "Config: ERROR!"
echo ""
echo "=== ТЕКУЩАЯ НАГРУЗКА ==="
curl -s http://127.0.0.1:8080/nginx_status 2>/dev/null || echo "stub_status недоступен"
Типичные ошибки и мифы
Миф 1: "worker_processes 4096 увеличит производительность" Нет. Оптимум — по одному воркеру на ядро. Больше воркеров = больше переключений контекста = хуже.
Миф 2: "worker_connections 65535 — максимум соединений" Нет. Это максимум на один воркер. Итого: worker_processes × worker_connections. При 4 воркерах и 10240 соединениях = 40960 одновременных соединений.
Миф 3: "keepalive_timeout 0 ускорит сервер" Наоборот. Keepalive экономит TLS handshake и TCP установку соединения. Отключение keepalive нагрузит сервер больше.
Миф 4: "gzip_comp_level 9 — лучше" Нет. Разница в размере между уровнями 6 и 9 — 1-3%. Разница в CPU — в 3-5 раз. Используйте gzip_comp_level 6.
Ошибка: proxy_cache без proxy_cache_use_stale При недоступности upstream без use_stale клиенты получат 502. С use_stale error timeout — получат устаревший кэш пока upstream восстанавливается. Всегда включайте!
Ошибка: не настроен upstream keepalive Без keepalive в блоке upstream каждый запрос создаёт новое TCP соединение к backend. При 1000 RPS — 1000 новых TCP handshake в секунду. С keepalive 64 — переиспользуются 64 соединения.
Заключение
Тюнинг Nginx — это итеративный процесс. Хороший порядок:
Системный уровень: sysctl, ulimit, BBR — без этого упрётесь в ОС
Workers и events:
worker_processes auto,multi_accept on,epollБуферы и таймауты: адаптируйте под характер трафика (размер ответов, скорость клиентов)
Кэш: proxy_cache или fastcgi_cache — самый большой прирост производительности
Upstream keepalive: критично для высоких RPS
SSL оптимизация: session cache + stapling + HTTP/2
Сжатие: gzip + brotli_static для предкомпрессии
Rate limiting: защита без ущерба для легитимного трафика
Измеряйте до и после каждого изменения. Доверяйте цифрам, а не интуиции. И помните: лучший тюнинг — тот, который решает вашу конкретную проблему, а не скопированный из статьи.
Create an account or sign in to leave a review
There are no reviews to display.