Оптимизация Nginx и кэширование на Ubuntu
Оптимизация Nginx начинается с неплохой точки: пакет Ubuntu уже запускает по процессу на ядро, включает sendfile и gzip для HTML. Остаётся несколько настроек, которые зависят от вашей нагрузки и сайта: лимиты соединений, сжатие CSS и JavaScript, кэш статики в браузере и кэш страниц от приложения. Значения ниже — отправная точка для Ubuntu 24.04 с Nginx 1.24 (в 22.04 версия 1.18, настройки те же). Меняйте по одной вещи и смотрите на результат.
Рабочие процессы и соединения
worker_processes auto запускает по рабочему процессу на ядро процессора, и в /etc/nginx/nginx.conf Ubuntu это уже стоит. worker_connections — сколько соединений один процесс держит одновременно, в Ubuntu там 768. В это число входят и посетители, и соединения с приложением, так что за прокси каждый посетитель занимает два. Грубый предел — процессы × worker_connections, а при проксировании вдвое меньше. Для нагруженного сайта разумно начать с 4096. Каждое соединение — открытый файл, поэтому поднимите и лимит открытых файлов для процессов через worker_rlimit_nofile, примерно вдвое больше worker_connections.
sudo nano /etc/nginx/nginx.confПоменяйте начало файла и блок events:
worker_processes auto;
worker_rlimit_nofile 8192;
events {
worker_connections 4096;
}sudo nginx -t
sudo systemctl reload nginx
nproc
ps -C nginx -o pid,argsnproc покажет число ядер, а ps — одну строку nginx: master process и столько же строк nginx: worker process. Каждому соединению нужен открытый файл, а служба стартует с лимитом 1024 на процесс, поэтому вместе с большим worker_connections нужен и worker_rlimit_nofile.
Keepalive для посетителей и для приложения
Keepalive позволяет пропустить по одному соединению много запросов. Для посетителей он уже включён: keepalive_timeout по умолчанию 75 секунд, keepalive_requests — 1000. Таймаут покороче, например 30 секунд, быстрее освобождает соединения на нагруженном сервере. А вот к приложению Nginx 1.24 открывает новое соединение на каждый запрос. Блок upstream с keepalive держит несколько из них открытыми, а в location для этого нужны HTTP/1.1 и пустой заголовок Connection.
upstream app_backend {
server 127.0.0.1:3000;
keepalive 16;
}
server {
# listen и server_name как раньше
keepalive_timeout 30s;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo nginx -t
sudo systemctl reload nginxkeepalive 16 — сколько простаивающих соединений с приложением держит каждый процесс, а не предел для всех соединений. Основы proxy_pass и этих заголовков — в статье как настроить Nginx reverse proxy. Location для WebSocket берёт Connection из map, его оставьте как есть.
Как включить gzip для CSS, JavaScript и JSON
В nginx.conf Ubuntu стоит gzip on, но список типов закомментирован, и Nginx сжимает только HTML. В разделе Gzip Settings файла /etc/nginx/nginx.conf уберите # перед строками gzip и приведите их к такому виду. Уровень 5 хорошо сжимает текст и почти не нагружает процессор, уровни выше дают мало. Картинки, видео и архивы уже сжаты, их в список не добавляйте.
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;sudo nginx -t
sudo systemctl reload nginx
curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' https://example.com/style.cssВ последней команде укажите адрес настоящего CSS- или JS-файла своего сайта. В заголовках должно быть Content-Encoding: gzip. Команда делает обычный GET-запрос: на запрос HEAD (curl с ключом -I) Nginx отвечает без сжатия, и такая проверка ничего не покажет.
Заголовки кэша для статики и open_file_cache
expires добавляет в ответ Expires и Cache-Control: max-age, и браузер не спрашивает файл снова до этого срока. Долгий срок вроде 30 дней безопасен только для файлов, у которых имя меняется с каждым выпуском, например app.3f9a1c.js от сборщика. Для файлов с постоянным именем начните с нескольких часов. Добавьте такой location в блок server сайта, который отдаёт файлы из root:
location ~* \.(css|js|woff2|svg|png|jpg|jpeg|webp|avif|ico)$ {
expires 30d;
access_log off;
}open_file_cache держит открытые файлы и результаты их поиска, чтобы Nginx не искал одни и те же файлы на диске на каждый запрос. Это помогает сайту с большим количеством статики. Значения — пример из документации Nginx. Положите их в отдельный файл в /etc/nginx/conf.d/, Ubuntu читает его внутри блока http:
sudo nano /etc/nginx/conf.d/open-file-cache.confopen_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;sudo nginx -t
sudo systemctl reload nginx
curl -sI https://example.com/style.cssВ ответе должны быть Cache-Control: max-age=2592000 (30 дней в секундах) и дата в Expires. Из-за open_file_cache_valid 30s после выкладки Nginx может до 30 секунд отдавать старую версию изменённого файла.
Кэширование страниц в Nginx: proxy_cache и fastcgi_cache
Кэширование целых ответов в Nginx снимает нагрузку с приложения: страница из кэша приходит с диска, без обращения к Node.js, Python или PHP. Это подходит страницам, одинаковым для всех: блогу, каталогу, API с открытыми данными. Ответы с Set-Cookie или с Cache-Control: private, no-cache, no-store Nginx сам не кэширует, но для страниц с личными данными не включайте кэш, пока не проверите, что отдаёт приложение. Сначала создайте папку кэша и опишите его зону в блоке http:
sudo mkdir -p /var/cache/nginx
sudo nano /etc/nginx/conf.d/cache.confproxy_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m use_temp_path=off;Потом включите кэш в location, который проксирует приложение:
location / {
# строки proxy_pass и proxy_set_header как раньше
proxy_cache app_cache;
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}sudo nginx -t
sudo systemctl reload nginx
curl -sI https://app.example.com/ | grep X-Cache-Status
curl -sI https://app.example.com/ | grep X-Cache-StatusПервый ответ покажет X-Cache-Status: MISS, второй — HIT. BYPASS или вечный MISS значит, что приложение шлёт заголовки, которые запрещают кэш. В 10 МБ keys_zone помещается около 80 тысяч ключей, а max_size ограничивает кэш на диске. proxy_cache_use_stale отдаёт последнюю удачную копию, пока приложение лежит или тормозит.
Для PHP через PHP-FPM тот же кэш называется fastcgi_. Ключ нужно задать самому, а посетителей с cookie сессии лучше пускать мимо кэша:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_cache php_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 10m;
fastcgi_cache_bypass $cookie_PHPSESSID;
fastcgi_no_cache $cookie_PHPSESSID;
add_header X-Cache-Status $upstream_cache_status;
}Ему нужна своя зона в /etc/nginx/conf.d/cache.conf: fastcgi_cache_path /var/cache/nginx/php levels=1:2 keys_zone=php_cache:10m max_size=1g inactive=60m use_temp_path=off;. Чтобы очистить кэш, удалите файлы в его папке, например:
sudo rm -rf /var/cache/nginx/app/*Удалятся только копии из кэша, Nginx снова возьмёт их у приложения.
Если вы работаете и на Mac

Сборка мусора в MacWashing находит кэш всех знакомых ей программ, в том числе спрятанный внутри их данных, а ещё журналы, мусор Xcode и остатки удалённых программ. Она показывает, сколько места занимает каждая находка, и очищает всё за один раз. Всё спорное остаётся без галочки, пока вы не решите.
Бесплатная пробная версия · macOS 14 и новее
Вопросы и ответы
Что важнее всего в оптимизации Nginx?
Для большинства сайтов: gzip для текстовых файлов, долгие заголовки кэша для статики с версией в имени и кэш страниц там, где они одинаковы для всех. Лимиты процессов и соединений важны, когда посетителей одновременно много.
worker_processes auto или конкретное число?
auto ставит по процессу на ядро, и это подходит почти любому серверу. Конкретное число нужно, только если на сервере есть что-то ещё, чему нужны свои ядра.
Уровень gzip 9 лучше, чем 5?
Файлы становятся меньше совсем ненамного, а процессор заметно сильнее работает на каждом ответе. Обычно берут уровни от 4 до 6.