Оптимизация Nginx под высокую нагрузку — это не магия и не тонна секретных директив, а четыре понятных блока работы: воркеры и соединения, keepalive, кеширование и лимиты. Nginx «из коробки» настроен консервативно: дефолтный конфиг рассчитан на слабую машину и умеренный трафик. Когда на сайт приходит рекламная кампания, пост в соцсетях или просто органический рост, дефолтов перестаёт хватать — появляются ошибки 502/504, растёт время ответа, соединения упираются в лимиты. В этой статье разберём, какие параметры реально влияют на производительность Nginx, приведём рабочие конфиги для Ubuntu 24.04 LTS и покажем, как проверить результат цифрами, а не ощущениями.
Все примеры проверены на Nginx из стандартных репозиториев Ubuntu 24.04. Если вы ещё не ставили веб-сервер, начните со статьи об установке и базовой настройке Nginx — здесь мы будем дорабатывать уже работающую конфигурацию.
Шаг 1. worker_processes и worker_connections: фундамент производительности
Nginx работает по событийной модели: несколько процессов-воркеров обслуживают тысячи соединений каждый. Два параметра задают «потолок» сервера:
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 8192;
multi_accept on;
}
Что здесь важно:
- worker_processes auto — Nginx создаст по одному воркеру на ядро CPU. Это оптимально: воркер почти не блокируется, и больше процессов, чем ядер, дадут только лишние переключения контекста. На VPS с 20 vCPU будет 20 воркеров.
- worker_connections — максимум соединений на один воркер. Дефолтные 768 под нагрузкой заканчиваются мгновенно. 4096–8192 — разумная планка; теоретический максимум клиентов ≈ worker_processes × worker_connections.
- worker_rlimit_nofile — лимит открытых файловых дескрипторов для воркера. Каждое соединение — это дескриптор (а при проксировании — два), поэтому значение должно быть минимум вдвое больше worker_connections.
- multi_accept on — воркер принимает все новые соединения сразу, а не по одному за итерацию цикла событий. Под всплесками трафика это снижает задержку установления соединения.
Проверьте текущие лимиты systemd-юнита — они тоже могут ограничивать Nginx:
systemctl show nginx | grep LimitNOFILE
Если значение маленькое, поднимите его через drop-in:
sudo mkdir -p /etc/systemd/system/nginx.service.d
printf '[Service]\nLimitNOFILE=65535\n' | sudo tee /etc/systemd/system/nginx.service.d/limits.conf
sudo systemctl daemon-reload && sudo systemctl restart nginx
Шаг 2. Keepalive: экономим на установке соединений
Установка TCP-соединения и TLS-хендшейк — самые дорогие операции в HTTP-обмене. Keepalive позволяет переиспользовать уже открытое соединение для десятков запросов подряд. Настраивается в двух местах: для клиентов и для бэкенда.
Keepalive для клиентов
# в блоке http { }
keepalive_timeout 30;
keepalive_requests 1000;
keepalive_timeout 30 — соединение живёт 30 секунд после последнего запроса. Больше 60–75 секунд ставить не стоит: тысячи простаивающих соединений занимают память и слоты worker_connections. keepalive_requests 1000 — сколько запросов можно отправить через одно соединение (дефолт 1000 в свежих версиях, в старых был 100 — проверьте).
Keepalive к бэкенду (upstream)
Частая ошибка: клиентский keepalive настроен, а к PHP-FPM или Node.js Nginx открывает новое соединение на каждый запрос. Исправляется так:
upstream backend {
server 127.0.0.1:3000;
keepalive 64;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Директива keepalive 64 держит до 64 простаивающих соединений с бэкендом на каждый воркер. Строки proxy_http_version 1.1 и пустой заголовок Connection обязательны — без них keepalive к upstream не работает. Заодно включите современные протоколы для клиентов — HTTP/2 и HTTP/3 в Nginx мультиплексируют запросы в одном соединении и дополнительно снижают накладные расходы.
Шаг 3. Кеширование: главный множитель производительности
Никакой тюнинг воркеров не сравнится по эффекту с кешированием. Если Nginx отдаёт ответ из кеша, бэкенд вообще не работает — а именно бэкенд (PHP, Python, Node.js, база данных) почти всегда является узким местом.
Кеш ответов бэкенда (proxy_cache / fastcgi_cache)
# в блоке http { }
proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=appcache:50m
max_size=2g inactive=60m use_temp_path=off;
server {
location / {
proxy_pass http://backend;
proxy_cache appcache;
proxy_cache_valid 200 301 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}
}
Разберём ключевые директивы:
- keys_zone=appcache:50m — 50 МБ разделяемой памяти под ключи кеша (хватит примерно на 400 тысяч записей), сами ответы лежат на диске. На NVMe-дисках чтение из такого кеша занимает доли миллисекунды — на тарифах Cheap-Host NVMe стоит во всех конфигурациях, и дисковый кеш работает почти как память.
- proxy_cache_use_stale — если бэкенд упал или обновляет запись, Nginx отдаст устаревшую копию вместо ошибки 502. Под нагрузкой это спасает сайт при сбоях приложения.
- proxy_cache_lock on — при промахе кеша к бэкенду уйдёт только один запрос, остальные подождут результата. Защищает от «эффекта толпы» (cache stampede).
- X-Cache-Status — заголовок для отладки: HIT, MISS, STALE. Позволяет измерить долю попаданий в кеш.
Для PHP-FPM всё аналогично, только директивы называются fastcgi_cache_path, fastcgi_cache и т.д. Кеширование на стороне приложения (объектный кеш, запросы к БД) тоже никто не отменял — про это есть отдельная статья о Redis и кешировании сайта.
Статика: кеш в браузере и open_file_cache
location ~* \.(css|js|png|jpg|jpeg|webp|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# в блоке http { }
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
sendfile on;
tcp_nopush on;
open_file_cache кеширует дескрипторы и метаданные часто запрашиваемых файлов — Nginx перестаёт дёргать файловую систему на каждый запрос статики. sendfile передаёт файл из ядра прямо в сокет без копирования в память процесса, а tcp_nopush отправляет заголовки и начало файла одним пакетом. Не забудьте и про сжатие — Gzip и Brotli сокращают трафик в 3–5 раз и разгружают канал.
Шаг 4. Лимиты: защищаем сервер от перегрузки
Высокая нагрузка — это не только легитимные пользователи, но и агрессивные боты, кривые интеграции и всплески трафика. Лимиты не ускоряют Nginx, но не дают одному источнику положить сервер для всех остальных.
# в блоке http { }
limit_req_zone $binary_remote_addr zone=perip:20m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:20m;
server {
location / {
limit_req zone=perip burst=30 nodelay;
limit_conn addr 20;
}
location /api/ {
limit_req zone=perip burst=10 nodelay;
}
}
- limit_req_zone … rate=10r/s — не более 10 запросов в секунду с одного IP. Зона на 20 МБ хранит состояние примерно для 320 тысяч адресов.
- burst=30 nodelay — краткий всплеск до 30 запросов пройдёт без задержки (это нормально для загрузки страницы с ресурсами), а вот стабильный поток выше лимита получит 503.
- limit_conn addr 20 — не более 20 одновременных соединений с одного IP: отсекает качалки и медленные атаки на исчерпание соединений.
Полезно также ограничить размеры и таймауты запросов — медленные клиенты не должны занимать воркеры минутами:
client_max_body_size 20m;
client_body_timeout 15s;
client_header_timeout 15s;
send_timeout 15s;
reset_timedout_connection on;
Проверяем результат: цифры вместо ощущений
Любую оптимизацию нужно измерять. Минимальный набор:
- Проверить конфиг и применить без разрыва соединений:
sudo nginx -t && sudo systemctl reload nginx. - Включить
stub_statusи следить за активными соединениями:
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
- Прогнать нагрузочный тест до и после изменений:
wrk -t8 -c500 -d60s https://example.com/покажет RPS, задержки и ошибки. Подробная методика — в статье о нагрузочном тестировании: ab, wrk и k6. - Смотреть долю попаданий в кеш по заголовку X-Cache-Status в логах.
Типичная картина после тюнинга: на страницах, попадающих в proxy_cache, RPS вырастает в 10–50 раз, а p99-задержка падает с сотен миллисекунд до единиц.
Какие ресурсы сервера нужны под высокую нагрузку
Nginx масштабируется по ядрам почти линейно, кеш любит быстрый диск, а бэкенд — память. Ориентиры такие:
| Нагрузка | Что важно | Подходящий тариф Cheap-Host |
|---|---|---|
| До ~300 RPS, сайт + PHP | 4–6 ядер, 8 ГБ RAM | Start — 450 ₽/мес (6 vCPU, 8 ГБ, NVMe 96 ГБ) |
| До ~1000 RPS, кеширование, несколько сайтов | 8–12 ядер, 16 ГБ RAM | Pro — 750 ₽/мес (12 vCPU, 16 ГБ, NVMe 128 ГБ) |
| Тысячи RPS, тяжёлый бэкенд, пики трафика | Максимум ядер и RAM, быстрый диск | Ultra — 1500 ₽/мес (20 vCPU, 32 ГБ, NVMe 192 ГБ) |
Важный нюанс: worker_processes auto имеет смысл только тогда, когда ядра действительно ваши. На KVM-виртуализации (как у Cheap-Host) ресурсы выделенные, и 20 воркеров на тарифе Ultra — это реально 20 параллельных потоков обработки, а не «до 20, если соседи не мешают». Плюс безлимитный трафик: при всплесках нагрузки не придётся считать гигабайты.
FAQ
Сколько worker_processes ставить в Nginx?
Ставьте worker_processes auto — по воркеру на ядро. Больше ядер ставить бессмысленно: воркеры начнут конкурировать за CPU, и задержки вырастут. Исключение — если воркеры часто блокируются на дисковом вводе-выводе без aio, но на NVMe-дисках это редкость.
Какое значение keepalive_timeout оптимально под нагрузкой?
15–30 секунд. Большой таймаут (например, 300) держит тысячи простаивающих соединений и съедает память и слоты соединений; слишком маленький (1–2 секунды) заставляет браузеры заново проходить TCP- и TLS-хендшейки.
Что даёт proxy_cache и когда его включать?
proxy_cache отдаёт сохранённые ответы бэкенда без обращения к приложению. Если страница может быть неактуальной хотя бы 1–10 минут — её можно кешировать, и нагрузка на бэкенд падает в десятки раз. Включайте, как только бэкенд становится узким местом.
Как проверить, что оптимизация помогла?
Нагрузочный тест до и после (wrk, ab): сравните RPS, p99-задержку и долю ошибок. Плюс stub_status для соединений и заголовок X-Cache-Status для оценки доли попаданий в кеш.
Хватит ли VPS для 1000 запросов в секунду?
Для статики и закешированных ответов — да, с запасом. Узкое место обычно бэкенд, поэтому кеширование важнее «железа». Для тяжёлых проектов с пиками берите запас по ядрам и памяти — уровень Ultra: 20 vCPU и 32 ГБ RAM.
Вывод
Порядок работ при оптимизации Nginx под нагрузку: сначала worker_processes auto, worker_connections и лимиты дескрипторов, затем keepalive к клиентам и бэкенду, потом кеширование (оно даёт основной прирост) и в конце — limit_req/limit_conn как страховка от перегрузки. Каждое изменение проверяйте нагрузочным тестом, а не «на глаз».
Если под нагруженный проект нужен сервер — у Cheap-Host есть тариф Ultra за 1500 ₽/мес: 20 vCPU, 32 ГБ RAM, NVMe 192 ГБ, KVM с выделенными ресурсами, безлимитный трафик и выдача сервера за 45–60 секунд. Посмотреть все тарифы можно на cheap-host.onl, а при росте нагрузки CPU, RAM и диск апгрейдятся без потери данных.