Взаимодействие Nginx, PHP и MariaDB на веб-сервере
Введение
Связка Nginx, PHP и MariaDB является одной из самых распространённых для современных веб-приложений. Nginx выступает в роли фронтального веб-сервера, PHP (через FPM) обрабатывает скрипты, а MariaDB обеспечивает хранение данных. Основная задача - настроить их совместную работу так, чтобы запросы передавались без задержек, а ошибки подключения сводились к минимуму. Далее рассмотрено основное решение и альтернативные подходы.
Основное решение: Unix-сокет и локальная база данных
Как организовать передачу PHP-запросов от Nginx к PHP-FPM через Unix-сокет?
Использование Unix-сокета вместо TCP-порта снижает накладные расходы на сетевые соединения, особенно когда PHP-FPM работает на том же сервере. Это наиболее эффективный и производительный вариант.
Пример конфигурации Nginx (файл /etc/nginx/conf.d/default.conf):
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
index index.php index.html;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}Nginx php mariadb (nginx, php и mariadb)
Пояснение: директива fastcgi_pass указывает на сокет PHP-FPM. Параметр SCRIPT_FILENAME передаёт полный путь к запрашиваемому скрипту. Файл fastcgi_params содержит стандартные переменные FastCGI.
Типичная ошибка: файл сокета не найден (502 Bad Gateway). Причина - несовпадение пути в конфигурации Nginx и фактического расположения сокета. Проверяется командой ls -la /var/run/php/. Также возможны проблемы с правами доступа - пользователь Nginx (www-data) должен иметь доступ к сокету.
Настройка PHP-FPM (пул www.conf): убедиться, что раскомментирована строка listen = /var/run/php/php8.1-fpm.sock и заданы правильные права (listen.mode = 0660, listen.owner = www-data, listen.group = www-data).
Как подключиться к MariaDB из PHP-скрипта?
Стандартный способ - через расширение PDO или mysqli. Пример с PDO:
<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8';
$user = 'dbuser';
$password = 'dbpass';
try {
$pdo = new PDO($dsn, $user, $password);
echo 'Подключение успешно';
} catch (PDOException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
?>Nginx php postgresql (nginx, php и postgresql)
Хост localhost указывает на Unix-сокет MariaDB, что повышает скорость. Для TCP-соединения используется 127.0.0.1.
Ошибка: SQLSTATE[HY000] [2002] Connection refused - значит, MariaDB не запущена или использует не тот сокет/порт. Проверяется командой systemctl status mariadb и netstat -tlnp | grep 3306.
Варианты конфигурации
Как настроить Nginx и PHP-FPM через TCP-порт (не сокет)?
Удобно при разделении серверов (Nginx на одном узле, PHP-FPM на другом). В конфигурации PHP-FPM меняется listen = 127.0.0.1:9000 (или внешний IP). В Nginx - fastcgi_pass 127.0.0.1:9000;. Недостаток - дополнительная сетевая задержка и необходимость открывать порт в файрволе.
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Проблема: если порт занят другим процессом, возникает ошибка Address already in use. Решение - сменить порт в пуле PHP-FPM и в Nginx.
Как организовать отдельные пулы PHP-FPM для разных сайтов?
Каждый сайт получает свой процесс-обработчик с индивидуальными настройками (лимиты, версия PHP). Создаётся файл пула, например /etc/php/8.1/fpm/pool.d/site2.conf:
[site2]
user = site2_user
listen = /var/run/php/php8.1-site2.sock
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 5
В Nginx указывается соответствующий сокет для каждого блока server.
Ошибка: сокет не создаётся из-за неправильного пути или прав. Проверяется владелец директории /var/run/php/ и listen.mode.
Как обеспечить устойчивость при перезапуске MariaDB (persistent connections)?
PHP PDO поддерживает постоянные соединения (PDO::ATTR_PERSISTENT => true). Однако при резком обрыве связи MariaDB могут возникнуть «битые» соединения. Рекомендуется проверять соединение перед выполнением запросов или использовать пул соединений (например, через персистентный слой в фреймворке).
$options = [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
$pdo = new PDO($dsn, $user, $password, $options);
Проблема: при долгом простое постоянное соединение может быть разорвано MariaDB по таймауту (wait_timeout). Следующий запрос выдаст ошибку. Решение - увеличить wait_timeout или реализовать повторное подключение.
Как использовать несколько версий PHP на одном сервере?
Устанавливаются разные версии PHP-FPM. Каждая версия создаёт свой сокет (например, php8.1-fpm.sock и php7.4-fpm.sock). В Nginx в location для определённых расширений или директорий указывается разный сокет:
location ~ \.php$ {
if ($request_uri ~ ^/legacy/) {
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
# ... остальные параметры
}
Такой подход требует внимательного тестирования совместимости приложений.
Общие проблемы и способы их решения
502 Bad Gateway: PHP-FPM не отвечает. Проверяется статус службы (systemctl status php8.1-fpm), логи (/var/log/php8.1-fpm.log), правильность пути к сокету.
504 Gateway Timeout: долгий ответ PHP. Настраиваются таймауты в Nginx (fastcgi_read_timeout 300) и в PHP-FPM (request_terminate_timeout).
Ошибки доступа к базе данных: неверные учётные данные, отсутствие прав у пользователя MariaDB. Проверяется через клиент командной строки mysql -u dbuser -p.
Расширенные примеры настройки и кода
Пример конфигурации Nginx с несколькими location для разных версий PHP
server {
listen 80;
root /var/www/html;
# для сайта на PHP 7.4
location ^~ /legacy {
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
# остальные запросы на PHP 8.1
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location / {
try_files $uri $uri/ =404;
}
}
Пояснение: блок ^~ /legacy перехватывает все URL, начинающиеся с /legacy, и отправляет их на PHP 7.4. Остальные .php-файлы обрабатываются PHP 8.1. Обратите внимание на порядок location - сначала более специфичные.
Пример PHP-скрипта с подготовленными запросами к MariaDB (PDO) и обработкой ошибок
<?php
$dsn = 'mysql:host=localhost;dbname=shop;charset=utf8mb4';
$user = 'shop_user';
$pass = 'secure_pass';
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT id, name, price FROM products WHERE active = :active AND price > :min_price');
$stmt->execute([':active' => 1, ':min_price' => 100]);
$products = $stmt->fetchAll();
foreach ($products as $product) {
echo $product['name'] . ' - ' . $product['price'] . "<br>";
}
} catch (PDOException $e) {
error_log('DB error: ' . $e->getMessage());
echo 'Временно недоступно. Попробуйте позже.';
}
?>
Результат (пример): Яблоки - 150
Бананы - 120
Пояснение: используется подготовленное выражение (prepare+bind) для защиты от SQL-инъекций. Режим ERRMODE_EXCEPTION перехватывает ошибки БД и логирует их, не показывая пользователю детали.
Настройка пула PHP-FPM с ограничением памяти и максимального числа запросов
[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30
php_admin_value[date.timezone] = Europe/Moscow
Пояснение: значение pm.max_requests перезапускает процесс после обработки 500 запросов, предотвращая утечку памяти. Параметры php_admin_value задают настройки PHP для всех процессов пула.
Мониторинг соединений MariaDB и настройка лимитов
-- Просмотр активных соединений
SHOW PROCESSLIST;
-- Глобальные лимиты (в /etc/mysql/mariadb.conf.d/50-server.cnf)
[mysqld]
max_connections = 200
connect_timeout = 10
wait_timeout = 600
-- Пользовательские лимиты (создание пользователя с ограничением)
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON mydb.* TO 'webapp'@'localhost' WITH MAX_USER_CONNECTIONS 20;
Результат SHOW PROCESSLIST: +----+------+-----------+-------+---------+------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+-------+---------+------+-------+------------------+ | 45 | web | localhost | mydb | Query | 0 | init | SELECT ... | +----+------+-----------+-------+---------+------+-------+------------------+
Пояснение: мониторинг позволяет выявить медленные запросы или превышение лимитов. Ограничение соединений для конкретного пользователя защищает от перегрузки БД одним приложением.
Настройка кеширования запросов MariaDB (query cache) для встроенных приложений
[mysqld]
query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 2M
Пояснение: кеш запросов ускоряет повторяющиеся SELECT, но в версиях MariaDB 10.1+ рекомендуется использовать более современные методы (например, Redis). Включение кеша актуально для чтения, но может снизить производительность при частых UPDATE.
Пример обработки ошибок подключения к БД с повторными попытками (ретраи)
<?php
$max_attempts = 3;
$attempt = 0;
do {
try {
$pdo = new PDO($dsn, $user, $pass);
$connected = true;
} catch (PDOException $e) {
$attempt++;
if ($attempt >= $max_attempts) {
throw $e;
}
sleep(1); // пауза перед повторной попыткой
}
} while (!$connected);
echo 'Соединение установлено с ' . $attempt . ' попытки(ок)';
?>
Результат: Соединение установлено с 2 попытки(ок)
Пояснение: такой подход полезен при временной недоступности БД во время перезагрузки или сбоя. Однако использовать ретраи стоит осторожно, чтобы не усугубить нагрузку.