Оптимизация 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:

/etc/nginx/nginx.conf
worker_processes auto; worker_rlimit_nofile 8192; events { worker_connections 4096; }
sudo nginx -t sudo systemctl reload nginx nproc ps -C nginx -o pid,args

nproc покажет число ядер, а 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.

/etc/nginx/sites-available/app.example.com
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 nginx

keepalive 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 хорошо сжимает текст и почти не нагружает процессор, уровни выше дают мало. Картинки, видео и архивы уже сжаты, их в список не добавляйте.

/etc/nginx/nginx.conf
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:

/etc/nginx/sites-available/example.com
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.conf
/etc/nginx/conf.d/open-file-cache.conf
open_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.conf
/etc/nginx/conf.d/cache.conf
proxy_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m use_temp_path=off;

Потом включите кэш в location, который проксирует приложение:

/etc/nginx/sites-available/app.example.com
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 сессии лучше пускать мимо кэша:

/etc/nginx/sites-available/example.com
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, пользовательский кэш, установщики и журналы, к очистке 22,83 ГБ

Сборка мусора в MacWashing находит кэш всех знакомых ей программ, в том числе спрятанный внутри их данных, а ещё журналы, мусор Xcode и остатки удалённых программ. Она показывает, сколько места занимает каждая находка, и очищает всё за один раз. Всё спорное остаётся без галочки, пока вы не решите.

Бесплатная пробная версия · macOS 14 и новее

Вопросы и ответы

Что важнее всего в оптимизации Nginx?

Для большинства сайтов: gzip для текстовых файлов, долгие заголовки кэша для статики с версией в имени и кэш страниц там, где они одинаковы для всех. Лимиты процессов и соединений важны, когда посетителей одновременно много.

worker_processes auto или конкретное число?

auto ставит по процессу на ядро, и это подходит почти любому серверу. Конкретное число нужно, только если на сервере есть что-то ещё, чему нужны свои ядра.

Уровень gzip 9 лучше, чем 5?

Файлы становятся меньше совсем ненамного, а процессор заметно сильнее работает на каждом ответе. Обычно берут уровни от 4 до 6.