Дата публикации документа: 22-02-2026

Дата обновления документа: 22-02-2026

Производительность PHP. Фундамент TTFB 26 мс

Оптимизация стека LAMP/LEMP: MySQL, Apache, PHP-FPM, OPcache, кэширование

Пошаговая оптимизация серверного стека для максимальной производительности PHP-приложений под высокой нагрузкой.

Документ ориентирован для решения вопросов:

Содержание:

CMS / Фреймворк Что можно применить
OpenCart Всё: MySQL, Apache, PHP, кэширование, прогрев
WordPress InnoDB, OPcache, .htaccess, прогрев, Brotli/Gzip
Magento MySQL, PHP-FPM, OPcache, кэширование, Nginx (аналог)
Laravel / Symfony OPcache, PHP-FPM, кэширование маршрутов, прогрев

Все конфигурации разработаны и многократно протестированы на PHP 7.1 в production-среде.

Совместимость с версиями PHP:

Решения не рекомендуются для PHP ниже 7.1 из-за отсутствия поддержки ключевых оптимизаций и известных уязвимостей безопасности.

Рекомендуемые аппаратные ресурсы: NVMe SSD, 6+ CPU, от 12 ГБ RAM

Архитектура производительности: цепочка обработки запроса
Браузер
Service Worker
Кэш найден?
Да → Мгновенный ответ
Нет → Запрос на сервер
Apache 2.4
APCu HTML-кэш?
Да → Ответ из APCu
Нет → PHP-FPM
PHP-FPM
OPcache активен?
Да → Выполнение из памяти
OpenCart / Laravel / WP
Запрос к MySQL
MySQL InnoDB
InnoDB Buffer Pool?
Да → Данные из RAM
Формирование HTML
apcu_store(HTML)
Ответ клиенту
Service Worker
Кэширует ответ
Клиент
Service Worker
Проверка кэша
Apache
PHP-FPM
OPcache
CMS
MySQL
Buffer Pool
Действие

1. Оптимизация MySQL (/etc/my.cnf)

Перевод таблиц с MyISAM на InnoDB

По умолчанию многие CMS используют MyISAM. Рекомендуется перевести все таблицы на InnoDB:

ALTER TABLE oc_product ENGINE=InnoDB;
ALTER TABLE oc_product_description ENGINE=InnoDB;
ALTER TABLE oc_category ENGINE=InnoDB;
ALTER TABLE oc_order ENGINE=InnoDB;
-- Повторить для всех таблиц

Почему это важно:

Проверить тип таблиц:

SELECT table_name, engine FROM information_schema.tables WHERE table_schema = 'your_database';

Периодическая оптимизация таблиц

Раз в неделю — оптимизировать таблицы для дефрагментации и освобождения места:

# Через mysqlcheck
mysqlcheck -u root -p --optimize --all-databases

# Или для одной БД
mysqlcheck -u root -p your_database_name --optimize --all-tables

Можно добавить в cron:

0 3 * * 0 /usr/bin/mysqlcheck -u root -p'password' your_db --optimize --silent

Для InnoDB OPTIMIZE TABLE перестраивает таблицу и индексы — полезно после массовых удалений/обновлений.

Пример: результат выполнения OPTIMIZE TABLE в phpMyAdmin

После запуска OPTIMIZE TABLE для InnoDB-таблиц, phpMyAdmin показывает:

SQL-запрос успешно выполнен.
OPTIMIZE TABLE `oc_product`, `oc_category`, `oc_order`, `oc_customer`, `oc_cart`;

+---------------------+----------+----------+-----------------------------------------------------------+
| Table               | Op       | Msg_type | Msg_text                                                  |
+---------------------+----------+----------+-----------------------------------------------------------+
| mydb.oc_product     | optimize | note     | Table does not support optimize, doing recreate + analyze |
| mydb.oc_product     | optimize | status   | OK                                                        |
| mydb.oc_category    | optimize | note     | Table does not support optimize, doing recreate + analyze |
| mydb.oc_category    | optimize | status   | OK                                                        |
| mydb.oc_order       | optimize | note     | Table does not support optimize, doing recreate + analyze |
| mydb.oc_order       | optimize | status   | OK                                                        |
| mydb.oc_customer    | optimize | note     | Table does not support optimize, doing recreate + analyze |
| mydb.oc_customer    | optimize | status   | OK                                                        |
| mydb.oc_cart        | optimize | note     | Table does not support optimize, doing recreate + analyze |
| mydb.oc_cart        | optimize | status   | OK                                                        |
+---------------------+----------+----------+-----------------------------------------------------------+

Что это означает:

Важно: Сделать резервную копию. OPTIMIZE TABLE для InnoDB требует временного места на диске (примерно = размер таблицы) и блокирует таблицу на время операции. Рекомендуется выполнять в период низкой нагрузки.

[mysqld]
# Привязка к локальному интерфейсу
bind-address = 127.0.0.1
# Максимальный размер пакета (для больших запросов и импорта)
max_allowed_packet = 64M
# Размер стека потока
thread_stack = 256K

# Логирование медленных запросов
slow_query_log = /var/log/mysql/mysql-slow.log
long_query_time = 1
# Таймауты соединений
interactive_timeout = 60
wait_timeout = 60
# Защита от флуда подключений
max_connect_errors = 10000
max_connections = 300
thread_cache_size = 100
thread_concurrency = 6

# Путь к данным
datadir = /var/lib/mysql
socket = /var/run/mysqld/mysql.sock
symbolic-links = 0
# Движок по умолчанию — InnoDB
default-storage-engine = InnoDB
# Отдельный файл на таблицу — удобно для обслуживания
innodb_file_per_table = 1
# Кэш открытых таблиц
table_open_cache = 2000
table_definition_cache = 600
open_files_limit = 10000

# === InnoDB: основные настройки производительности ===
# Буферный пул — 70–80% от RAM (8G при 12G)
innodb_buffer_pool_size = 8G
# Разделение пула на экземпляры (по числу CPU)
innodb_buffer_pool_instances = 6
# Запись лога раз в секунду (не после каждой транзакции)
innodb_flush_log_at_trx_commit = 2
# Прямая запись на диск, минуя кэш ОС
innodb_flush_method = O_DIRECT
# Адаптивный хеш-индекс — ускоряет частые чтения
innodb_adaptive_hash_index = ON
# Размер файла журнала транзакций (больше = меньше I/O)
innodb_log_file_size = 2G
# Буфер лога в памяти
innodb_log_buffer_size = 256M
# Сохранение "горячего" кэша при остановке
innodb_buffer_pool_dump_at_shutdown = ON
# Загрузка кэша при старте
innodb_buffer_pool_load_at_startup = ON
# Пропускная способность I/O (NVMe)
innodb_io_capacity = 2000
# Параллельные потоки чтения/записи
innodb_read_io_threads = 8
innodb_write_io_threads = 8
# Время ожидания блокировки
innodb_lock_wait_timeout = 120
# Макс. процент "грязных" страниц
innodb_max_dirty_pages_pct = 70
# Адаптивная фоновая запись
innodb_adaptive_flushing = ON
# Защита от случайного сканирования
innodb_old_blocks_time = 1000
# Отключение сбора мета-статистики
innodb_stats_on_metadata = OFF
# Уровень изоляции — меньше блокировок
transaction-isolation = READ-COMMITTED

# Кодировка по умолчанию
collation-server = utf8_general_ci
init-connect = 'SET NAMES utf8'
character-set-server = utf8

# Query Cache — устарел, отключаем полностью
query_cache_type = 0
query_cache_size = 0

# Временные таблицы в памяти (важно для JOIN, ORDER BY)
tmp_table_size = 512M
max_heap_table_size = 512M
# Директория временных файлов (лучше на tmpfs)
tmpdir = /tmp

2. Настройка Apache 2.4

Основная конфигурация: /etc/httpd/conf/httpd.conf

ServerRoot "/etc/httpd"
Listen 80

# Загрузка модулей
Include conf.modules.d/*.conf

User apache
Group apache

# Безопасность
ServerSignature Off
ServerTokens Prod

# Корневой доступ — запрещён
<Directory />
    AllowOverride None
    Require all denied
</Directory>

# MPM Worker — потоковый режим (рекомендуется)
<IfModule mpm_worker_module>
    StartServers         6
    MinSpareThreads     50
    MaxSpareThreads     100
    ThreadsPerChild     25
    MaxRequestWorkers   300
    ServerLimit         12
    MaxRequestsPerChild 0
</IfModule>

# Fallback: Prefork (если Worker недоступен)
<IfModule mpm_prefork_module>
    StartServers       10
    MinSpareServers     5
    MaxSpareServers    20
    MaxRequestWorkers 300
    MaxConnectionsPerChild 5000
</IfModule>

DocumentRoot "/var/www/html"

# Основной каталог сайта — без .htaccess
<Directory "/var/www">
    AllowOverride None
    Require all granted
</Directory>

# Только для нужных директорий — разрешить .htaccess
<Directory "/var/www/html">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

# Индексные файлы
<IfModule dir_module>
    DirectoryIndex index.php index.html index.htm
</IfModule>

# Запрет доступа ко всем .ht* файлам
<Files ".ht*">
    Require all denied
</Files>

# Логи
ErrorLog "logs/error_log"
LogLevel warn

# Формат логов
LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog "logs/access_log" combined

# Отдача файлов ядром ОС — ускоряет статику
EnableSendfile on

# Подключение дополнительных конфигураций
IncludeOptional conf.d/*.conf
IncludeOptional vhost.d/*.conf

# Таймаут Keep-Alive — быстрое освобождение соединений
KeepAliveTimeout 1

Виртуальный хост: /etc/httpd/vhost.d/site.conf

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /path/to/site

    # Обработка PHP через PHP-FPM
    <IfModule proxy_fcgi_module>
        <FilesMatch \.php$>
            SetHandler "proxy:unix:/var/run/php-fpm/php-site.sock|fcgi://localhost/path/to/site"
        </FilesMatch>
    </IfModule>

    <Directory "/path/to/site">
        AllowOverride All
        Require all granted
    </Directory>

    # Доступ к .well-known для Let's Encrypt
    <Directory '/path/to/site/.well-known'>
        Options Indexes FollowSymLinks Includes ExecCGI
        ForceType 'text/plain'
        AddDefaultCharset Off
        Require all granted
    </Directory>

    CustomLog /path/to/logs/access.log combined
    ErrorLog /path/to/logs/error.log
    DirectoryIndex index.php index.html index.htm
</VirtualHost>

3. Локальный .htaccess — ядро безопасности и производительности

Размещается в корне сайта. Управляет URL, безопасностью, заголовками и кэшированием.

# Исправление дублирующихся & в query-строке
RewriteCond %{QUERY_STRING} ^(.*)&(.*)$
RewriteRule ^(.*)$ /$1?%1&%2 [L,R=301]

# Запрет листинга директорий
Options -Indexes +ExecCGI

# Блокировка прямого доступа к чувствительным файлам
<FilesMatch "(?i)((config\.php|\.env|\.sql|\.bak|\.twig\.tpl|\.ini|\.log\.txt))">
    Require all denied
</FilesMatch>

# === URL-перезапись (SEO) ===
<IfModule mod_rewrite.c>
    Options +FollowSymlinks
    RewriteEngine On
    RewriteBase /

    # Пропуск статики — не обрабатывать через PHP
    RewriteRule ^(.+\.(js|css|png|jpg|jpeg|gif|webp|ico|svg))$ - [L]

    # Карта сайта
    RewriteRule ^sitemap.xml$ index.php?route=feed/fast_sitemap [L]
    RewriteRule ^sitemap([^\.]+).xml$ index.php?route=feed/fast_sitemap&path=$1 [L]
    RewriteRule ^googlebase.xml$ index.php?route=extension/feed/google_base [L]

    # Защита от доступа к системным файлам
    RewriteRule ^system/download/(.*) index.php?route=error/not_found [L]

    # Основное правило — все остальные запросы в index.php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^([^?]*) index.php?_route_=$1 [L,QSA]
</IfModule>

# === HTTP-заголовки: безопасность и кэширование ===
<IfModule mod_headers.c>
    # Безопасные заголовки
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "SAMEORIGIN"
    Header set X-XSS-Protection "1; mode=block"
    Header set Referrer-Policy "no-referrer-when-downgrade"
    Header set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    # manifest.json — правильный тип + без кэширования
    <FilesMatch "^manifest\.json$">
        Header set Content-Type "application/manifest+json"
        Header unset Cache-Control
    </FilesMatch>

    # CORS для статики (если нужен)
    <FilesMatch "\.(webp|png|jpg|jpeg|ico|js|css)$">
        Header set Access-Control-Allow-Origin "https://example.com"
        Header set Access-Control-Allow-Methods "GET, OPTIONS"
        Header set Access-Control-Allow-Headers "Content-Type"
    </FilesMatch>

    # Неизменяемые JS-файлы (маршруты, трекеры и т.п.)
    <FilesMatch "\.(js\.route|js\.track|js\.pref|js\.sfc|js\.asc)$">
        Header set Content-Type "application/javascript"
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>

    # service-worker.js — не кэшировать!
    <FilesMatch "^service-worker\.js$">
        Header set Cache-Control "no-cache, no-store, must-revalidate"
        Header set Pragma "no-cache"
        Header set Expires "0"
    </FilesMatch>

    # Кэширование статики
    <FilesMatch "\.(js|css)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
    <FilesMatch "\.(webp|png|jpg|jpeg|gif|svg)$">
        Header set Cache-Control "public, max-age=2592000"
    </FilesMatch>
    <FilesMatch "\.(woff2?|ttf|otf)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
    <FilesMatch "\.json$">
        Header set Cache-Control "public, max-age=86400"
    </FilesMatch>

    # Кэширование статических HTML-страниц
    <FilesMatch "^(sitemap|privacy|about)\.html$">
        Header set Cache-Control "public, max-age=60, stale-while-revalidate=60"
    </FilesMatch>

    # Поддержка сжатия: Vary по Accept-Encoding
    Header append Vary Accept-Encoding
</IfModule>

# === Кэширование через Expires ===
<IfModule mod_expires.c>
    ExpiresActive On

    # Статика — 1 год
    ExpiresByType text/css A31536000
    ExpiresByType text/javascript A31536000
    ExpiresByType application/javascript A31536000
    ExpiresByType image/svg+xml A31536000

    # Изображения — 1 месяц
    ExpiresByType image/webp A2592000
    ExpiresByType image/png A2592000
    ExpiresByType image/jpeg A2592000
    ExpiresByType image/jpg A2592000
    ExpiresByType image/gif A2592000

    # Шрифты — 1 год
    ExpiresByType font/woff A31536000
    ExpiresByType font/woff2 A31536000

    # JSON — 1 день
    ExpiresByType application/json A86400
</IfModule>

# === MIME-типы ===
<IfModule mod_mime.c>
    AddType text/xml xml
    AddType application/javascript js
    AddType application/vnd.ms-fontobject eot
    AddType application/x-font-ttf ttf ttc
    AddType font/opentype otf
    AddType font/woff woff
    AddType font/woff2 woff2
    AddType image/svg+xml svg
    AddType image/webp webp
</IfModule>

# === Сжатие: Brotli (приоритет) ===
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css application/json application/javascript application/xml
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|svg|mp4|ogg|mp3|avi|flv|mov)(?:\?.*)?$ no-brotli
    <FilesMatch "\.(css|js|html|php)$">
        SetOutputFilter BROTLI_COMPRESS
    </FilesMatch>
</IfModule>

# === Сжатие: Gzip (fallback) ===
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css application/json application/javascript application/xml
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|svg|mp4|ogg|mp3|avi|flv|mov)(?:\?.*)?$ no-gzip dont-vary
    <FilesMatch "\.(css|js|html|php)$">
        SetOutputFilter DEFLATE
    </FilesMatch>
</IfModule>

# === Дополнительные настройки ===
# Генерация ETag для эффективного кэширования
FileETag MTime Size

4. Настройка PHP-FPM — управление процессами

Файл: /etc/php71w/php-fpm.d/site.conf

[site]
; Сокет для Apache
listen = /var/run/php-fpm/php-site.sock

; Изоляция через chroot (опционально)
chroot = /path/to/site
chdir = /

; Разрешить подключение только с localhost
listen.allowed_clients = 127.0.0.1

; Корень сайта
php_admin_value[doc_root] = /public_html
php_admin_value[include_path] = .:/public_html

; Пользователь и группа
user = siteuser
group = sitegroup

; Настройки сокета
listen.owner = apache
listen.group = apache
listen.mode = 0666

; Максимальная очередь подключений (важно при всплесках)
listen.backlog = 4096

; Стратегия управления процессами
pm = dynamic
pm.max_children = 120
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30

; Перезапуск после N запросов — профилактика утечек
pm.max_requests = 500

; Таймаут бездействия
pm.process_idle_timeout = 60s

; Лимиты ресурсов
rlimit_files = 50000
rlimit_core = unlimited

; Убить скрипт, если выполняется дольше 20 секунд
request_terminate_timeout = 20s

; Только PHP-файлы могут выполняться
security.limit_extensions = .php

; === Настройки OPcache (можно вынести в отдельный файл) ===
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 2048
php_admin_value[opcache.interned_strings_buffer] = 32
php_admin_value[opcache.max_accelerated_files] = 100000
php_admin_value[opcache.revalidate_freq] = 60
php_admin_value[opcache.fast_shutdown] = 1
php_admin_value[opcache.error_log] = /var/log/php-fpm/opcache.log
php_admin_value[opcache.log_verbosity_level] = 3

; Сессии
php_value[session.save_handler] = files
php_value[session.save_path] = /tmp

5. Настройка OPcache (/etc/php.d/10-opcache.ini)

zend_extension=opcache.so

opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=2048
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=100000
opcache.use_cwd=1
opcache.validate_timestamps=0
opcache.revalidate_freq=60
opcache.save_comments=0
opcache.fast_shutdown=1
opcache.blacklist_filename=/etc/php.d/opcache*.blacklist
opcache.error_log=/var/log/php-fpm/opcache-error.log
opcache.log_verbosity_level=1
; Использует huge pages (2M вместо 4K) — снижает нагрузку на TLB
; Требует: echo 'vm.nr_hugepages = 1280' >> /etc/sysctl.conf && sysctl -p
opcache.huge_code_pages=1
opcache.file_update_protection=10

Пример: диагностика и мониторинг OPcache в production

После настройки OPcache с параметрами:

opcache.memory_consumption=2048
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=100000
opcache.validate_timestamps=0
opcache.huge_code_pages=1

Рекомендуется регулярно проверять его состояние. Это позволяет:

5.1. Пример: cтатус OPcache

Через функцию opcache_get_status() можно получить полную информацию о работе кэша.

Array
(
    [opcache_enabled] => 1
    [memory_usage] => Array
        (
            [used_memory] => 94356256             /* ~90 МБ */
            [free_memory] => 2053127392           /* ~1.91 ГБ свободно */
            [wasted_memory] => 0
            [current_wasted_percentage] => 0
        )

    [interned_strings_usage] => Array
        (
            [buffer_size] => 33554432             /* 32 МБ */
            [used_memory] => 2535032              /* ~2.4 МБ использовано */
            [free_memory] => 31019400             /* ~29.6 МБ свободно */
            [number_of_strings] => 41815
        )

    [opcache_statistics] => Array
        (
            [num_cached_scripts] => 698           /* Файлов в кэше */
            [hits] => 13521943                    /* Успешных обращений */
            [misses] => 804                       /* Промахов */
            [opcache_hit_rate] => 99.994          /* Хитрейт: почти идеал */
            [oom_restarts] => 0                   /* Перезапусков из-за нехватки памяти */
            [hash_restarts] => 0                  /* Коллизий хешей */
            [manual_restarts] => 0                /* Ручных сбросов */
            [start_time] => 1771470603            /* Время запуска (Unix) */
        )

    [scripts] => Array                            /* Детали по файлам */
        (
            [/sites/.../zemez_search.php] => Array
                (
                    [hits] => 54809               /* Часто используется (поиск) */
                    [memory_consumption] => 1936
                )

            [/sites/storage/cache/.../02accountlogin.inc] => Array
                (
                    [hits] => 55
                    [memory_consumption] => 48672 /* Большой файл (~48 КБ) */
                )

            [/sites/.../RawHeadersParser.php] => Array
                (
                    [hits] => 2                   /* Редко используется */
                )
        )
)

5.2. Как интерпретировать ключевые метрики

Метрика Значение Что значит Рекомендация
opcache_hit_rate 99.994% Почти все запросы обслуживаются из кэша Идеально. PHP не компилируется повторно
misses 804 Мало промахов при 13.5 млн обращений max_accelerated_files достаточно
oom_restarts 0 Не было исчерпания памяти memory_consumption=2048 подобран верно
interned_strings_usage ~2.4 МБ из 32 МБ Буфер строк почти не используется Можно уменьшить до 16 МБ
num_cached_scripts 698 Из 100K возможных — используется мало Норма для OpenCart / WordPress
scripts[hits] от 2 до 54809 Видно, какие файлы "горячие" Полезно для профилирования

5.3. Экспорт метрик в Prometheus

Создайте скрипт для сбора данных: /var/www/monitoring/opcache-metrics.php

<?php
// Запрет доступа снаружи
if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1' && $_SERVER['REMOTE_ADDR'] !== '::1') {
    http_response_code(403);
    exit('Access denied');
}

// Только GET
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    http_response_code(405);
    exit('Method not allowed');
}

$status = opcache_get_status(false);
if (!$status || !isset($status['opcache_statistics'])) {
    http_response_code(500);
    exit('OPcache not available or disabled');
}

$stats = $status['opcache_statistics'];
$memory = $status['memory_usage'];
$strings = $status['interned_strings_usage'];

function mb($bytes) {
    return round($bytes / (1024 * 1024), 2);
}

header('Content-Type: text/plain; charset=UTF-8');

echo "# HELP opcache_enabled OPcache включён\n";
echo "# TYPE opcache_enabled gauge\n";
echo "opcache_enabled 1\n\n";

echo "# HELP opcache_hit_rate Процент попаданий в кэш\n";
echo "# TYPE opcache_hit_rate gauge\n";
echo "opcache_hit_rate " . number_format($stats['opcache_hit_rate'], 6) . "\n\n";

echo "# HELP opcache_memory_usage_bytes Использование памяти OPcache\n";
echo "# TYPE opcache_memory_usage_bytes gauge\n";
echo "opcache_memory_usage_bytes{type=\"used\"} {$memory['used_memory']}\n";
echo "opcache_memory_usage_bytes{type=\"free\"} {$memory['free_memory']}\n";
echo "opcache_memory_usage_bytes{type=\"wasted\"} {$memory['wasted_memory']}\n\n";

echo "# HELP opcache_interned_strings_usage_bytes Использование буфера строк\n";
echo "# TYPE opcache_interned_strings_usage_bytes gauge\n";
echo "opcache_interned_strings_usage_bytes{type=\"used\"} {$strings['used_memory']}\n";
echo "opcache_interned_strings_usage_bytes{type=\"free\"} {$strings['free_memory']}\n\n";

echo "# HELP opcache_cache_stats Статистика кэша\n";
echo "# TYPE opcache_cache_stats counter\n";
echo "opcache_cache_stats{event=\"hits\"} {$stats['hits']}\n";
echo "opcache_cache_stats{event=\"misses\"} {$stats['misses']}\n";
echo "opcache_cache_stats{event=\"oom_restarts\"} {$stats['oom_restarts']}\n";
echo "opcache_cache_stats{event=\"hash_restarts\"} {$stats['hash_restarts']}\n";
echo "opcache_cache_stats{event=\"manual_restarts\"} {$stats['manual_restarts']}\n\n";

echo "# HELP opcache_script_count Количество закешированных скриптов\n";
echo "# TYPE opcache_script_count gauge\n";
echo "opcache_script_count {$stats['num_cached_scripts']}\n\n";

echo "# HELP opcache_uptime_seconds Время работы OPcache\n";
echo "# TYPE opcache_uptime_seconds gauge\n";
echo "opcache_uptime_seconds " . (time() - $stats['start_time']) . "\n\n";

5.4. Защита эндпоинта

Разрешите доступ только с localhost.

Apache (.htaccess или vhost):

<Location "/monitoring/opcache-metrics.php">
    Require local
</Location>

Nginx (в конфигурацию сайта):

location = /monitoring/opcache-metrics.php {
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/var/run/php-fpm/www.sock;
    include fastcgi_params;
}

5.5. Настройка Prometheus

Добавьте задачу в prometheus.yml:

scrape_configs:
  - job_name: 'opcache'
    scrape_interval: 30s
    static_configs:
      - targets: ['127.0.0.1:80']
    metrics_path: /monitoring/opcache-metrics.php
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        replacement: 'php-fpm-opcache'

5.6. Grafana Dashboard (JSON)

Импортируйте следующий JSON в Grafana: Dashboard → Import → Paste JSON

{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": "-- Grafana --",
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "gnetId": null,
  "graphTooltip": 0,
  "id": null,
  "links": [],
  "panels": [
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "orange",
                "value": 95
              },
              {
                "color": "red",
                "value": 98
              }
            ]
          },
          "unit": "percent"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 6,
        "w": 8,
        "x": 0,
        "y": 0
      },
      "id": 1,
      "options": {
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "last"
          ],
          "fields": "",
          "values": false
        },
        "showThresholdLabels": false,
        "showUnfilled": true,
        "text": {}
      },
      "pluginVersion": "8.3.3",
      "targets": [
        {
          "expr": "opcache_hit_rate",
          "interval": "",
          "legendFormat": "Hit Rate",
          "refId": "A"
        }
      ],
      "title": "OPcache Hit Rate",
      "type": "gauge"
    },
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 20,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineWidth": 2,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          },
          "unit": "decbytes"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 6,
        "w": 16,
        "x": 8,
        "y": 0
      },
      "id": 2,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom"
        },
        "tooltip": {
          "mode": "multi",
          "sort": "none"
        }
      },
      "targets": [
        {
          "expr": "opcache_memory_usage_bytes",
          "interval": "",
          "legendFormat": "{{type}}",
          "refId": "A"
        }
      ],
      "title": "Memory Usage",
      "type": "timeseries"
    },
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 1
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 4,
        "w": 6,
        "x": 0,
        "y": 6
      },
      "id": 3,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": [
            "last"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "8.3.3",
      "targets": [
        {
          "expr": "opcache_cache_stats{event=\"oom_restarts\"}",
          "interval": "",
          "legendFormat": "OOM Restarts",
          "refId": "A"
        }
      ],
      "title": "OOM Restarts",
      "type": "stat"
    },
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 1
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 4,
        "w": 6,
        "x": 6,
        "y": 6
      },
      "id": 4,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": [
            "last"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "expr": "opcache_cache_stats{event=\"hash_restarts\"}",
          "interval": "",
          "legendFormat": "Hash Restarts",
          "refId": "A"
        }
      ],
      "title": "Hash Restarts",
      "type": "stat"
    },
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 1
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 4,
        "w": 6,
        "x": 12,
        "y": 6
      },
      "id": 5,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": [
            "last"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "expr": "opcache_script_count",
          "interval": "",
          "legendFormat": "Cached Scripts",
          "refId": "A"
        }
      ],
      "title": "Cached Scripts",
      "type": "stat"
    },
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "bars",
            "fillOpacity": 100,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineWidth": 0,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green"
              }
            ]
          },
          "unit": "s"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 0,
        "y": 10
      },
      "id": 6,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom"
        },
        "tooltip": {
          "mode": "single"
        }
      },
      "targets": [
        {
          "expr": "opcache_uptime_seconds",
          "interval": "",
          "legendFormat": "Uptime",
          "refId": "A"
        }
      ],
      "title": "OPcache Uptime",
      "type": "bargauge"
    },
    {
      "datasource": "Prometheus",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 20,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "smooth",
            "lineWidth": 2,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green"
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 12,
        "y": 10
      },
      "id": 7,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom"
        },
        "tooltip": {
          "mode": "multi",
          "sort": "desc"
        }
      },
      "targets": [
        {
          "expr": "rate(opcache_cache_stats{event=\"hits\"}[5m])",
          "interval": "",
          "legendFormat": "Hits/sec",
          "refId": "A"
        },
        {
          "expr": "rate(opcache_cache_stats{event=\"misses\"}[5m])",
          "interval": "",
          "legendFormat": "Misses/sec",
          "refId": "B"
        }
      ],
      "title": "Hit/Miss Rate (5m)",
      "type": "timeseries"
    }
  ],
  "refresh": "30s",
  "schemaVersion": 36,
  "style": "dark",
  "tags": [
    "php",
    "opcache",
    "performance"
  ],
  "templating": {
    "list": []
  },
  "time": {
    "from": "now-1h",
    "to": "now"
  },
  "timepicker": {},
  "timezone": "",
  "title": "PHP OPcache Monitoring",
  "uid": "opcache-dashboard",
  "version": 1
}

Важно: opcache.validate_timestamps=0 отключает проверку изменений файлов. Обновление кэша — только через перезапуск PHP-FPM или вызов opcache_reset().

Для production — идеально. Для разработки — временно отключить.

Такой подход превращает OPcache из «чёрного ящика» в прозрачный, контролируемый компонент архитектуры.

6. Кэширование HTML через APCu

Memcached и Redis кэшируют данные, но не готовые HTML-страницы. Для максимальной скорости — кэшируйте целые страницы до запуска контроллеров.

Решение: использовать APCu (встроен в PHP) для хранения HTML с учётом:

Ключевые особенности:

Пример (упрощённо):

<?php
$key = 'page_' . md5($_SERVER['REQUEST_URI'] . $_SERVER['HTTP_USER_AGENT']);
$html = apcu_fetch($key);

if ($html !== false) {
    echo gzdecode($html);
    exit;
}

// ... выполнение контроллера ...

apcu_store($key, gzencode($output), 3600); // 1 час

Brotli применяется только к статике (JS, CSS, JSON), так как выигрыш в сжатии оправдан. Для динамических HTML — GZIP на уровне кэширования эффективнее.

7. Прогрев кэша — автоматический "разогрев" сайта

Запускается по cron. Прогревает ключевые страницы для ПК и мобильных.

#!/bin/bash
# Скрипт: /path/to/scripts/preload_pages.sh
# Запуск: */15 * * * * /path/to/scripts/preload_pages.sh

SCRIPT_NAME=$(basename "$0")
START_TIME=$(date +%s)

PAGES_FILE="/path/to/scripts/txt/pages_to_warm.txt"

echo "🚀 Запущен скрипт $SCRIPT_NAME в $(date)"

if [ ! -f "$PAGES_FILE" ]; then
  echo "❌ Файл $PAGES_FILE не найден"
  exit 1
fi

if [ ! -s "$PAGES_FILE" ]; then
  echo "❌ Файл $PAGES_FILE пустой"
  exit 1
fi

readarray -t UNIQUE_PAGES < "$PAGES_FILE"

DESKTOP_PAGES=0
MOBILE_PAGES=0

echo "🔥 Начинаем прогрев страниц для ПК..."
for PAGE in "${UNIQUE_PAGES[@]}"; do
  URL="https://example.com$PAGE"
  HTTP_CODE=$(curl -fs -o /dev/null -w "%{http_code}" --connect-timeout 10 "$URL")
  if [ "$HTTP_CODE" -eq 200 ]; then
    ((DESKTOP_PAGES++))
  else
    echo "⚠️ Ошибка прогрева ($HTTP_CODE): $URL"
  fi
  sleep 0.5
done
echo "✅ Прогреты $DESKTOP_PAGES страниц для ПК"

echo "📱 Начинаем прогрев страниц для смартфонов..."
for PAGE in "${UNIQUE_PAGES[@]}"; do
  URL="https://example.com$PAGE"
  HTTP_CODE=$(curl -fs -o /dev/null -w "%{http_code}" \
    --connect-timeout 10 \
    -A "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" \
    "$URL")
  if [ "$HTTP_CODE" -eq 200 ]; then
    ((MOBILE_PAGES++))
  else
    echo "⚠️ Ошибка прогрева ($HTTP_CODE): $URL"
  fi
  sleep 0.5
done
echo "✅ Прогреты $MOBILE_PAGES страниц для смартфонов"

END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "🎉 Общая статистика:"
echo "   ⚙️ ПК: $DESKTOP_PAGES страниц"
echo "   📱 Смартфоны: $MOBILE_PAGES страниц"
echo "⏳ Общее время выполнения: $DURATION секунд"

Список страниц для прогрева: /path/to/scripts/txt/pages_to_warm.txt

/
/category/popular
/product/featured-1
/product/featured-2
/information/delivery
/information/privacy
/blog/news-1
/blog/news-2

Разделите страницы на группы и запустите несколько скриптов с разной периодичностью (например, каждые 15, 30, 60 минут), если контент сильно варьируется.

8. Оптимизация на стороне клиента

Чтобы сделать переходы ещё быстрее:

Service Worker — кэширование на устройстве

Предиктивная навигация

Пример предзагрузки при наведении:

document.addEventListener('mouseover', (e) => {
    const link = e.target.closest('a');
    if (link && shouldPreload(link.href)) {
        fetch(link.href, { method: 'HEAD' });
    }
});

Результат — переходы как в нативном приложении.

9. Диагностика и администрирование

После настройки сервера необходимо обязательно диагностировать состояние системы, просматривать логи, тестировать нагрузку и управлять сервисами. Ниже — набор примеров практических команд для администрирования.

Статус сервисов

# Проверка состояния ключевых служб
sudo systemctl status mysqld
sudo systemctl status httpd
sudo systemctl status nginx
sudo systemctl status php-fpm
sudo systemctl status php71w-fpm@appname.service  # если используется pool

# Состояние планировщика задач
systemctl status cron || systemctl status crond || systemctl status systemd-timer

Примеры вывода systemctl status

MySQL (высокая нагрузка, кэширование в RAM)

● mysqld.service - MySQL database server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled)
   Active: active (running) since Mon 2026-01-01 01:00:14 +03; 1 months 22 days ago
 Main PID: 27104 (mysqld_safe)
    Tasks: 61
   Memory: 5.4G
   CGroup: /system.slice/mysqld.service
           ├─27104 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
           └─27769 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --log-error=server.err --open-files-limit=10000

Warning: Journal has been rotated since unit was started. Log output is incomplete.

PHP-FPM (pool с высокой активностью)

● php71w-fpm@site.service - The PHP FastCGI Process Manager - site
   Loaded: loaded (/usr/lib/systemd/system/php71w-fpm@site.service; enabled)
   Active: active (running) since Thu 2026-02-19 08:10:03 +03; 3 days ago
 Main PID: 7319 (php-fpm)
   Status: "Processes active: 0, idle: 26, Requests: 148825, slow: 0, Traffic: 0.3req/sec"
    Tasks: 27
   Memory: 508.6M
   CGroup: /system.slice/system-php71w\x2dfpm.slice/php71w-fpm@site.service
           ├─ 7319 php-fpm: master process (/etc/php71w/php-fpm.site.conf)
           ├─ 7427 php-fpm: pool site
           ├─18313 php-fpm: pool site
           ...
           └─32641 php-fpm: pool site

Apache (обслуживает CMS, высокая нагрузка, graceful reloads)

● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled)
   Active: active (running) since Mon 2026-01-01 01:00:16 +03; 1 months 22 days ago
     Docs: man:httpd.service(8)
  Process: 21699 ExecReload=/usr/sbin/httpd $OPTIONS -k graceful (code=exited, status=0/SUCCESS)
 Main PID: 27819 (httpd)
   Status: "Total requests: 1528011; Idle/Busy workers 98/1; Requests/sec: 0.337; Bytes/sec: 19KB"
    Tasks: 213
   Memory: 530.6M
   CGroup: /system.slice/httpd.service
           ├─21712 /usr/sbin/httpd -DFOREGROUND
           ├─21757 /usr/sbin/httpd -DFOREGROUND
           ...
           └─27819 /usr/sbin/httpd -DFOREGROUND

Feb 22 12:00:03 server systemd[1]: Reloaded The Apache HTTP Server.

Nginx (обслуживает MySQL, phpMyAdmin, BrainyCP, Roundcube, Squirrelmail, API)

● nginxb.service - The Nginx HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/nginxb.service; enabled)
   Active: active (running) since Mon 2026-01-01 01:00:17 +03; 1 months 22 days ago
  Process: 28287 ExecStart=/usr/sbin/nginxb (code=exited, status=0/SUCCESS)
 Main PID: 28288 (nginxb)
    Tasks: 8
   Memory: 44.3M
   CGroup: /system.slice/nginxb.service
           ├─28288 nginx: master process /usr/sbin/nginxb
           ├─28289 nginx: worker process
           ...
           └─28295 nginx: cache manager process

Cron — планировщик задач

● crond.service - Command Scheduler
   Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled)
   Active: active (running) since Sat 2025-10-04 21:09:14 +03; 4 months 18 days ago
 Main PID: 571 (crond)
    Tasks: 1
   Memory: 2.3M
   CGroup: /system.slice/crond.service
           └─571 /usr/sbin/crond -n

Примеры команд

Нагрузочное тестирование

# Apache Benchmark: 1000 запросов, 100 параллельных соединений
ab -n 1000 -c 100 https://example.com/

# Краткий HTTP-тест (проверка времени ответа)
curl -o /dev/null -s -w "DNS: %{time_namelookup} | Connect: %{time_connect} | TTFB: %{time_starttransfer} | Total: %{time_total}\n" https://example.com/

Мониторинг ресурсов

# Загрузка CPU, памяти, процессов
htop

# Дисковый I/O (в реальном времени)
iotop

# Объём свободной памяти
free -h

# Поиск запущенных процессов
pgrep mysqld
pgrep httpd
pgrep nginx
pgrep php-fpm

# Все процессы (с фильтром по ключевым службам)
ps aux | grep -E 'mysqld|httpd|nginx|php-fpm'

# Топ-10 процессов по потреблению памяти
ps aux --sort=-%mem | head -n 10

# Статистика загрузки CPU (5 измерений с интервалом 1 сек)
mpstat 1 5

Мониторинг дискового пространства

# Общий объём использования диска
df -h

# Размер корневых директорий (отсортировано по убыванию)
sudo du -sh /* 2>/dev/null | grep -v '^0\.' | sort -hr

# Размер поддиректорий /var
du -sh /var/* 2>/dev/null | sort -hr

# Поиск файлов больше 100 МБ
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | head -20

Логи — просмотр и диагностика

# MySQL
sudo tail -f /var/log/mysqld.log
sudo tail -n 20 -f /var/log/mysqld.log
tail -n 20 /var/lib/mysql/example.com.err

# Apache
sudo tail -f /var/log/httpd/error_log
sudo tail -n 20 -f /var/log/httpd/error_log
sudo tail -n 50 /etc/httpd/vhost_logs/example.com_error

# Nginx
tail -f /var/log/nginx/error.log
sudo tail -n 20 -f /var/log/nginx/error.log

# PHP-FPM
tail -f /var/log/php-fpm/appname.log
sudo tail -n 20 -f /var/log/php-fpm/appname.log

# OPcache
sudo tail -f /var/log/php-fpm/opcache.log
sudo tail -n 20 -f /var/log/php-fpm/opcache.log

# Cron
cat /var/log/cron | grep -i "preload\|php"

# Проверка активных модулей Apache
grep -r 'LoadModule' /etc/httpd/conf.modules.d/

Проверка конфигурации

# Проверка синтаксиса Apache
sudo apachectl configtest

# Проверка синтаксиса Nginx
/usr/sbin/nginx -t -c /etc/nginx/nginx.conf

# Проверка подключённых виртуальных хостов
apache2ctl -S || httpd -S
nginx -T 2>/dev/null | grep server_name

Перезапуск сервисов

# Перезапуск MySQL
sudo systemctl restart mysqld

# Перезапуск веб-серверов
sudo systemctl restart httpd
sudo systemctl restart nginx

# Перезапуск PHP-FPM
sudo systemctl restart php-fpm
sudo systemctl restart php71w-fpm@appname.service

# Безопасная перезагрузка (не убивает соединения)
sudo systemctl reload httpd
sudo systemctl reload nginx

# Принудительная перезагрузка демона
sudo systemctl daemon-reexec

Очистка кэша

# Очистка OPcache
php -r 'opcache_reset();'

# Очистка APCu (если используется)
php -r 'apcu_clear_cache();'

# Очистка кеша приложения (пример для OpenCart, Laravel и др.)
rm -rf /path/to/site/storage/cache/*
rm -rf /path/to/site/system/storage/cache/*

Проверка веб-сервера и SSL

# Проверка HTTP-статуса сайта
curl -I https://example.com/

# Подробная проверка с заголовками
curl -s -D - -o /dev/null -H "Host: example.com" http://127.0.0.1/

# Активные порты (80, 443 и др.)
sudo netstat -tuln | grep ':80\|:443'
sudo lsof -i :80
sudo lsof -i :443

# Проверка OCSP Stapling (SSL-кэш)
openssl s_client -connect example.com:443 -status -servername example.com 2>/dev/null | grep -A 10 -B 2 "OCSP response"

# Проверка сертификата
openssl x509 -in /path/to/ssl/example.com.crt -text -noout | grep -i "not after"

9.1. Автоматизированный мониторинг через Prometheus + Grafana

Команды htop, df -h, tail -f — отличны для разовой диагностики, но они:

Чтобы перейти к проактивному мониторингу, рекомендуется использовать стек Prometheus + Grafana + Node Exporter.

1. Установка Node Exporter (мониторинг сервера)

Node Exporter собирает метрики системы: CPU, память, диски, сеть.

# Скачайте и запустите Node Exporter
wget https://github.com/prometheus/node_exporter/releases/latest/download/node_exporter-*.linux-amd64.tar.gz
tar xvfz node_exporter-*.linux-amd64.tar.gz
cd node_exporter-*linux-amd64

# Запуск в фоне
./node_exporter &

# Проверка: http://your-server:9100/metrics
curl -s http://localhost:9100/metrics | head -10

Рекомендуется запускать через systemd для автозагрузки.

9.2. Настройка Prometheus

Добавьте задачу в prometheus.yml:

scrape_configs:
  # ... другие job'ы ...

  - job_name: 'node'
    scrape_interval: 30s
    static_configs:
      - targets: ['your-server-ip:9100']
        labels:
          instance: 'production-web-01'

9.3. Примеры ключевых метрик из Node Exporter

Метрика Аналог в SSH Назначение
node_cpu_seconds_total{mode="idle"} mpstat, htop Загрузка CPU в реальном времени
node_memory_MemAvailable_bytes free -h Свободная память (точнее, чем MemFree)
node_filesystem_avail_bytes df -h Свободное место на диске
node_disk_io_time_seconds_total iotop Нагрузка на диск (I/O wait)
node_network_receive_bytes_total iftop, ip -s link Трафик сети

9.4. Grafana Dashboard для системного мониторинга

Импортируйте официальный дашборд: Node Exporter Full (ID: 1860)

Или используйте этот JSON-фрагмент (упрощённая версия):

{
  "title": "Server Health",
  "tags": ["node", "system", "production"],
  "panels": [
    {
      "title": "CPU Usage",
      "type": "timeseries",
      "datasource": "Prometheus",
      "targets": [
        {
          "expr": "100 - (avg by(instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
          "legendFormat": "CPU Used %"
        }
      ],
      "unit": "percent"
    },
    {
      "title": "Memory Usage",
      "type": "timeseries",
      "datasource": "Prometheus",
      "targets": [
        {
          "expr": "(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100",
          "legendFormat": "Used %"
        }
      ],
      "unit": "percent"
    },
    {
      "title": "Disk Space (Root)",
      "type": "gauge",
      "datasource": "Prometheus",
      "targets": [
        {
          "expr": "100 - ((node_filesystem_avail_bytes{mountpoint=\"/\"} / node_filesystem_size_bytes{mountpoint=\"/\"}) * 100)",
          "legendFormat": "Used %"
        }
      ],
      "unit": "percent"
    }
  ],
  "refresh": "30s",
  "schemaVersion": 36,
  "version": 1
}

9.5. Интеграция с вашими текущими командами

Пример: Вместо htop вы видите график CPU в Grafana за 24 часа и получаете алерт, если нагрузка >90% более 5 минут.

Вместо df -h — дашборд с цветовыми индикаторами свободного места.

Вместо tail -f /var/log/php-fpm/appname.log — алерт при появлении PHP Fatal error через Loki + Promtail.

9.6. Дальнейшее развитие

Такой подход превращает реактивное администрирование (по SSH) в проактивный SRE-подход.

10. Заключение

Комплексный подход к оптимизации стека LAMP/LEMP позволяет не только ускорить веб-сервис, но и сделать его стабильным, предсказуемым и легко управляемым под высокой нагрузкой.

Корректная настройка позволяет:

Представленные в документе настройки универсальны и применимы к любому PHP-приложению: OpenCart, WordPress, Laravel, Magento, Symfony и самописным CMS.

Рекомендация: минимизируйте реактивное администрирование. Настройте мониторинг: Prometheus + Grafana, алерты — это позволит выявлять проблемы до пользователей. Технический долг в инфраструктуре всегда приводит к росту TTFB, простою, снижению финансовых показателей, ущербу репутации компании и утрате доверия пользователей — целесообразнее его предотвратить, чем устранять последствия.

Документы