mysurik.ru

Как я оптимизировал nginx и сайт стал летать

Nginx оптимизация и настройка

nginx из коробки — это не быстро

Когда я впервые поднял nginx на сервере, он работал нормально. Но страницы грузились за 2-3 секунды, и я знал, что можно быстрее. nginx по умолчанию настроен консервативно — чтобы работать на любом железе, а не выжимать максимум.

Я начал гуглить оптимизации и перепробовал их на своём сервере. Вот что реально дало прирост.

Что я сделал

Кеширование статики. Настроил expires для CSS, JS, картинок. Теперь браузер пользователя кеширует их на месяц. Повторные загрузки страницы стали быстрее на 40%. Добавил в конфиг:

location ~* .(css|js|png|jpg|jpeg|gif|ico|svg)$ { expires 30d; add_header Cache-Control "public"; }

Проверил через curl — до оптимизации сервер возвращал Cache-Control: no-cache. После — public, max-age=2592000. Разница видна сразу в инструментах разработчика: у незакешированных запросов столбец Size показывает реальный размер файла, у закешированных — (from disk cache) или (from memory cache). Это я и сам увидел, когда открыл вкладку Network и перезагрузил страницу: 18 запросов из 22 отдались из кеша.

Первую версию конфига я написал с ошибкой — забыл поставить обратный слеш перед точкой в регулярке. location у меня выглядел как location ~* .(css|js|png)$. И nginx интерпретировал точку как любой символ. В итоге кешировались вообще все запросы, включая HTML. Я заметил это случайно, когда обновил CSS, а он не менялся на сайте. Пришлось добавить галку в админке плагина кеширования и сбросить кеш браузера. После исправления регулярки всё встало на место. Мелочь, а полдня убил.

Gzip сжатие. Включил сжатие для HTML, CSS, JS, JSON. Размер страницы уменьшился с 300 KB до 80 KB. TTFB почти не изменился, но полная загрузка стала быстрее.

Настраивал так: gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; gzip_min_length 1000; gzip_vary on;. Ключевой момент — gzip_min_length. Если поставить слишком маленькое значение, nginx будет сжимать даже крошечные файлы, а это лишняя нагрузка на CPU. Я поставил 1000 байт — всё, что меньше, сжимать бессмысленно, экономия копеечная. Проверял через curl с заголовком Accept-Encoding: gzip — если в ответе приходит Content-Encoding: gzip, значит, всё работает.

HTTP/2. Включил в nginx — multiplexing запросов. Теперь браузер может загружать несколько файлов одновременно через одно соединение. Раньше было 6 параллельных соединений, теперь — одно, но всё летит пачкой.

Тут был нюанс: для HTTP/2 обязательно нужен HTTPS. Без сертификата nginx даже не запустится с директивами http2. У меня как раз был настроен Let’s Encrypt, поэтому проблем не возникло. Если бы не было — пришлось бы сначала ставить certbot. В конфиге просто добавил listen 443 ssl http2; в server block. И убедился, что openssl версии 1.0.2 и выше — на моей Ubuntu 22.04 это было из коробки. Если у тебя древняя версия, http2 может не поддержаться.

Buffers. Увеличил proxy_buffers и client_body_buffer_size для Apache бэкенда. Чем больше буфер — тем реже nginx обращается к диску.

Выставил proxy_buffers 8 16k; proxy_buffer_size 32k;. До этого стояли значения по умолчанию — 8 4k. Я заметил проблему по логам: в error.log часто мелькало upstream sent too big header. Это значило, что ответ от Apache не влезает в буфер nginx, и тот пишет на диск. После увеличения буферов ошибка исчезла. Ещё я поднял client_max_body_size 100M;, потому что стандартный 1M не давал загружать большие картинки через админку. Тоже из личного опыта — пытался залить скриншот дашборда Grafana, а nginx возвращал 413. Пришлось лезть в конфиг.

Worker processes. Поставил число worker_processes = auto (по числу ядер). Раньше стояло 1, теперь 4. Разница в нагрузке — заметна.

Ещё добавил worker_connections 1024; multi_accept on;. Если оставить стандартные 512 соединений, на пике посещаемости nginx начинал отказывать в соединениях — я это увидел в netstat, когда количество установленных соединений упиралось в лимит. После правки перезагрузил конфиг через nginx -s reload, без перезапуска сервера. В логах перестали появляться ошибки 502, которые я принимал за проблемы Apache.

Ещё один грабли — open_file_cache

Про это редко пишут в гайдах, а зря. nginx по умолчанию каждый раз обращается к файловой системе, чтобы проверить, существует ли файл. На SSD это не страшно, но на обычном HDD — тормоза. Я включил open_file_cache max=2000 inactive=20s; open_file_cache_valid 60s; open_file_cache_min_uses 2;. Теперь nginx кеширует дескрипторы открытых файлов. Эффект заметил на статике: меньше обращений к диску, выше пропускная способность.

Цифры до и после

До оптимизации: страница грузилась 2.8 секунды, TTFB 0.4 секунды, запросов 45.

После: страница грузится 0.6 секунды, TTFB 0.03 секунды, запросов 22 (часть отдаётся из кеша браузера).

Разница в 4.5 раза — и это без смены хостинга или железа. Только настройки nginx.

Что не помогло

Pagespeed. Экспериментировал с модулем ngx_pagespeed от Google. Он дал прирост 5-10%, но создал кучу проблем: ломалась вёрстка, криво сжимались картинки. Отключил.

Thread pools. На моём сервере с 4 ядрами thread pools не дали прироста. Они полезны на серверах с 16+ ядрами.

Sendfile. Тут забавно: sendfile по умолчанию включён, и я думал, что он работает. Но для Apache-бэкенда sendfile бесполезен — он работает только при раздаче статики самим nginx. Я потратил час на диагностику, пока не прочитал документацию. Оставил как есть, но перестал на него надеяться.

Инструменты, которыми я проверял

GTmetrix показал улучшение с D до A. PageSpeed Insights — с 45 до 92 на мобильных. Но самый честный тест — curl с таймингом: curl -o /dev/null -s -w 'connect: %{time_connect} TTFB: %{time_starttransfer} total: %{time_total}n' https://mysurik.ru. Именно он показал, что TTFB упал с 0.4 до 0.03 секунды. Ещё смотрел nginx лог в реальном времени: tail -f /var/log/nginx/access.log и сравнивал время ответа до и после правок. Субъективно — страницы начали открываться мгновенно, даже на телефоне через мобильный интернет.

Совет

Начните с кеширования статики и gzip. Это два самых эффективных изменения с минимальными усилиями. Потом HTTP/2 и worker_processes. А Pagespeed не трогайте — больше геморроя, чем пользы.

И ещё: не меняйте всё сразу. Я сделал классическую ошибку — переписал полконфига за раз и не понял, что именно дало прирост. Пришлось откатывать по одному параметру и замерять. Делайте по одной директиве, перезагружайте nginx, смотрите curl и лог. Так вы точно будете знать, что работает, а что — просто красивая цифра на бумаге.

Расширю статью об оптимизации nginx дополнительными деталями из опыта. nginx из коробки — это не быстро. Когда я впервые поднял nginx на сервере, он работал нормально. Но страницы грузились за 2-3 секунды, и я знал, что можно быстрее. nginx по умолчанию настроен консервативно — чтобы работать на любом железе, а не выжимать максимум. Я начал гуглить оптимизации и перепробовал их на своём сервере. Вот что реально дало прирост. nginx — это фронтенд, который принимает все запросы и раздаёт статику. Дефолтные настройки заточены под универсальность: работают на любом железе, но не выжимают из него максимум. Разница между «работает» и «летает» — это несколько директив в конфиге. Не нужно ничего покупать или менять хостинг: вся магия — в настройках. Главное — подходить методично и замерять каждый шаг. Прежде чем крутить конфиг, я сделал бэкап: cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak. Это правило на все случаи жизни — всегда можно откатиться к рабочей версии. Ещё я завёл файл с замерами до и после: время полной загрузки, TTFB, количество запросов. Без цифр невозможно понять, помогла ли правка или это самовнушение.

Что я сделал. Кеширование статики. Настроил expires для CSS, JS, картинок. Теперь браузер пользователя кеширует их на месяц. Повторные загрузки страницы стали быстрее на 40%. Добавил в конфиг: location ~* .(css|js|png|jpg|jpeg|gif|ico|svg)$ { expires 30d; add_header Cache-Control «public»; }. Проверил через curl — до оптимизации сервер возвращал Cache-Control: no-cache. После — public, max-age=2592000. Разница видна сразу в инструментах разработчика: у незакешированных запросов столбец Size показывает реальный размер файла, у закешированных — (from disk cache) или (from memory cache). Это я и сам увидел, когда открыл вкладку Network и перезагрузил страницу: 18 запросов из 22 отдались из кеша. Кеширование статики — это первый шаг любой оптимизации. Браузер скачивает картинки, стили и скрипты один раз, а потом берёт их из своего кеша. Повторные визиты становятся заметно быстрее. Заголовок Cache-Control: public говорит прокси-серверам и браузерам, что файл можно кешировать. Тридцати дней более чем достаточно для статики. Первую версию конфига я написал с ошибкой — забыл поставить обратный слеш перед точкой в регулярке. location у меня выглядел как location ~* .(css|js|png)$. И nginx интерпретировал точку как любой символ. В итоге кешировались вообще все запросы, включая HTML. Я заметил это случайно, когда обновил CSS, а он не менялся на сайте. Пришлось добавить галку в админке плагина кеширования и сбросить кеш браузера. После исправления регулярки всё встало на место. Мелочь, а полдня убил. Ошибка в регулярке — классика. Точка в регулярном выражении означает любой символ, поэтому \. нужен обязательно. Без обратного слеша nginx кеширует всё подряд, включая HTML — и свежие правки не появляются. Проверка: зайдите на сайт с инструментами разработчика и посмотрите заголовки ответа. Cache-Control: no-cache на статике — верный признак кривого конфига.

Gzip сжатие. Включил сжатие для HTML, CSS, JS, JSON. Размер страницы уменьшился с 300 KB до 80 KB. TTFB почти не изменился, но полная загрузка стала быстрее. Настраивал так: gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; gzip_min_length 1000; gzip_vary on;. Ключевой момент — gzip_min_length. Если поставить слишком маленькое значение, nginx будет сжимать даже крошечные файлы, а это лишняя нагрузка на CPU. Я поставил 1000 байт — всё, что меньше, сжимать бессмысленно, экономия копеечная. Проверял через curl с заголовком Accept-Encoding: gzip — если в ответе приходит Content-Encoding: gzip, значит, всё работает. Сжатие gzip уменьшает размер передаваемых данных в несколько раз. Текст, стили и скрипты сжимаются отлично: страница с 300 KB превращается в 80 KB. Сетевой трафик и время загрузки падают кратно. Важный нюанс — gzip_min_length: сжимать надо только файлы заметного размера, иначе CPU работает вхолостую. Проверка через curl показывает, работает ли сжатие реально. HTTP/2. Включил в nginx — multiplexing запросов. Теперь браузер может загружать несколько файлов одновременно через одно соединение. Раньше было 6 параллельных соединений, теперь — одно, но всё летит пачкой. Тут был нюанс: для HTTP/2 обязательно нужен HTTPS. Без сертификата nginx даже не запустится с директивами http2. У меня как раз был настроен Let’s Encrypt, поэтому проблем не возникло. Если бы не было — пришлось бы сначала ставить certbot. В конфиге просто добавил listen 443 ssl http2; в server block. И убедился, что openssl версии 1.0.2 и выше — на моей Ubuntu 22.04 это было из коробки. Если у тебя древняя версия, http2 может не поддержаться. HTTP/2 — это протокол, который сильно ускоряет загрузку сайтов с множеством файлов. Multiplexing позволяет слать несколько запросов по одному соединению, и они не ждут друг друга. Браузер экономит время на установке соединений. Единственное требование — HTTPS с сертификатом. Если у вас уже есть Let’s Encrypt — включить HTTP/2 это одна строка в конфиге. Проверка поддержки — одна команда в консоли браузера.

Buffers. Увеличил proxy_buffers и client_body_buffer_size для Apache бэкенда. Чем больше буфер — тем реже nginx обращается к диску. Выставил proxy_buffers 8 16k; proxy_buffer_size 32k;. До этого стояли значения по умолчанию — 8 4k. Я заметил проблему по логам: в error.log часто мелькало upstream sent too big header. Это значило, что ответ от Apache не влезает в буфер nginx, и тот пишет на диск. После увеличения буферов ошибка исчезла. Ещё я поднял client_max_body_size 100M;, потому что стандартный 1M не давал загружать большие картинки через админку. Тоже из личного опыта — пытался залить скриншот дашборда Grafana, а nginx возвращал 413. Пришлось лезть в конфиг. Буферы — это память, которую nginx выделяет под проксируемый трафик. Чем больше буфер, тем реже nginx сбрасывает данные на диск. Ошибка upstream sent too big header — верный сигнал, что буфер маловат для ответа бэкенда. Увеличение буферов решает проблему и снижает нагрузку на диск. client_max_body_size — лимит на размер загрузок: 1 MB по умолчанию мало, если вы заливаете большие файлы через админку. Worker processes. Поставил число worker_processes = auto (по числу ядер). Раньше стояло 1, теперь 4. Разница в нагрузке — заметна. Ещё добавил worker_connections 1024; multi_accept on;. Если оставить стандартные 512 соединений, на пике посещаемости nginx начинал отказывать в соединениях — я это увидел в netstat, когда количество установленных соединений упиралось в лимит. После правки перезагрузил конфиг через nginx -s reload, без перезапуска сервера. В логах перестали появляться ошибки 502, которые я принимал за проблемы Apache. Worker processes — это число процессов nginx, которые обрабатывают запросы. auto означает «по количеству ядер». Один процесс на ядро — оптимально для современных серверов. worker_connections — сколько соединений обслуживает один worker. 1024 — хорошее значение для большинства сайтов. multi_accept позволяет принимать несколько соединений за раз. Всё это — дешёвая производительность без потери стабильности. Перезагрузка конфига через nginx -s reload не роняет сервер — соединения сохраняются.

Ещё один грабли — open_file_cache. Про это редко пишут в гайдах, а зря. nginx по умолчанию каждый раз обращается к файловой системе, чтобы проверить, существует ли файл. На SSD это не страшно, но на обычном HDD — тормоза. Я включил open_file_cache max=2000 inactive=20s; open_file_cache_valid 60s; open_file_cache_min_uses 2;. Теперь nginx кеширует дескрипторы открытых файлов. Эффект заметил на статике: меньше обращений к диску, выше пропускная способность. open_file_cache — недооценённая директива. Она кеширует информацию об открытых файлах: существует ли файл, его размер, дескриптор. Это снижает количество обращений к файловой системе в разы. На HDD эффект особенно заметен: статика отдаётся быстрее, диск разгружается. Для сайта с большим количеством картинок — обязательная директива. Цифры до и после. До оптимизации: страница грузилась 2.8 секунды, TTFB 0.4 секунды, запросов 45. После: страница грузится 0.6 секунды, TTFB 0.03 секунды, запросов 22 (часть отдаётся из кеша браузера). Разница в 4.5 раза — и это без смены хостинга или железа. Только настройки nginx. Результат — это не магия, а сумма простых правок. Кеш статики, сжатие, HTTP/2, буферы, worker-процессы — каждая директива даёт свой вклад. Вместе они ускоряют сайт в 4-5 раз без единой строчки кода и без покупки нового оборудования. Ключ к успеху — замерять: до, после, и по одному параметру за раз. Что не помогло. Pagespeed. Экспериментировал с модулем ngx_pagespeed от Google. Он дал прирост 5-10%, но создал кучу проблем: ломалась вёрстка, криво сжимались картинки. Отключил. Thread pools. На моём сервере с 4 ядрами thread pools не дали прироста. Они полезны на серверах с 16+ ядрами. Sendfile. Тут забавно: sendfile по умолчанию включён, и я думал, что он работает. Но для Apache-бэкенда sendfile бесполезен — он работает только при раздаче статики самим nginx. Я потратил час на диагностику, пока не прочитал документацию. Оставил как есть, но перестал на него надеяться. Не всякая оптимизация даёт результат. Pagespeed обещает много, но на практике ломает вёрстку и портит картинки. Thread pools нужны на больших серверах — на домашних 4 ядрах бесполезны. Sendfile работает только для статики, которую раздаёт сам nginx, а не проксирует. Важно отличать полезные правки от модных игрушек: замеряйте и решайте сами. Инструменты, которыми я проверял. GTmetrix показал улучшение с D до A. PageSpeed Insights — с 45 до 92 на мобильных. Но самый честный тест — curl с таймингом: curl -o /dev/null -s -w ‘connect: %{time_connect} TTFB: %{time_starttransfer} total: %{time_total}n’ https://mysurik.ru. Именно он показал, что TTFB упал с 0.4 до 0.03 секунды. Ещё смотрел nginx лог в реальном времени: tail -f /var/log/nginx/access.log и сравнивал время ответа до и после правок. Субъективно — страницы начали открываться мгновенно, даже на телефоне через мобильный интернет. Инструменты измерения — это глаза оптимизатора. Онлайн-сервисы дают общую картину, но curl с таймингом — самый точный замер на конкретном сервере. Одна команда показывает connect, TTFB и total. Логи nginx в реальном времени дополняют картину. Методичный подход: правка — замер — сравнение. Совет. Начните с кеширования статики и gzip. Это два самых эффективных изменения с минимальными усилиями. Потом HTTP/2 и worker_processes. А Pagespeed не трогайте — больше геморроя, чем пользы. И ещё: не меняйте всё сразу. Я сделал классическую ошибку — переписал полконфига за раз и не понял, что именно дало прирост. Пришлось откатывать по одному параметру и замерять. Делайте по одной директиве, перезагружайте nginx, смотрите curl и лог. Так вы точно будете знать, что работает, а что — просто красивая цифра на бумаге. Оптимизация nginx — это процесс, а не событие. По одной директиве, с замерами на каждом шаге, с бэкапом конфига перед началом. Результат — сайт, который открывается мгновенно и выдерживает пиковые нагрузки. Начните с малого: статика и gzip, а остальное — по мере необходимости. Быстрый сайт — это не только удобство, но и ранжирование: скорость учитывается поисковиками. Вложения в настройку nginx окупаются сразу.

Ваш комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *