PHP, MySQL, Nginx: як налаштувати серверний стек так, щоб адмін не прокидався о третій ночі
Уявіть: п'ятниця, 23:47, ви вже в ліжку з серіалом, і тут - сповіщення. Сайт ліг. MySQL з'їв всю пам'ять, PHP-воркери закінчились, Nginx повертає 502. Знайомо? Якщо ви хоч раз налаштовували хостинг-сервер "на око", без розуміння як саме PHP, MySQL і веб-сервер взаємодіють - ця історія рано чи пізно стане вашою. Але є хороша новина: грамотне налаштування серверного стека - це не ракетна наука. Це набір конкретних рішень, які перетворюють хаос на передбачувану систему.
Серверний стек - це оркестр, а не три окремі інструменти
Більшість гайдів розповідають, як налаштувати Nginx окремо, PHP окремо, MySQL окремо. І це головна помилка. Ваш сервер - це оркестр, де кожен інструмент грає свою партію, але фальшива нота одного ламає всю симфонію. Коли PHP-FPM породжує 50 воркерів, а MySQL може тримати лише 30 з'єднань - ви отримуєте чергу. Коли Nginx приймає 10 000 з'єднань, а PHP обробляє лише 200 - ви отримуєте таймаути.
Головний принцип: налаштовувати потрібно не компоненти, а зв'язки між ними. Це як у кухні ресторану - неважливо, наскільки швидко шеф ріже овочі, якщо офіціант носить по одній тарілці.
Ось базовий серверний стек для 90% веб-проєктів:
- Nginx - приймає запити, роздає статику, проксює динаміку
- PHP-FPM - виконує серверний код (WordPress, Laravel, будь-який PHP-фреймворк)
- MySQL/MariaDB - зберігає та віддає дані
- OPcache - кешує скомпільований PHP-код у пам'яті
- Redis або Memcached - кешує об'єктні запити та сесії
Nginx: перестаньте копіювати конфіги з Stack Overflow
Серйозно. Кожен другий конфіг Nginx, який гуляє по інтернету - це Франкенштейн з директив, написаних для різних версій і різних завдань. Я бачив сервери, де worker_connections стояв на 1024 при 16 ГБ RAM, і навпаки - 65 536 з'єднань на VPS з 1 ГБ пам'яті. Обидва варіанти - шлях до біди.
Ось формула, яка реально працює:
- worker_processes - ставте auto або рівно кількості CPU-ядер. 4 ядра = 4 воркери. Не більше.
- worker_connections - для типового сайту 2048-4096 на воркер. Помножте на кількість воркерів - це ваша стеля одночасних з'єднань.
- keepalive_timeout - 15-30 секунд. Не 65, як стоїть за замовчуванням. Довгий keepalive тримає з'єднання відкритими і з'їдає ресурси.
- gzip - увімкніть для text/html, text/css, application/javascript. Рівень стиснення 4-6 (не 9, бо CPU плакатиме).
- fastcgi_buffers - 16 16k для більшості випадків. Якщо бачите "upstream sent too big header" - збільшуйте.
"Nginx configuration is not about performance tuning - it's about not getting in the way of performance." - Martin Fjordvald, один з розробників Nginx-модулів
Окремо про статику. Nginx роздає CSS, JS, зображення в десятки разів швидше, ніж будь-який PHP-скрипт. Тому завжди налаштовуйте окремий location-блок для статичних файлів з довгим expires (30 днів мінімум) і вимкненим логуванням. Менше записів у лог - менше навантаження на диск.
PHP-FPM: скільки воркерів вам насправді потрібно
Це питання, яке породжує більше міфів, ніж тема НЛО. Хтось каже "ставте 50", хтось - "рівно кількості ядер". Правда, як завжди, в арифметиці.
Один PHP-FPM воркер у середньому споживає від 20 до 60 МБ RAM (залежно від вашого додатка). WordPress з десятком плагінів - це приблизно 40-50 МБ на воркер. Ось проста математика:
| RAM сервера | Для ОС та MySQL | Доступно для PHP | Max воркерів (при 40 МБ) |
|---|---|---|---|
| 2 ГБ | ~800 МБ | ~1200 МБ | 30 |
| 4 ГБ | ~1500 МБ | ~2500 МБ | 62 |
| 8 ГБ | ~2500 МБ | ~5500 МБ | 137 |
| 16 ГБ | ~4000 МБ | ~12000 МБ | 300 |
Ключовий параметр - pm (process manager). Три режими:
- static - фіксована кількість воркерів. Передбачувано, але з'їдає RAM навіть коли трафіку нуль.
- dynamic - золота середина. Мінімум воркерів завжди активні, решта піднімаються за потребою.
- ondemand - воркери створюються лише при запиті. Економить RAM, але перший запит після простою буде повільнішим.
Для продакшн-серверів з постійним трафіком я рекомендую dynamic з такими параметрами: pm.max_children - за таблицею вище, pm.start_servers - 25% від max, pm.min_spare_servers - 10% від max, pm.max_spare_servers - 30% від max. І обов'язково pm.max_requests = 500 - це перезавантажує воркер після 500 запитів і запобігає витоку пам'яті.
OPcache - це те, що перетворює PHP з інтерпретованої мови у щось схоже на компільовану. Увімкніть його і забудьте про існування. Серйозно. Просто три рядки: opcache.memory_consumption=128, opcache.max_accelerated_files=10000, opcache.validate_timestamps=0 (на продакшні). Останній параметр означає, що OPcache не перевіряє, чи змінились файли - після деплою потрібно вручну скидати кеш, але приріст швидкості - до 70%.
MySQL: конфіг за замовчуванням - це як заводські налаштування в гоночній машині
Стандартний my.cnf розрахований на те, щоб MySQL запустився на будь-якому залізі. На калькуляторі Casio. На холодильнику Samsung. Тому він консервативний до абсурду. InnoDB buffer pool - 128 МБ? У 2025 році? Це як поставити бак на 5 літрів у вантажівку.
InnoDB buffer pool size - це найважливіший параметр MySQL. Встановіть його на 70-80% від пам'яті, виділеної для MySQL. Якщо у вас 4 ГБ RAM і ~1.5 ГБ виділено для бази - ставте innodb_buffer_pool_size = 1G. Цей буфер кешує дані та індекси в оперативній пам'яті, і кожне потрапляння в кеш замість читання з диска - це різниця між 0.1 мс і 10 мс.
Інші параметри, які варто змінити одразу:
- max_connections - поставте трохи більше, ніж pm.max_children у PHP-FPM. Якщо PHP має 60 воркерів - поставте 80 (запас для адмінських підключень).
- innodb_log_file_size - 256 МБ або 512 МБ. Більший лог = менше операцій запису на диск.
- query_cache_type = 0 - так, вимкніть кеш запитів. У MySQL 8.0 його вже видалили, а в 5.7 він частіше шкодить, ніж допомагає. Для кешування використовуйте Redis.
- slow_query_log = 1 і long_query_time = 1 - логуйте все, що виконується довше секунди. Це ваш детектив для пошуку проблемних запитів.
- innodb_flush_log_at_trx_commit = 2 - компроміс між продуктивністю та надійністю. Значення 1 (за замовчуванням) дає ACID-гарантії, але уповільнює запис. Значення 2 може втратити до секунди транзакцій при краші, зате працює вдвічі швидше.
Перевірити, наскільки ефективно працює ваш buffer pool, можна командою SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; - якщо hit rate нижче 99%, вам потрібно більше пам'яті.
Redis: маленький демон, який вирішує великі проблеми
Якщо ваш сайт робить більше 100 запитів до бази на одну сторінку (а WordPress з WooCommerce легко робить 200+), Redis - це не розкіш, а необхідність. Він зберігає результати частих запитів у RAM і віддає їх за мікросекунди замість мілісекунд.
Налаштування Redis мінімальне. Встановіть maxmemory 256mb (або скільки можете дозволити), maxmemory-policy allkeys-lru (видаляти найстаріші ключі при заповненні) - і підключіть до вашого додатка відповідний плагін або бібліотеку. Для WordPress це WP Redis або Redis Object Cache. Для Laravel - вбудована підтримка з коробки.
Одна деталь, яку часто забувають: вимкніть persistence у Redis, якщо використовуєте його лише як кеш. Параметри save "" і appendonly no запобігають запису на диск і прибирають зайве навантаження. Якщо Redis перезавантажиться - кеш перебудується сам. Нічого страшного.
Як перевірити, що все працює разом
Налаштували? Тепер тестуйте. Не "відкрив сайт у браузері - начебто працює", а по-дорослому.
- Запустіть ab -n 1000 -c 50 http://yoursite.com/ (Apache Benchmark) - це 1000 запитів з 50 одночасними. Дивіться на Requests per second і Failed requests.
- Моніторте htop під час тесту. CPU не повинен стабільно сидіти на 100%. RAM не повинна йти в swap.
- Перевірте php-fpm status page - скільки active/idle воркерів, чи є listen queue (черга означає, що воркерів мало).
- Перегляньте MySQL slow log після тесту - з'явились нові повільні запити?
Якщо listen queue в PHP-FPM не пустий - додавайте воркери (якщо є RAM) або оптимізуйте код. Якщо CPU на 100% - проблема в коді або відсутності кешування. Якщо swap використовується - ви перевищили ліміт RAM, зменшуйте буфери або переїжджайте на потужніший сервер.
Правило номер один: спочатку виміряй, потім крути. Змінюйте лише один параметр за раз і тестуйте знову. Інакше ви ніколи не дізнаєтесь, що саме допомогло, а що зламало.
Знаєте, що насправді відрізняє стабільний сервер від того, що падає щоп'ятниці? Не дороге залізо і не магічний хостер. Це адмін (або власник), який витратив один вечір, щоб зрозуміти, як компоненти працюють разом, і налаштував їх під свій конкретний проєкт. Який з описаних параметрів ви перевірите на своєму сервері першим?