Тюнинг производительности Nginx на VPS

11 мин чтения·Matthieu·vpslinuxperformancenginx|

Настройка Nginx для продакшн-трафика на Linux VPS. Worker-процессы, сжатие, кеширование, HTTP/2, TLS, параметры ядра и бенчмарки с wrk.

Дефолтная установка Nginx справляется с умеренным трафиком без проблем. Но дефолтные настройки консервативны. На VPS с 4 vCPU и 8 ГБ оперативки можно выжать гораздо больше запросов в секунду, подкрутив worker-процессы, сжатие, кеширование и параметры ядра. Этот гайд проходит по каждому уровню, с бенчмарками как доказательство эффекта каждого изменения.

Предполагаем, что Nginx уже установлен и раздаёт трафик. Если нет, начни с руководства по администрированию Nginx на VPS.

Все примеры рассчитаны на Nginx mainline на Debian 12 или Ubuntu 24.04. Структура конфигурационных файлов описана в руководстве по структуре конфигурационных файлов Nginx.

Как установить базовую линию производительности Nginx?

Прежде чем менять что-либо, замерь текущую производительность с помощью wrk. Это даёт базовую линию для сравнения после тюнинга. Без цифр ты просто гадаешь.

Установи wrk на отдельной машине (локальной рабочей станции или другом VPS). Никогда не запускай бенчмарк с того же сервера, который тестируешь. Инструмент бенчмарка и веб-сервер будут конкурировать за CPU, и результаты потеряют смысл.

apt install wrk

Запусти 30-секундный тест с 4 потоками и 200 соединениями на статическую страницу:

wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    12.34ms    5.67ms  89.12ms   78.45%
    Req/Sec     4.12k   312.45     5.23k    72.50%
  493440 requests in 30.01s, 1.92GB read
Requests/sec:  16442.18
Transfer/sec:     65.52MB

Запиши четыре числа: requests/sec, среднюю задержку, максимальную задержку и transfer/sec. Это твоя базовая линия.

Сколько worker-процессов и соединений должен использовать Nginx?

Установи worker_processes auto для создания одного воркера на ядро CPU. На VPS с 4 vCPU это 4 воркера. Каждый воркер однопоточный и обрабатывает соединения независимо через epoll. Один воркер на ядро избавляет от накладных расходов на переключение контекста.

Формула для максимального числа одновременных соединений:

макс. соединений = worker_processes x worker_connections

vCPU worker_processes worker_connections Макс. соединений
1 1 2048 2 048
2 2 2048 4 096
4 4 2048 8 192
8 8 2048 16 384

Каждое соединение потребляет файловый дескриптор. Установи worker_rlimit_nofile выше worker_connections, чтобы не упереться в лимит ОС. Безопасное значение — worker_connections * 2, что учитывает upstream-соединения при проксировании.

Отредактируй /etc/nginx/nginx.conf:

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

worker_cpu_affinity auto привязывает каждый воркер к ядру CPU, уменьшая промахи кеша из-за миграции процессов. multi_accept on позволяет воркеру принять все ожидающие соединения разом, а не по одному. use epoll — дефолт в Linux, но лучше указать явно.

Директива accept_mutex по дефолту off с Nginx 1.11.3, потому что ядра Linux 4.5+ поддерживают EPOLLEXCLUSIVE, который распределяет соединения между воркерами без мьютекса. Оставь выключенным.

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx

Какие TCP-директивы улучшают пропускную способность Nginx?

Три директивы работают вместе, оптимизируя отправку данных через TCP. sendfile обходит буфер пользовательского пространства, копируя данные напрямую между файловыми дескрипторами в ядре. tcp_nopush объединяет заголовки ответа и начало файла в один TCP-пакет. tcp_nodelay отключает алгоритм Нагла, чтобы мелкие пакеты (вроде конца ответа) отправлялись сразу.

Добавь в блок http файла /etc/nginx/nginx.conf:

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    #...существующие директивы...
}

sendfile больше всего влияет на раздачу статических файлов. Без неё Nginx читает файл в буфер, затем пишет буфер в сокет. Два копирования. С sendfile ядро делает zero-copy передачу. На нагруженном файловом сервере это одно уже заметно снижает нагрузку на CPU.

tcp_nopush и tcp_nodelay не противоречат друг другу. Nginx применяет tcp_nopush при формировании ответа, затем переключается на tcp_nodelay для последнего пакета. Результат: меньше пакетов в целом, без задержки на последнем.

Как настроить keepalive-соединения для Nginx?

Keepalive-соединения позволяют клиенту повторно использовать TCP-соединение для нескольких HTTP-запросов. Это избавляет от накладных расходов на TCP-рукопожатие и TLS-согласование при каждом запросе. Одна загрузка страницы может вызвать 20-50 подзапросов для CSS, JS, картинок и шрифтов.

http {
    keepalive_timeout 65;
    keepalive_requests 1000;

    #...существующие директивы...
}

keepalive_timeout 65 закрывает неактивные соединения через 65 секунд. Слишком высокое значение тратит файловые дескрипторы на простаивающих клиентов. Слишком низкое — вынуждает переподключаться. 65 секунд — разумный дефолт для большинства нагрузок.

keepalive_requests 1000 разрешает до 1 000 запросов на соединение, после чего Nginx его закрывает. Дефолтное значение — 1000 с Nginx 1.19.10. Если ты за балансировщиком, отправляющим много запросов на соединение, увеличь до 10000.

Upstream keepalive

Если Nginx проксирует на бэкенд (Node.js, Python, Go), upstream keepalive-соединения не дают Nginx открывать новое TCP-соединение к бэкенду на каждый запрос. Именно тут большинство прокси-конфигов теряют время.

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 обязателен, потому что keepalive — фича HTTP/1.1. Пустой заголовок Connection очищает Connection: close клиента, чтобы Nginx не передавал его на upstream.

Подробнее о настройке проксирования — в руководстве по настройке reverse proxy в Nginx.

Как подобрать размер прокси-буферов Nginx?

Когда Nginx проксирует запрос, он буферизует ответ бэкенда. Если буфер слишком маленький, Nginx записывает ответ во временный файл на диске. Дисковый I/O на порядки медленнее оперативки.

http {
    proxy_buffer_size 16k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;

    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    #...существующие директивы...
}

proxy_buffer_size 16k обрабатывает заголовки ответа от бэкенда. Большинство заголовков укладывается в 4k-8k, но приложения с кучей кук или кастомных заголовков требуют больше. 16k — безопасно и не расточительно.

proxy_buffers 4 32k выделяет 4 буфера по 32k (всего 128k на соединение) для тела ответа. Подбирай размер под типичный размер ответов. API-ответы до 100k влезут без проблем. Если раздаёшь большие payload'ы, увеличивай количество буферов, а не их размер.

proxy_busy_buffers_size 64k контролирует, сколько забуферизованных данных Nginx может отправить клиенту, пока ещё читает от бэкенда. Не должно превышать сумму proxy_buffers.

Следи за этим в логе ошибок:

an upstream response is buffered to a temporary file

Если видишь это часто, увеличь proxy_buffers. Проверь лог:

journalctl -u nginx --no-pager | grep "temporary file"

Как настроить кеширование статических файлов в Nginx?

Кеширование статики говорит браузерам хранить ресурсы локально. Это полностью исключает повторные запросы. Для ресурсов с хешированными именами (вроде app.a1b2c3.js) устанавливай агрессивное время жизни. Для HTML — короткое.

server {
    location ~* \.(css|js|woff2|woff|ttf|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|webp|avif)$ {
        expires 30d;
        add_header Cache-Control "public";
        access_log off;
    }

    location ~* \.html$ {
        expires 1h;
        add_header Cache-Control "public, no-cache";
    }
}

immutable говорит браузеру вообще не ревалидировать ресурс. Используй только для файлов с fingerprint в имени. access_log off на статике снижает дисковый I/O от логирования.

Про заголовки кеширования и их взаимодействие с заголовками безопасности — см..

open_file_cache

Nginx может кешировать файловые дескрипторы, время модификации и проверки существования для часто запрашиваемых файлов. Это избавляет от повторных системных вызовов stat() и open().

http {
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

max=10000 хранит до 10 000 записей. Подбирай под количество раздаваемых статических файлов. Если раздаёшь 500 файлов, max=1000 хватит. Если 50 000 ресурсов с CDN-ориджина — увеличивай.

inactive=30s удаляет записи, к которым не обращались 30 секунд. open_file_cache_min_uses 2 кеширует только файлы, запрошенные минимум дважды за период inactive. Это не даёт одноразовым запросам засорять кеш.

open_file_cache_errors on кеширует и 404-ответы. Если клиент повторно запрашивает несуществующий файл, Nginx отвечает из кеша, не обращаясь к файловой системе.

Как включить gzip- и Brotli-сжатие в Nginx?

Gzip на уровне 4-6 даёт лучший баланс между нагрузкой на CPU и степенью сжатия. Выше уровня 6 выигрыш — меньше 2% при двукратном увеличении времени CPU. Brotli на уровне 4 обычно сжимает лучше, чем gzip на уровне 9, при сопоставимой нагрузке на CPU.

Gzip

http {
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_vary on;
    gzip_proxied any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml;
}

gzip_min_length 256 пропускает файлы меньше 256 байт. Сжатие крошечных файлов может дать результат больше оригинала из-за заголовков gzip. gzip_vary on добавляет заголовок Vary: Accept-Encoding, чтобы кеши хранили сжатую и несжатую версии отдельно. gzip_proxied any сжимает ответы, даже если запрос пришёл через прокси.

Brotli

Brotli даёт на 15-25% лучшее сжатие, чем gzip, на текстовых ресурсах. Поддерживается всеми современными браузерами. На Ubuntu 24.04 с пакетом Nginx из дистрибутива модуль ставится напрямую:

apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static

На Debian 12 или при использовании Nginx mainline с nginx.org модуль Brotli не включён. Нужно скомпилировать его как динамический модуль или использовать сторонний репозиторий. В репозитории ngx_brotli есть инструкции по сборке.

После загрузки модуля настрой его:

http {
    brotli on;
    brotli_comp_level 4;
    brotli_static on;
    brotli_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        image/svg+xml;
}

brotli_static on раздаёт заранее сжатые .br-файлы, если они есть. Это позволяет сжимать ресурсы на этапе сборки с высоким уровнем сжатия (например, 11) без затрат CPU в рантайме.

brotli_comp_level 4 — оптимальная точка для динамического сжатия. В отличие от gzip, уровни 1-4 у Brotli быстрые. Уровни 5+ становятся заметно медленнее.

Сравнение сжатия

Тип контента gzip уровень 5 Brotli уровень 4 Победитель
HTML 72% 78% Brotli
CSS 80% 85% Brotli
JavaScript 75% 82% Brotli
JSON 78% 83% Brotli

Проценты показывают сэкономленные байты относительно оригинала. Brotli стабильно выигрывает с отрывом в 5-8 процентных пунктов.

Оба модуля могут работать одновременно. Nginx отдаёт Brotli клиентам, объявляющим Accept-Encoding: br, и откатывается на gzip для остальных.

Как оптимизировать производительность TLS и HTTP/2 в Nginx?

TLS добавляет задержку из-за рукопожатий и обмена ключами. Кеширование сессий, OCSP stapling и TLS 1.3 минимизируют эти накладные расходы. HTTP/2 мультиплексирует запросы через одно соединение, устраняя head-of-line blocking на уровне HTTP.

HTTP/2

С Nginx 1.25.1 параметр http2 в директиве listen устарел. Используй директиву http2:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;

    #...
}

Производительность TLS

http {
    ssl_protocols TLSv1.2 TLSv1.3;
    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;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 127.0.0.53 valid=300s;
    resolver_timeout 5s;
}

ssl_session_cache shared:SSL:10m хранит параметры TLS-сессий в разделяемой памяти размером 10 МБ. Один мегабайт вмещает около 4 000 сессий. Возвращающиеся клиенты пропускают полное TLS-рукопожатие и возобновляют сессию гораздо дешевле.

ssl_session_tickets off — более безопасный дефолт. Тикеты сессий используют симметричный ключ, который при компрометации позволяет расшифровать все прошлые сессии (нет forward secrecy). Если нужны тикеты для мультисерверных конфигураций, часто ротируй ключи.

ssl_stapling on заставляет Nginx получить и закешировать OCSP-ответ от CA, а затем включить его в TLS-рукопожатие. Клиенту не нужно отдельно обращаться к CA. Это экономит 100-300 мс на первом соединении.

ssl_prefer_server_ciphers off корректно для TLS 1.3, где клиент и сервер согласовывают шифры иначе. Для обратной совместимости с TLS 1.2 выбранные шифры всё ещё важны, но все наборы шифров TLS 1.3 достаточно сильные.

Полная настройка TLS и Let's Encrypt — в руководстве по TLS и Let's Encrypt для Nginx.

Какие параметры ядра Linux улучшают производительность Nginx?

Четыре параметра ядра ограничивают способность Nginx обрабатывать большие объёмы соединений. Дефолтные значения консервативны для сервера общего назначения. Подкрутив их, убираешь узкие места на уровне ОС.

Параметр По умолчанию Рекомендовано Зачем
net.core.somaxconn 4096 65535 Максимальная очередь listen backlog. Низкие значения вызывают потерю соединений при всплесках.
fs.file-max ~100000 500000 Системный лимит файловых дескрипторов. Каждое соединение — файловый дескриптор.
net.ipv4.tcp_tw_reuse 0 1 Повторное использование TIME_WAIT сокетов для новых соединений. Ускоряет оборот соединений.
net.ipv4.tcp_fastopen 0 3 Включает TCP Fast Open для клиента и сервера. Экономит один round trip на новых соединениях.
net.ipv4.ip_local_port_range 32768 60999 1024 65535 Расширяет диапазон эфемерных портов для исходящих соединений (прокси/upstream).
net.core.netdev_max_backlog 1000 16384 Длина очереди входящих пакетов, когда интерфейс принимает быстрее, чем ядро обрабатывает.

Применить на лету без перезагрузки:

sysctl -w net.core.somaxconn=65535
sysctl -w fs.file-max=500000
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.core.netdev_max_backlog=16384

Сделать постоянными после перезагрузки:

cat > /etc/sysctl.d/99-nginx-tuning.conf << 'EOF'
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384
EOF
sysctl -p /etc/sysctl.d/99-nginx-tuning.conf
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384

Также увеличь лимит файловых дескрипторов на процесс для systemd-юнита Nginx. Создай override:

mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=65535
EOF
systemctl daemon-reload
systemctl restart nginx
cat /proc/$(pgrep -f 'nginx: master')/limits | grep "open files"
Max open files            65535                65535                files

Как оптимизировать производительность access-логов?

Запись строки лога на каждый запрос потребляет дисковый I/O. На высоконагруженных серверах логирование доступа может стать узким местом. Буферизованное логирование пишет на диск пачками.

http {
    access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
}

buffer=64k накапливает записи лога в буфере на 64 КБ в памяти. flush=5s сбрасывает буфер на диск не реже чем раз в 5 секунд, даже если он не заполнен. Обмен: пара секунд задержки в логах на заметно меньший дисковый I/O.

Если access-логи для статики (картинки, CSS, JS) не нужны, отключи их по location, как показано в разделе кеширования выше.

Насколько быстрее становится тюнингованный Nginx?

Запусти тот же бенчмарк wrk после применения всех изменений. Тестируй с той же машины, с теми же параметрами:

wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.21ms    2.34ms  42.56ms   81.23%
    Req/Sec     9.78k   478.12    11.42k    68.75%
  1171200 requests in 30.02s, 4.56GB read
Requests/sec:  39013.66
Transfer/sec:    155.61MB

До vs. после

Метрика До После Изменение
Requests/sec 16 442 39 014 +137%
Ср. задержка 12,34 мс 5,21 мс -58%
Макс. задержка 89,12 мс 42,56 мс -52%
Transfer/sec 65,52 МБ 155,61 МБ +137%

Эти цифры получены на VPS Virtua Cloud с 4 vCPU, 8 ГБ RAM под Debian 12 с Nginx mainline, раздающем статическую HTML-страницу с CSS и JavaScript. Твои результаты будут отличаться в зависимости от нагрузки, сетевых условий и наличия проксирования на бэкенд.

Самый большой выигрыш дают sysctl ядра (убирает узкие места ОС), тюнинг воркеров/соединений (использует весь доступный CPU) и сжатие (уменьшает байты на проводе). Кеширование TLS-сессий и HTTP/2 дают меньший, но измеримый эффект, особенно на задержке первого соединения.

Полная оптимизированная конфигурация

Полный /etc/nginx/nginx.conf со всеми настройками:

user www-data;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Оптимизация TCP
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keepalive
    keepalive_timeout 65;
    keepalive_requests 1000;

    # Буферы
    proxy_buffer_size 16k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;
    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # Файловый кеш
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Gzip
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_vary on;
    gzip_proxied any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml;

    # Brotli (если модуль установлен)
    # brotli on;
    # brotli_comp_level 4;
    # brotli_static on;
    # brotli_types text/plain text/css text/javascript
    #     application/javascript application/json application/xml
    #     image/svg+xml;

    # TLS
    ssl_protocols TLSv1.2 TLSv1.3;
    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;

    # Логирование
    access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
    error_log /var/log/nginx/error.log warn;

    # Скрыть версию
    server_tokens off;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

server_tokens off скрывает версию Nginx в заголовках ответа и страницах ошибок. Раскрытие версии помогает злоумышленникам целиться в известные уязвимости.

Что-то пошло не так?

Сначала проверь лог ошибок:

journalctl -u nginx -f

Типичные проблемы после тюнинга:

  • "too many open files" в логе ошибок: worker_rlimit_nofile ниже worker_connections, или в systemd не задан LimitNOFILE. Проверь оба.
  • "could not build optimal types_hash": увеличь types_hash_max_size до 4096 в блоке http.
  • Модуль Brotli не загружается: выполни nginx -V 2>&1 | grep brotli, чтобы проверить, скомпилирован ли модуль. При использовании динамических модулей убедись, что директивы load_module есть в начале nginx.conf.
  • OCSP stapling не работает: первый запрос после старта Nginx не будет содержать stapled-ответ. Проверь с помощью openssl s_client -connect your-server:443 -status < /dev/null 2>&1 | grep -A 2 "OCSP Response". Если показывает "no response sent", убедись, что ssl_trusted_certificate указывает на полную цепочку и resolver задан.
  • wrk не показывает улучшений: убедись, что тестируешь с другой машины. При тестировании через интернет сетевая задержка доминирует и скрывает серверные улучшения. Тестируй с VPS в том же дата-центре для точных цифр.

Авторское право 2026 Virtua.Cloud. Все права защищены. Данный контент является оригинальным произведением команды Virtua.Cloud. Воспроизведение, повторная публикация или распространение без письменного разрешения запрещены.

Готовы попробовать?

Разверните свой сервер за секунды. Linux, Windows или FreeBSD.

Смотреть тарифы VPS