Руководства

Как ускорить сайт на VPS: кэш, PHP-FPM, HTTP/2 и сжатие

Как ускорить сайт на VPS: кэш, PHP-FPM, HTTP/2 и сжатие

Сайт переехал на VPS, а быстрее не стал, или стал медленнее, чем был на хостинге. Обычно дело не в том, что сервер слабый, а в том, что на свежем VPS почти ничего не настроено: нет кэша, не включено сжатие, PHP работает с настройками по умолчанию. Ниже — порядок действий: сначала измерить, где теряется время, потом закрыть самые дешёвые причины, и только в конце решать, нужен ли другой тариф.

Шаг 0. Измерьте, где теряется время

Самая частая ошибка — оптимизировать наугад: ставить CDN, когда тормозит база, или покупать ядра, когда не включён кэш. Одна команда с любой машины показывает, из чего складывается загрузка страницы:

curl -o /dev/null -s -w 'DNS:     %{time_namelookup}\nConnect: %{time_connect}\nTLS:     %{time_appconnect}\nTTFB:    %{time_starttransfer}\nTotal:   %{time_total}\n' https://example.com/

Значения накопительные, в секундах. Смотреть нужно на разницы:

  • Connect − DNS — сетевая задержка до сервера. Если она больше 0,1 с, сервер далеко от вас или от ваших пользователей. Это лечится выбором локации, а не настройками — подробно в статье «Как выбрать локацию сервера».
  • TTFB − TLS — сколько сервер думал над ответом. Это главная цифра. Больше 0,5 с для обычной страницы — проблема на сервере: PHP, база, отсутствие кэша.
  • Total − TTFB — передача самого HTML. Обычно мелочь; если нет — страница слишком тяжёлая или не сжимается.

Запустите команду 3–4 раза: первый запрос бывает медленнее из-за холодного кэша. Проверьте не только главную, но и тяжёлые страницы — каталог с фильтром, поиск, карточку товара.

Если TTFB в норме, а сайт в браузере всё равно открывается долго, проблема во фронтенде: картинки, скрипты, шрифты. Это видно во вкладке «Сеть» панели разработчика или в PageSpeed Insights. О нём — в конце статьи.

Шаг 1. Посмотрите на сервер под нагрузкой

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

vmstat 1 10
free -h
df -h

В выводе vmstat нужны четыре колонки:

Что видноЧто это значитКуда смотреть
us близко к 100Процессор занят вашим кодомКэш, OPcache, медленные запросы (шаги 3–5)
wa стабильно выше 10–20Процессы ждут дискБаза, логи, своп; при SSD — смотреть в сторону NVMe
st стабильно выше 5–10Виртуальной машине не хватает процессора хостаНаписать в поддержку провайдера
si/so не нулиСистема активно пишет в свопНе хватает памяти: уменьшить PHP-FPM или поднять тариф

Ещё одна полезная проверка — top, затем клавиша 1: появится загрузка по каждому ядру. Если одно ядро на 100%, а остальные почти свободны, приложение упирается в один поток. Больше ядер тут не поможет, поможет только более высокая частота. К этому вернёмся в конце.

Шаг 2. Nginx: HTTP/2, сжатие, кэш статики

Три настройки, которые занимают десять минут и ускоряют сайт для всех посетителей.

HTTP/2

Позволяет браузеру загружать десятки файлов по одному соединению, а не открывать новое на каждые несколько. Работает только по HTTPS. В блоке server:

# nginx 1.25.1 и новее
listen 443 ssl;
http2 on;

# более старые версии
listen 443 ssl http2;

Версию покажет nginx -v.

Сжатие

HTML, CSS и JavaScript сжимаются в 3–5 раз. На свежей установке gzip часто включён только для HTML. В блоке http в /etc/nginx/nginx.conf:

gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json
           application/xml image/svg+xml font/ttf;

Brotli сжимает ещё на 15–20% лучше, но требует отдельного модуля. В свежих Debian и Ubuntu он ставится пакетом libnginx-mod-http-brotli-filter; если пакета нет, хватит gzip. Картинки (JPEG, PNG, WebP) в список не добавляйте: они уже сжаты, и процессор потратится впустую.

Кэш статики в браузере

Без заголовков кэширования браузер перепроверяет каждый файл при каждом заходе:

location ~* \.(css|js|woff2|svg|png|jpg|jpeg|webp|avif|ico)$ {
    expires 30d;
    access_log off;
}

Чтобы пользователи не застряли на старой версии стилей после обновления, добавляйте к ссылке версию: style.css?v=15. Меняете файл — увеличиваете номер.

После правок: nginx -t && systemctl reload nginx. Проверка:

curl -sI --http2 https://example.com/ | head -1
curl -sI -H 'Accept-Encoding: gzip' https://example.com/ | grep -i content-encoding

Первая команда должна показать HTTP/2 200, вторая — content-encoding: gzip.

Шаг 3. PHP: версия, OPcache, PHP-FPM

Версия PHP

Если сайт до сих пор работает на PHP 7.4, переход на 8.x даёт заметный прирост без единой строчки кода, к тому же 7.4 давно не получает исправлений безопасности. Перед переходом проверьте совместимость плагинов и модулей на копии сайта.

OPcache

Без него PHP заново разбирает каждый файл на каждом запросе. Обычно он включён, но с маленькими лимитами, которых не хватает CMS с тысячами файлов. Настройки — в /etc/php/8.3/fpm/conf.d/10-opcache.ini (номер версии подставьте свой):

opcache.enable=1
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60

Важно: php -i в консоли показывает настройки CLI, а не FPM. Смотрите файл выше или выведите phpinfo() временным скриптом на сайте и сразу удалите его.

PHP-FPM

Сколько процессов PHP одновременно обслуживают запросы, задаёт pm.max_children в /etc/php/8.3/fpm/pool.d/www.conf. Мало — запросы стоят в очереди даже при свободном процессоре; много — кончается память, начинается своп. Как посчитать значение под вашу память — в статье «Пики нагрузки».

Для поиска медленных мест включите журнал медленных запросов PHP в том же файле:

request_slowlog_timeout = 3s
slowlog = /var/log/php8.3-fpm-slow.log

В журнал попадёт стек вызовов каждого запроса дольше трёх секунд: видно, какой плагин или функция тормозит. После перезапуска FPM (systemctl restart php8.3-fpm) подождите день и посмотрите, что накопилось.

Шаг 4. Кэш: объектный и страничный

Объектный кэш (Redis)

CMS при каждом запросе достаёт из базы одни и те же настройки, меню и списки. Redis держит их в памяти:

apt install redis-server php8.3-redis
systemctl enable --now redis-server

Дальше подключение на стороне CMS: в WordPress — плагин Redis Object Cache, в 1С-Битрикс — раздел cache в .settings.php. Redis съедает память, поэтому задайте ему потолок в /etc/redis/redis.conf: maxmemory 256mb и maxmemory-policy allkeys-lru.

Кэш страниц в Nginx

Самое сильное средство: страница для гостя отдаётся без запуска PHP и обращений к базе. В блоке http:

fastcgi_cache_path /var/cache/nginx/fcgi levels=1:2 keys_zone=SITE:50m
                   inactive=60m max_size=1g;

В блоке server — правила, когда кэш не использовать:

set $skip 0;
if ($request_method = POST) { set $skip 1; }
if ($query_string != "")    { set $skip 1; }
if ($http_cookie ~* "wordpress_logged_in|woocommerce_items_in_cart|comment_author") {
    set $skip 1;
}

И в location ~ \.php$ рядом с fastcgi_pass:

fastcgi_cache SITE;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 10m;
fastcgi_cache_bypass $skip;
fastcgi_no_cache $skip;
add_header X-Cache $upstream_cache_status;

Проверка: curl -sI https://example.com/ | grep X-Cache — при втором запросе должно быть HIT. Если всегда MISS или BYPASS, скорее всего сайт ставит сессионную куку каждому посетителю (например, PHPSESSID). Тогда Nginx честно не кэширует, и сначала надо убрать сессию у гостей.

Корзину, оформление заказа и личный кабинет держите вне кэша. Для каталога с часто меняющимися ценами хватит и короткого кэша на 30–60 секунд — подробнее о нём в статье «Пики нагрузки».

Шаг 5. База данных

Если в журнале PHP-FPM видны долгие вызовы к базе, а TTFB большой даже на страницах без кэша, следующий шаг — журнал медленных запросов MySQL и индексы. Вторая частая причина — кэш InnoDB по умолчанию 128 МБ, когда сама база занимает гигабайт. Оба вопроса разобраны в статье «База данных на VPS».

Шаг 6. Фронтенд: то, что сервер не исправит

Если TTFB в норме, а страница грузится секунды, дело в весе страницы:

  • Картинки обычно занимают больше всего. Перевод в WebP или AVIF и отдача в нужном размере, а не в исходном разрешении с камеры, часто уменьшают вес страницы в разы.
  • Отложенная загрузка — атрибут loading="lazy" у картинок ниже первого экрана.
  • Сторонние скрипты — счётчики, чаты, виджеты. Каждый добавляет запросы к чужим серверам, на скорость которых вы не влияете. Уберите лишние.
  • CDN для статики полезен, если аудитория разбросана по разным странам: файлы приезжают с ближайшего к пользователю узла, а сервер остаётся один.

Когда дело всё-таки в железе

Если кэш настроен, OPcache работает, база с индексами, а тяжёлые страницы всё равно отвечают медленно при небольшой нагрузке, значит, упёрлись в ресурсы. По шагу 1 уже понятно, в какие:

  • Одно ядро на 100%, остальные свободны. PHP выполняет запрос в один поток, и время ответа определяет частота процессора. Добавлять ядра бесполезно, нужна частота выше.
  • Высокий wa. Диск не успевает. Разница между SSD и NVMe заметна именно на базе данных и случайном чтении.
  • Своп. Нужно больше памяти: следующий тариф или меньше процессов PHP.
  • Высокий st. Проблема на стороне хоста — сначала в поддержку, а если не помогло, переезд на другую площадку.

Для однопоточных задач важнее всего частота. Вот как отличаются площадки у нашего провайдера:

ПлощадкаПроцессорЧастотаДиск
ГерманияIntel Xeon E5-2650 v42.2 GHzSSD RAID 10
НидерландыIntel Xeon Gold2.6 GHzNVMe
ЧехияIntel Xeon Gold 62262.9 GHzNVMe
ФинляндияIntel Xeon Gold 61543.0 GHzNVMe
HI-CPU (FI, NL, DE, US)AMD Ryzen 9, DDR5до 5.7 GHzNVMe

Практический вывод. Если сайт на SSD-площадке и упирается в диск — переезд на любую NVMe-локацию из таблицы. Если упирается в одно ядро — HI-CPU: тяжёлая страница на Ryzen с частотой до 5.7 GHz генерируется заметно быстрее, чем на Xeon 2.2–3.0 GHz. Если просто не хватает памяти — следующий тариф на той же площадке, переход выполняется без переноса данных. Как вообще прикинуть ресурсы под проект — в статье «Как выбрать тариф VPS».

HI-CPU Start: Ryzen до 5.7 GHz — $12/мес   Medium в Финляндии: 3 vCPU, 4 ГБ, NVMe — $20/мес

Цены указаны на момент публикации, оплата у провайдера. HI-CPU Start — 2 vCPU и 2 ГБ памяти, этого хватает сайту на WordPress или небольшому магазину с кэшем. Если памяти нужно больше, HI-CPU Medium — 3 vCPU и 4 ГБ.

Порядок действий

  1. Замерить TTFB командой curl на нескольких страницах.
  2. Посмотреть vmstat и загрузку по ядрам, пока сайт тормозит.
  3. Включить HTTP/2, сжатие и кэш статики в Nginx.
  4. Обновить PHP до 8.x, поднять лимиты OPcache, проверить pm.max_children, включить slowlog.
  5. Подключить Redis и кэш страниц для гостей.
  6. Разобрать медленные запросы к базе.
  7. Облегчить страницу: картинки, отложенная загрузка, лишние скрипты.
  8. Только после этого — менять тариф или площадку.

Чтобы замедление не подкралось снова, настройте мониторинг времени ответа: он покажет рост TTFB раньше, чем жалобы пользователей. А если сайт не тормозит, а не открывается вовсе, порядок действий другой — в статье «Что делать, если сайт упал».

Коротко

  • Сначала измерьте: сеть, время работы сервера (TTFB) и вес страницы лечатся по-разному.
  • HTTP/2, сжатие и кэш статики — десять минут работы и выигрыш для всех посетителей.
  • Для PHP-сайтов главное — OPcache с нормальными лимитами и кэш страниц для гостей.
  • Медленные запросы ищите по журналам PHP-FPM и MySQL, а не на глаз.
  • Железо меняйте последним и под конкретное узкое место: частота — HI-CPU, диск — NVMe, память — тариф выше.