Взаимодействие Nginx, PHP и MariaDB на веб-сервере

Раздел: Администрирование веб-серверов -> Интеграция Nginx с базами данных

Введение

Связка 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 попытки(ок)

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

Nginx, PHP и MariaDB - comments

En
Nginx php mariadb (php)