Сайт переехал на 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 v4 | 2.2 GHz | SSD RAID 10 |
| Нидерланды | Intel Xeon Gold | 2.6 GHz | NVMe |
| Чехия | Intel Xeon Gold 6226 | 2.9 GHz | NVMe |
| Финляндия | Intel Xeon Gold 6154 | 3.0 GHz | NVMe |
| HI-CPU (FI, NL, DE, US) | AMD Ryzen 9, DDR5 | до 5.7 GHz | NVMe |
Практический вывод. Если сайт на 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 ГБ.
Порядок действий
- Замерить TTFB командой
curlна нескольких страницах. - Посмотреть
vmstatи загрузку по ядрам, пока сайт тормозит. - Включить HTTP/2, сжатие и кэш статики в Nginx.
- Обновить PHP до 8.x, поднять лимиты OPcache, проверить
pm.max_children, включить slowlog. - Подключить Redis и кэш страниц для гостей.
- Разобрать медленные запросы к базе.
- Облегчить страницу: картинки, отложенная загрузка, лишние скрипты.
- Только после этого — менять тариф или площадку.
Чтобы замедление не подкралось снова, настройте мониторинг времени ответа: он покажет рост TTFB раньше, чем жалобы пользователей. А если сайт не тормозит, а не открывается вовсе, порядок действий другой — в статье «Что делать, если сайт упал».
Коротко
- Сначала измерьте: сеть, время работы сервера (TTFB) и вес страницы лечатся по-разному.
- HTTP/2, сжатие и кэш статики — десять минут работы и выигрыш для всех посетителей.
- Для PHP-сайтов главное — OPcache с нормальными лимитами и кэш страниц для гостей.
- Медленные запросы ищите по журналам PHP-FPM и MySQL, а не на глаз.
- Железо меняйте последним и под конкретное узкое место: частота — HI-CPU, диск — NVMe, память — тариф выше.
