LAMP на Ubuntu 24.04: PHP-FPM, MariaDB і HTTPS
Покроково налаштуйте Apache, MariaDB з unix_socket, PHP 8.3 через PHP-FPM, name-based vhost і безкоштовний HTTPS Let's Encrypt на Ubuntu 24.04.
Що ви створюєте
LAMP stack — це чотири компоненти на одному сервері Ubuntu 24.04: Linux в основі, Apache обробляє HTTP-запити, MariaDB зберігає дані, а PHP 8.3 виконує код. Наприкінці ви матимете name-based virtual host, який обслуговує каталог реального застосунку, базу даних з окремим користувачем із мінімально необхідними правами, PHP, підключений до Apache через PHP-FPM, і безкоштовний сертифікат Let's Encrypt.
Саме встановлення складається з чотирьох команд apt. Майже весь цей посібник присвячено з’єднанню цих компонентів і невеликій кількості помилок, через які новий stack може повертати порожню сторінку, передавати вихідний код браузеру для завантаження або не дозволяти ввійти до щойно встановленої бази даних. Кожна з цих проблем має характерні ознаки, які можна розпізнати. Нижче наведено назву кожної проблеми та точний текст, який ви побачите.
Передумови та важливі нюанси
Припускаємо, що у вас є новий KVM VPS з Ubuntu 24.04, користувач із правами sudo або root і публічна IPv4-адреса. Мінімальний стек працює з 1 GB RAM. Перед розгортанням реального застосунку з базою даних збільште обсяг до 2 GB, оскільки стандартні буфери MariaDB разом із кількома робочими процесами PHP-FPM швидко використовують перший гігабайт.
До завершення налаштування Certbot мають виконуватися дві умови. Подбайте про них заздалегідь. Потрібне доменне ім’я з A-записом, що вказує на публічну IP-адресу VPS. Let’s Encrypt виконує перевірку через HTTP для цього доменного імені. Для звичайної IP-адреси сертифікат отримати неможливо. Порти 80 і 443 мають бути доступні з інтернету. У багатьох провайдерів для цього потрібно відкрити їх у мережевому firewall у панелі керування а також у ufw на сервері. Зміни DNS можуть поширюватися до години, тому створіть A-запис заздалегідь. До моменту, коли він знадобиться, запис уже має стати доступним.
Крок 1 — установіть Apache і перевірте стандартну сторінку
sudo apt update
sudo apt install -y apache2apt запускає службу та вмикає її автоматичний запуск. Перевірте це:
systemctl status apache2Ви маєте побачити рядок active (running). Тепер відкрийте http://YOUR_SERVER_IP/ у браузері. Apache2 Ubuntu Default Page із великим банером «It works!» є правильним результатом. Це підтверджує, що Apache обслуговує запити, а не свідчить про помилку. Ця сторінка розташована за адресою /var/www/html/index.html, а обслуговує її стандартний віртуальний хост 000-default.conf, який постачається разом із системою. Пізніше ви вимкнете обидва компоненти. Зараз саме їхня наявність підтверджує правильність налаштування.
Якщо сторінка взагалі не завантажується, але systemctl повідомляє, що процес працює, причиною є firewall. Це буде наступним кроком.
Крок 2 — Відкрийте у firewall порти HTTP і HTTPS
Пакет apache2 реєструє три профілі застосунків ufw. Виведіть їхній список:
sudo ufw app listВи побачите Apache, Apache Full і Apache Secure. Apache — це лише порт 80, Apache Secure — лише порт 443, а Apache Full — обидва порти. Саме цей профіль вам потрібен, оскільки TLS ви додаватимете наприкінці.
sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enableДозвольте OpenSSH перед запуском ufw enable. Типова політика ufw забороняє весь вхідний трафік. Якщо активувати її без правила для SSH, власне з’єднання буде розірвано одразу після активації. Поточна сесія залишиться відкритою, але повторно підключитися ви вже не зможете. Перевірте результат за допомогою sudo ufw status. Для OpenSSH, Apache Full та відповідних правил для v6 має відображатися значення ALLOW.
Крок 3 — встановіть MariaDB і захистіть його
sudo apt install -y mariadb-server
systemctl status mariadbUbuntu 24.04 постачається з MariaDB 10.11 — версією з довгостроковою підтримкою, тому зовнішній репозиторій не потрібен. Коли сервіс запущено, посильте його захист:
sudo mysql_secure_installationУважно читайте підказки замість того, щоб бездумно натискати Enter. Коли система запитає поточний пароль root, натисніть Enter: пароля ще немає. На запит "Switch to unix_socket authentication?" відповідь нічого не змінює, оскільки для цього пакета автентифікацію вже ввімкнено, тому натисніть n. На запит "Change the root password?" відповідайте n з причини, наведеної в наступному абзаці. На решту запитань відповідайте Y: видаліть анонімних користувачів, забороніть віддалений вхід root, видаліть тестову базу даних і перезавантажте таблиці привілеїв.
Ось частина, яка часто збиває з пантелику. У MariaDB на Ubuntu обліковий запис бази даних root використовує автентифікацію unix_socket, а не пароль. Це означає, що база даних довіряє користувачу операційної системи, під яким ви вже пройшли автентифікацію. Тому з root shell працює така команда:
sudo mysql...і відкриває запрошення MariaDB [(none)]> без запиту пароля. Та сама команда, виконана непривілейованим користувачем, буде відхилена. У цьому і полягає суть: доступ до root бази даних пов’язаний із sudo на цьому сервері, і пароль неможливо викрасти, виманити фішингом або підібрати перебором. Це безпечніше за пароль, тому залиште поточні налаштування без змін. Звідси випливає правило: ніколи не підключайте застосунок до облікового запису root. Створюйте окремого користувача для кожного застосунку (крок 7), оскільки застосунок, який підключається через TCP за іменем користувача та паролем, не може використовувати автентифікацію через сокет. Крім того, кожен застосунок має бути обмежений доступом лише до власної бази даних.
Крок 4 — Встановлення PHP 8.3 із PHP-FPM
Стандартна версія PHP в Ubuntu 24.04 — 8.3. Встановіть менеджер процесів FPM і розширення, які потрібні типовому застосунку:
sudo apt install -y php8.3-fpm php8.3-mysql php8.3-cli \
php8.3-curl php8.3-xml php8.3-mbstring php8.3-zipЗверніть увагу, чого в цьому списку немає: libapache2-mod-php. Цей старіший пакет вбудовує інтерпретатор PHP у кожен процес Apache. Це просте рішення, але кожен worker має власну копію PHP незалежно від того, обробляє він скрипт чи статичне зображення. Обидва компоненти мають спільний життєвий цикл. Крім того, це працює лише з MPM prefork в Apache — найменш ефективним варіантом. PHP-FPM натомість запускає PHP як окремий пул процесів, із яким Apache взаємодіє через сокет. Тоді Apache може використовувати багатопотоковий MPM event для статичних файлів і передавати через сокет лише PHP-запити. Пул можна налаштовувати незалежно від вебсервера. Та сама конфігурація FPM згодом працюватиме, якщо перед Apache встановити nginx. Це поточний стандарт не без причини.
Apache підключається до FPM через модуль proxy_fcgi. Увімкніть його, увімкніть конфігурацію, яку встановив пакет FPM, і перезапустіть Apache:
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2a2enconf php8.3-fpm активує /etc/apache2/conf-available/php8.3-fpm.conf, де міститься правило для передавання PHP-файлів до сокета FPM. Це правило знаходить будь-який файл .php і передає його до сокета /run/php/php8.3-fpm.sock:
<FilesMatch ".+\.ph(ar|p|tml)$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>Не редагуйте цей файл: він постачається вже правильно налаштованим. Але шлях до сокета потрібно знати для діагностики помилок «PHP завантажується замість виконання» та «Primary script unknown». Обидві помилки виникають через те, що Apache і FPM використовують різні шляхи до цього сокета або до файла, який за ним розташований.
Крок 5 — Віртуальний хост для застосунку за ім’ям
Віртуальний хостинг за ім’ям дає змогу використовувати одну IP-адресу для багатьох сайтів. Apache вибирає сайт за заголовком Host: у запиті. Створіть окремий каталог для застосунку, подалі від стандартного /var/www/html:
sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testappПрава власності мають значення. В Ubuntu Apache і PHP-FPM працюють від імені користувача www-data, тому файли, які має читати вебсервер, і каталоги, до яких застосунок має записувати, наприклад каталог uploads, повинні належати www-data. Якщо ви також редагуватимете файли від імені свого користувача, поширений варіант — призначити себе власником файлів і додати свого користувача до групи www-data. Для простого розгортання найменше несподіванок дає www-data:www-data.
Створіть віртуальний хост у /etc/apache2/sites-available/testapp.conf:
<VirtualHost *:80>
ServerName app.example.com
DocumentRoot /var/www/testapp
<Directory /var/www/testapp>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/testapp-error.log
CustomLog ${APACHE_LOG_DIR}/testapp-access.log combined
</VirtualHost>Замініть ServerName на фактичне доменне ім’я. Options -Indexes забороняє Apache показувати вміст каталогу, якщо в ньому немає index-файлу. Інакше відвідувачі зможуть переглядати дерево вихідних файлів. AllowOverride All дає змогу використовувати файл .htaccess, якого очікує більшість PHP-застосунків для красивих URL. Якщо застосунок цього не потребує, замініть його на None, щоб трохи підвищити швидкодію. Увімкніть цей сайт, вимкніть стандартний, перевірте конфігурацію та перезавантажте Apache:
sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2Команда apache2ctl configtest має вивести Syntax OK. Рядок a2dissite 000-default часто забувають додати. Через це згодом може залишатися стандартна сторінка, як описано в розділі про типові помилки.
Крок 6 — Перевірте роботу PHP, а потім видаліть тестовий файл
Створіть однорядковий файл PHP у кореневому каталозі застосунку:
echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.phpВідкрийте http://app.example.com/info.php. Правильний результат — довга фіолетово-сіра таблиця PHP Version 8.3.x зі списком завантажених модулів, у якій у рядку Server API зазначено FPM/FastCGI. Цей останній рядок підтверджує, що запити обробляє PHP-FPM, а не mod_php.
Тепер негайно видаліть файл:
sudo rm /var/www/testapp/info.phpphpinfo() розкриває точну версію PHP, усі завантажені розширення, шляхи до файлів і відомості про середовище. Це може допомогти зловмиснику, який перевіряє сервер на наявність відомої вразливості. Це тест, а не функція. Видаліть файл одразу після перевірки сторінки. Якщо замість таблиці браузер запропонував завантажити info.php, PHP не підключено до Apache. Перш ніж робити щось інше, перейдіть до розділу про помилки.
Крок 7 — Створіть базу даних застосунку та користувача з мінімальними привілеями
Відкрийте базу даних як root, автентифікований через socket:
sudo mysqlПотім створіть одну базу даних і одного користувача, обмеженого саме цією базою:
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;Тут є три свідомі рішення. utf8mb4 — це справжній чотирибайтовий UTF-8, а старий псевдонім utf8 непомітно обрізає emoji та деякі символи CJK, тому завжди використовуйте utf8mb4. Привілей надано для appdb.*, а не для *.*: цей користувач може працювати лише зі своєю базою даних і не має доступу до інших, тому SQL injection у застосунку не дасть змоги прочитати таблиці інших сайтів. А 'appuser'@'localhost' обмежує обліковий запис підключеннями, що надходять із самого сервера.
Перевірте підключення від імені цього користувача:
mysql -u appuser -p appdbКоманда запросить пароль і відкриє запрошення MariaDB [appdb]>. Зверніть увагу: прапорця -h немає. Не додавайте його. Тоді клієнт підключиться через локальний Unix socket, який MariaDB саме так і визначає як localhost. Важливий нюанс: для MySQL і MariaDB localhost означає Unix socket, а 127.0.0.1 — TCP-підключення. У стандартній MariaDB на Ubuntu 24.04 сервер усе одно визначає TCP-підключення з 127.0.0.1 як підключення від localhost, тому обидва варіанти відповідають цьому обліковому запису. Але на серверах з увімкненим skip-name-resolve — це поширене оптимізаційне налаштування, а також стандартний варіант у багатьох образах контейнерів — ці два підключення зіставляються з різними хостами. Тому застосунок, який підключається до 127.0.0.1, отримає помилку ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) навіть за правильного пароля.
Тому вкажіть у застосунку хост localhost, користувача appuser і базу даних appdb. Не використовуйте root. PHP mysqli і PDO перемикаються на Unix socket, коли хостом є буквальний рядок localhost. Це відповідає щойно створеному обліковому запису. Якщо фреймворк вимагає числовий TCP-хост, створіть користувача відповідно до фактичного способу підключення: 'appuser'@'127.0.0.1' або @'%' — разом із правилом firewall — лише якщо базу даних справді потрібно підключати з іншої машини.
Крок 8 — Додайте HTTPS за допомогою Certbot
Передавання форми входу через звичайний HTTP надсилає паролі у відкритому вигляді, а всі сучасні браузери позначають таку сторінку як «Незахищено». Certbot усуває цю проблему однією командою. Встановіть його з плагіном для Apache:
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apacheТут Certbot використовує два плагіни. Модуль автентифікації apache підтверджує, що ви керуєте доменом, тимчасово публікуючи файл перевірки через запущений Apache. Потім модуль встановлення apache змінює віртуальний хост, додає блок 443, указує його на новий сертифікат і за замовчуванням перенаправляє весь HTTP-трафік на HTTPS. Починаючи з Certbot 2.0, запитання про перенаправлення не виводиться; передайте --no-redirect, якщо потрібно й надалі обслуговувати звичайний HTTP. Оскільки на кроці 5 ви вказали справжній ServerName, Certbot автоматично визначає домен. Сертифікати дійсні 90 днів, а пакет встановлює таймер systemd для їх поновлення. Перевірте таймер за допомогою sudo certbot renew --dry-run. Результат має завершуватися значенням Congratulations, all simulated renewals succeeded.
Повний опис перевірки, таймера поновлення та вимог до DNS і firewall наведено в супровідному посібнику як отримати безкоштовні TLS-сертифікати Let’s Encrypt за допомогою Certbot для Apache.
Резервні копії, оновлення та посилення захисту
Створюйте резервні копії двох компонентів, у яких зберігається стан системи: баз даних і web root. Найпростіший надійний підхід — щоночі створювати логічний дамп, sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', а потім копіювати його за межі сервера. Важливо обгорнути весь конвеєр у sudo sh -c: без цього shell виконує перенаправлення > /root/... від імені вашого користувача й завершується помилкою Permission denied, оскільки лише mysqldump успадкував sudo. --single-transaction створює узгоджений знімок таблиць InnoDB без їх блокування. Поєднайте це з tar для /var/www і /etc/apache2/sites-available, і ви зможете відновити весь стек на новому VPS із цих файлів.
Оновлення — звичайна sudo apt update && sudo apt upgrade. Найчастіше проблеми виникають під час оновлення версії PHP: коли майбутня версія Ubuntu змінить версію PHP за замовчуванням на PHP 8.4, apt може встановити php8.4-fpm паралельно з 8.3, socket зміниться на /run/php/php8.4-fpm.sock, а конфігурація Apache усе ще вказуватиме на socket версії 8.3. Увімкніть нову конфігурацію (sudo a2enconf php8.4-fpm) і вимкніть стару, інакше після звичайного оновлення сайт почне повертати Primary script unknown. Оскільки нові версії PHP випускаються швидше, ніж оновлюється LTS-дистрибутив, перевіряйте актуальні release notes PHP, а не фіксуйте patch version.
У перший день варто виконати два кроки для посилення захисту. По-перше, налаштуйте на сервері моніторинг SSH за допомогою Fail2Ban: публічний VPS за лічені хвилини починають сканувати автоматизовані спроби входу, а невеликий jail скорочує тисячі спроб до кількох перед блокуванням. По-друге, якщо ви хочете керувати віртуальними хостами Apache, базами даних MariaDB і користувачами через браузер, а не редагувати файли вручну, вебпанель керування Webmin працює поверх цього самого стека й змінює ті самі конфігураційні файли, які ви щойно створили. Жоден із цих інструментів не замінює розуміння окремих компонентів, але обидва спрощують щоденне адміністрування.
Типові несправності та повідомлення, які ви побачите
Сторінка за замовчуванням не зникає. Ви змінили віртуальний хост, перезавантажили конфігурацію, але браузер і далі показує "Apache2 Ubuntu Default Page" і банер "It works!". Apache обслуговує перший відповідний віртуальний хост. Якщо жоден ServerName не відповідає запиту, використовується конфігурація, яка першою стоїть в алфавітному порядку: 000-default.conf сортується перед testapp.conf. Або ім’я хоста в запиті не відповідає вашому ServerName, або ви не виконали sudo a2dissite 000-default. Вимкніть конфігурацію за замовчуванням за допомогою sudo systemctl reload apache2 і перевірте результат командою apache2ctl -S. Вона виводить карту віртуальних хостів і показує, яка конфігурація є типовою. Також очистьте кеш браузера: кешований код відповіді 200 зі старої сторінки може зберігатися.
Файл .php завантажується замість виконання. Ви відкриваєте info.php, а браузер завантажує файл із необробленим вихідним кодом <?php або показує його як звичайний текст. Apache обслуговує файл як статичний ресурс, оскільки обробник PHP не підключено, ви пропустили sudo a2enmod proxy_fcgi або sudo a2enconf php8.3-fpm, чи не перезапустили Apache після змін. Виконайте всі три команди (крок 4) і перезавантажте сторінку. Перевірте, що модуль завантажено, командою apache2ctl -M | grep fcgi. У результаті має бути proxy_fcgi_module. Це витік вихідного коду, а не косметична помилка, тому виправте її до розміщення на сервері реальних даних.
ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Ви виконали mysql -u root або mariadb -u root без sudo. Обліковий запис root використовує автентифікацію через unix_socket. Тому вхід дозволено лише тоді, коли ваш користувач ОС справді є root. Виконайте sudo mysql, без -u root і без пароля. Це очікувана поведінка коректно налаштованої socket-автентифікації, а не ознака несправного встановлення.
ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' із застосунку, використовуючи правильний пароль. Обліковий запис існує як 'appuser'@'localhost', але застосунок підключається через TCP до 127.0.0.1 на сервері, де вимкнено розпізнавання імен хостів (skip-name-resolve). Тому MariaDB розглядає ці два варіанти як різні хости: localhost — це Unix-сокет, а 127.0.0.1 — TCP. Задайте в застосунку хост localhost, щоб він використовував сокет і відповідав обліковому запису, або створіть другий обліковий запис 'appuser'@'127.0.0.1', якщо фреймворк підтримує лише TCP.
AH01071: Got error 'Primary script unknown' у /var/log/apache2/testapp-error.log, а браузер показує File not found.. Apache передав запит PHP-FPM, але FPM не знайшов скрипт за шляхом, який йому передав Apache. Зазвичай причина одна з двох: сокет FPM у конфігурації вказує на версію PHP, яку не встановлено (наприклад, сокет php8.4 залишився після оновлення, а працює лише версія 8.3), або самого файлу немає, оскільки DocumentRoot і фактичний каталог не збігаються. Перевірте наявність сокета командою ls -l /run/php/, переконайтеся, що DocumentRoot відповідає розташуванню файлу, і перезапустіть php8.3-fpm та apache2.
AH00558: apache2: Could not reliably determine the server's fully qualified domain name під час кожного перезапуску. Це нешкідливе попередження, а не помилка. Apache повідомляє, що глобальний ServerName не задано. Щоб прибрати попередження, запишіть ServerName your.domain у /etc/apache2/conf-available/servername.conf і виконайте sudo a2enconf servername.
(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 під час запуску Apache. Інший вебсервер уже використовує порт 80. Часто це залишений nginx після попереднього експерименту. Знайдіть процес командою sudo ss -ltnp | grep :80, потім зупиніть і вимкніть інший сервіс перед запуском Apache.
FAQ
mod_php чи PHP-FPM — що використовувати?
Використовуйте PHP-FPM. mod_php вбудовує інтерпретатор у кожен процес Apache і примусово вмикає повільний prefork MPM, тому Apache має накладні витрати PHP навіть під час обслуговування статичного зображення. PHP-FPM запускає PHP в окремому пулі з незалежними налаштуваннями, до якого Apache підключається через сокет. Він працює з швидшим потоковим event MPM і дає змогу без змін перейти на nginx пізніше. Це сучасний варіант за замовчуванням; mod_php має сенс лише для застарілого застосунку, який залежить від певної поведінки всередині процесу.
Чому браузер завантажує PHP-файл замість його виконання?
Apache обробляє файл .php як статичне завантаження, оскільки для нього не призначено обробник PHP. В Ubuntu 24.04 із FPM це означає, що ви пропустили один із кроків sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm або перезапуск Apache після них. Виконайте всі три кроки й перезавантажте конфігурацію, а потім перевірте за допомогою apache2ctl -M | grep fcgi, що proxy_fcgi_module зазначено у списку. Поки проблему не усунуто, сервер розкриває вихідний код, тому вважайте її терміновою.
Чому доступ root до MariaDB заборонено навіть із правильним паролем?
Тому що пароля немає: MariaDB в Ubuntu автентифікує обліковий запис root через unix_socket і пов’язує його з користувачем root операційної системи. mysql -u root зі звичайної оболонки навмисно повертає ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Натомість підключайтеся за допомогою sudo mysql і створіть окремого користувача з автентифікацією за паролем для кожного застосунку, а не повторно використовуйте root.
Як додати HTTPS для мого LAMP-сайту?
Установіть certbot і python3-certbot-apache, укажіть в A-записі домену IP-адресу сервера, а потім виконайте sudo certbot --apache. Аутентифікатор Apache підтверджує контроль над доменом через запущений Apache, а інсталятор змінює віртуальний хост для порту 443 і налаштовує автоматичне поновлення. У повному посібнику з Certbot і Apache описано перевірку, таймер поновлення та поширені причини збоїв.