VPSSPark Блог
← Вернуться к дневнику

Как устранить узкое место по пропускной способности: настройка Nginx reverse proxy

Заметки о серверах · 2026.07.22 · ~14 мин чтения

Частый поиск: настройка Nginx reverse proxy · оптимизация пропускной способности · gzip

Серверные стойки и сетевые коммутаторы—Nginx reverse proxy и оптимизация канала
Узкое место по каналу чаще связано с конфигурацией, а не с нехваткой CPU.

В прошлом месяце мы разбирали проблему клиента с SaaS-бэкендом: VPS 2 vCPU / 4 ГБ, CPU стабильно около 20 %, а каждый день в 15:00 сайт превращался в слайд-шоу. На графиках облака исходящая полоса упиралась в потолок — 100 Мбит/с насыщались сорок минут подряд. Первая мысль: «Пора апгрейдить». Мы открыли access-лог Nginx: 70 % трафика — несжатые JSON-ответы API и статический JS с случайным ?v= в URL; одни и те же ресурсы отдавались целиком снова и снова.

После Gzip, заголовков кэша для статики и upstream keepalive пик упал до 35 Мбит/с, загрузка — с 8 до 1,2 с, без лишних расходов. На небольших VPS это типично: узкое место — исходящая полоса, а не CPU. Nginx как reverse proxy между пользователями и бэкендом — лучший рычаг экономии трафика, если знать, какие «винты» крутить.

Это не гайд по установке Nginx, а чеклист оптимизации под полосу пропускания, проверенный в продакшене: диагностика, сжатие, кэш, затем переиспользование соединений и буферизация. Предполагается, что Nginx уже стоит как HTTPS reverse proxy на 443 (если файрвола ещё нет — сначала наш гайд по UFW для облачного сервера: открыть 22/80/443, потом оптимизировать).

~65 %
Типичное сжатие JSON после Gzip
4 шага
Диагностика→Сжатие→Кэш→Соединения
0 ₽
Большинство оптимизаций без апгрейда

Почему упирается полоса, а не CPU?

У облачных VPS тарификация полосы часто асимметрична: небольшой пакет — 100 Мбит/с пик и 1–2 ТБ в месяц; кажется достаточно, но несжатый JSON 500 КБ при 200 req/s теоретически даёт 800 Мбит/с и мгновенно пробивает лимит. Nginx отдаёт статику и простой reverse proxy с низкой загрузкой CPU — отсюда картина «CPU свободен, полоса забита».

Ещё одна ошибка — гнать весь трафик на origin. Без CDN, без кэша браузера, без proxy_cache каждый пользователь при обновлении снова тянет bundle.js и logo.png с VPS. http-модули в официальной документации Nginx по сути помогают передавать меньше байт или открывать меньше соединений — важно комбинировать по сценарию, а не копировать «универсальный» конфиг.

Оптимизация полосы Nginx reverse proxy в четыре шага: диагностика, сжатие, кэш, переиспользование соединений
Сначала бесплатные меры (gzip, expires, keepalive), затем CDN или расширение полосы.
Симптом Частая причина Первое действие
Медленный API, высокая полоса JSON/HTML без сжатия Включить gzip / brotli
Большой трафик при обновлении Статика без Cache-Control expires + имена с хешем
Рост соединений к бэкенду Новый upstream TCP на запрос Пул keepalive
Крупные загрузки забивают исходящий канал Прямая отдача с origin, нет сегментного кэша CDN или proxy_cache

Шаг 1: сначала диагностика, не слепой апгрейд полосы

Перед правками конфига — десять минут, чтобы понять, куда уходит трафик. На сервере обычно используем:

Диагностика полосы и соединений
# Трафик по соединениям в реальном времени (apt install iftop)
                sudo iftop -i eth0

                # Почасовая/дневная статистика (vnstat)
                vnstat -h
                vnstat -d

                # Access-лог Nginx: самые тяжёлые URL
                awk '{print $7, $10}' /var/log/nginx/access.log | \
                  awk '{a[$1]+=$2} END {for(i in a) print a[i], i}' | sort -rn | head -20

                # Текущие соединения ESTABLISHED
                ss -s
                ss -tn state established | wc -l

Если в топе /api/ с телами по сотням КБ — приоритет №1 сжатие. Если .js, .css, .woff2 всегда с кодом 200 (без 304) — не настроены заголовки кэша. Разделяйте входящий и исходящий трафик: DDoS и краулеры забивают вход — другая тема (rate limit, WAF, fail2ban). Здесь — оптимизация исходящего в нормальной нагрузке.

Графики полосы в облачной консоли агрегируются с задержкой 1–5 минут; при разборе ориентируйтесь на iftop на машине. Если тормозит SSH — наш гайд по отказу SSH на Linux-сервере, чтобы исключить сетевой сбой.

Шаг 2: Gzip / Brotli — самый дешёвый способ срезать полосу

Текстовые ответы (JSON, HTML, JS, CSS, SVG, XML) обычно сжимаются на 60–80 %. Встроенный модуль gzip настраивается несколькими строками; нагрузка на CPU на большинстве VPS приемлема.

/etc/nginx/nginx.conf или conf.d/gzip.conf
gzip on;
                gzip_vary on;
                gzip_proxied any;
                gzip_comp_level 5;          # 6–9 дают убывающий эффект, 5 — разумный баланс
                gzip_min_length 256;        # Мелкие ответы не сжимать
                gzip_types
                    text/plain
                    text/css
                    text/javascript
                    application/javascript
                    application/json
                    application/xml
                    image/svg+xml
                    font/woff2;

На практике: gzip_proxied any сжимает и проксированный контент с бэкенда (по умолчанию — только прямые ответы). JPEG/PNG/WebP и видео gzip не нужен — бинарь уже сжат, CPU зря. Проверка: curl -H 'Accept-Encoding: gzip' -I https://your.site/api/foo — есть ли Content-Encoding: gzip.

С модулем Brotli (некоторые дистрибутивы или своя сборка) при том же качестве ещё −15–20 % — удобно для текстовых API. Без модуля достаточно стабильного gzip.

HTTPS и старые клиенты
У очень старых клиентов бывают баги с gzip по HTTPS; у современных браузеров — нет. Для древней встроенной техники можно отключить gzip по User-Agent — в 2026 году это редкость.

Шаг 3: кэш статики и sendfile

Сборщики (Vite, Webpack) добавляют content hash в имя, например app.a3f2b1.js. Такие файлы можно кэшировать надолго — при втором визите сеть не нужна. Часто hash есть в билде, но Nginx не отдаёт Cache-Control, и браузер каждый раз запрашивает всё заново.

Пример location для статики
location ~* \.(js|css|woff2?|ttf|ico|svg)$ {
                    root /var/www/app/dist;
                    expires 30d;
                    add_header Cache-Control "public, immutable";
                    access_log off;           # При cache hit отключить access_log — меньше IO
                }

                # Рекомендуется в блоке http
                sendfile on;
                tcp_nopush on;
                tcp_nodelay on;

sendfile передаёт страницы файла из ядра в сокет, меньше копий в userspace — полезно для крупной статики и параллельных загрузок. При связке с AIO или пулами потоков протестируйте; для большинства сайтов хватает sendfile + tcp_nopush + tcp_nodelay.

HTML и index.html без immutable — иначе после деплоя пользователи увидят старую оболочку. Обычно: хешированные ресурсы на 30 дней, HTML с no-cache (согласованное кэширование).

Шаг 4: кэш reverse proxy (proxy_cache)

Для read-heavy API (списки настроек, каталоги, статьи) короткий TTL в Nginx делит полосу origin на hit rate. Одна из недооценённых возможностей модуля proxy.

Базовая настройка proxy_cache
# блок http
                proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=apicache:50m
                                 max_size=2g inactive=60m use_temp_path=off;

                upstream backend {
                    server 127.0.0.1:3000;
                    keepalive 32;              # см. следующий раздел
                }

                server {
                    location /api/ {
                        proxy_pass http://backend;
                        proxy_http_version 1.1;
                        proxy_set_header Connection "";

                        proxy_cache apicache;
                        proxy_cache_valid 200 5m;
                        proxy_cache_key "$scheme$request_method$host$request_uri";
                        add_header X-Cache-Status $upstream_cache_status;

                        proxy_buffering on;
                        proxy_buffers 16 16k;
                        proxy_busy_buffers_size 64k;
                    }
                }

$upstream_cache_status показывает HIT, MISS или BYPASS — сразу видно, работает ли кэш. Запись (POST/PUT/DELETE) должна обходить кэш; персонализированные API с cookie — осторожно, чтобы не отдать данные A пользователю B.

При proxy_buffering on Nginx сначала собирает ответ бэкенда в буфер, потом отдаёт клиенту — медленный бэкенд и быстрый клиент не держат соединение зря. Для SSE или крупных upload — proxy_buffering off в нужной location.

Шаг 5: upstream keepalive — меньше TCP-handshake

По умолчанию Nginx может открывать новое TCP к бэкенду на каждый запрос клиента. При высокой QPS TIME_WAIT и трёхсторонние рукопожатия съедают CPU и локальные порты — ответы медленнее, эффективная полоса падает. upstream keepalive держит пул соединений к бэкенду; после ответа соединение возвращается в пул.

keepalive — обязательная тройка
upstream backend {
                    server 127.0.0.1:8080;
                    keepalive 64;               # размер пула по QPS
                }

                location / {
                    proxy_pass http://backend;
                    proxy_http_version 1.1;     # HTTP/1.1 нужен для keepalive
                    proxy_set_header Connection "";  # убрать Connection: close
                }

Без proxy_http_version 1.1 или Connection "" keepalive не работает — самый частый случай «настроили зря» у клиентов. keepalive 64 хватает при PV до миллиона в день; слишком большой пул нагружает дескрипторы бэкенда.

Шаг 6: workers, соединения и ограничение скорости

При забитой полосе проверьте, не упирается ли Nginx в лимит соединений. Ключевые параметры в начале nginx.conf:

Workers и соединения (VPS 2 vCPU)
worker_processes auto;
                worker_rlimit_nofile 65535;

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

                # Опционально: лимит по IP против монополии полосы
                limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
                limit_conn_zone $binary_remote_addr zone=conn:10m;

                server {
                    limit_req zone=perip burst=20 nodelay;
                    limit_conn conn 50;
                }

worker_connections × worker_processes должно превышать пик одновременных соединений. На 2 ядрах worker_processes auto обычно даёт 2 — 4096 × 2 = 8192 с запасом для средних сайтов. Согласуйте fs.file-max и worker_rlimit_nofile, иначе в логах too many open files.

limit_rate ограничивает одну загрузку; limit_req защищает API от злоупотреблений. Это не «добавляет» полосу, но не даёт единичным аномалиям положить всех — последний рубеж управления трафиком.

Шаг 7: когда пора подключать CDN?

Если после всех оптимизаций полоса периодически упирается в потолок — CDN или больший тариф. Простые критерии:

  • Статика > 60 % исходящего → CDN для JS/CSS/картинок, origin только HTML и API
  • Пользователи в разных регионах → edge ближе к аудитории, меньше нагрузка на origin
  • Всплески (акции, стримы) → CDN держит пики, не покупать полосу «под максимум» на origin

С CDN на стороне Nginx: origin принимает только IP CDN (whitelist файрвола) и пробрасывает реальный IP клиента (X-Forwarded-For / real_ip). Иначе атакующий обходит защиту и кэш, бью напрямую по IP origin.

Полный пример блока server (подставьте домен и деплойте)

Все фрагменты в минимальный рабочий пример — бэкенд Node / Python на 127.0.0.1:3000:

/etc/nginx/sites-available/app.conf
upstream app {
                    server 127.0.0.1:3000;
                    keepalive 32;
                }

                server {
                    listen 443 ssl http2;
                    server_name app.example.com;

                    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
                    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

                    gzip on;
                    gzip_types application/json application/javascript text/css text/plain;
                    gzip_min_length 256;

                    location /assets/ {
                        alias /var/www/app/dist/assets/;
                        expires 30d;
                        add_header Cache-Control "public, immutable";
                        access_log off;
                    }

                    location / {
                        proxy_pass http://app;
                        proxy_http_version 1.1;
                        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_set_header Connection "";
                    }
                }

Затем sudo nginx -t && sudo systemctl reload nginx и вкладка Network в браузере: кэш 30 дней для assets, gzip для API. В нашем release-чеклисте: curl -sI https://app.example.com/assets/main.js | grep -i cache.

Проверка и постоянный мониторинг

Тюнинг — не разовая задача. В Grafana или облачном мониторинге смотрите четыре кривые: исходящая полоса, active connections Nginx, время ответа upstream, hit rate кэша (если есть proxy_cache). Перед релизом или акцией — нагрузочный тест: ab -n 1000 -c 50 https://app.example.com/ или hey -n 5000 -c 100, сравните пик полосы и P95 до/после.

Полоса упала, а задержка высокая — узкое место, скорее всего, БД или диск; другая тема. Но вы уже знаете: деньги в индексы, а не в слепую покупку 500 Мбит/с.

В двух словах
Сначала iftop для «тяжёлых» URL, затем gzip для текста, expires для статики, keepalive к бэкенду; read-heavy API — proxy_cache; если мало — CDN. Большинство проблем с полосой на VPS — конфигурация, не железо.

Помимо тюнинга: правильный узел тоже экономит полосу

Nginx уменьшает объём отдаваемых байт, но физическое расстояние пользователь–origin конфигом не сократить. Аудитория в APAC на VPS в US East — высокий RTT, ретрансмиты, ниже реальный throughput. Многие выносят сборку и превью на облачный Mac или ближайший узел, статику — в object storage + CDN, на origin остаётся API — счёт за трафик выглядит лучше.

Облачный Mac mini M4 VPSSPark подходит для CI, внутренних превью и jump-хоста: нативный macOS, Terminal и OpenSSH из коробки; в простое ~4 Вт, удобно 7×24 как релей или build-машина — экономичнее ещё одного маленького x86. С UFW только на 443 и приложениями на 127.0.0.1 меньше поверхность атаки и лишний трафик.

Если проектируете архитектуру с экономией полосы и простой эксплуатацией, облачный Mac mini VPSSPark — практичный low-power jump- и build-узел посмотреть тарифы — чтобы результат тюнинга Nginx не свёл на нет неверный выбор региона.

Ограниченное предложение

Канал настроен—узел тоже должен успевать

Энергоэффективный Mac mini · нативный Unix-узел · тихая работа 24/7

На главную
Ограниченное предложение Смотреть тарифы