Віртуальний хост Nginx — це блок server, який пов'язує доменне ім'я, корінь сайту та обробник PHP в один конфіг. На VDS з декількома сайтами саме ці блоки визначають, який запит куди потрапить.
Налаштування віртуального хоста Nginx
Створіть окремий файл конфігурації для сайту — не редагуйте default напряму, якщо сайтів буде більше одного:
nano /etc/nginx/sites-available/yourdomain.com
Вміст файлу — мінімальний робочий блок для сайту на PHP:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Активуйте конфігурацію симлінком у sites-enabled — Nginx читає конфіги саме з цієї папки, а sites-available служить лише сховищем:
ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Декілька сайтів на одній IP
Nginx обирає потрібний блок за заголовком Host із запиту, звіряючи його зі списком у server_name. Якщо жоден блок не збігся, спрацьовує блок із default_server у директиві listen — без нього перший за порядком читання файлів конфіг стає дефолтним неявно, що іноді призводить до несподіваного результату під час додавання нового сайту.
server {
listen 80 default_server;
server_name _;
return 444;
}
Такий блок-заглушка з return 444; обриває з'єднання для запитів із будь-яким доменом, який не налаштовано явно, — корисний захист від ботів, що сканують IP-адреси напряму, в обхід DNS.
Gzip-стиснення відповідей
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_min_length відсікає стиснення зовсім маленьких файлів, де виграш менший за накладні витрати на пакування, а gzip_comp_level у діапазоні 5–6 — розумний баланс між економією трафіку і навантаженням на CPU; рівень 9 майже не виграє в розмірі, але помітно дорожчий по процесору.
Кешування статики браузером
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
Розбір типових помилок
Помилка nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) означає, що порт 80 уже зайнято іншим процесом — найчастіше це Apache, що лишився увімкненим, або другий запущений екземпляр Nginx. Перевірте зайняті порти командою ss -tulpn | grep :80 і зупиніть зайвий процес.
502 Bad Gateway — Nginx не отримав відповіді від бекенду, вказаного в fastcgi_pass чи proxy_pass. Точна причина завжди в /var/log/nginx/error.log: рядок connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) вказує на права доступу до сокета, а (111: Connection refused) — на те, що PHP-FPM просто не запущено.
404 на всіх сторінках, крім головної у проєктах із «красивими» URL зазвичай означає, що директива try_files не налаштована на передачу запиту в index.php — Nginx чесно шукає файл на диску з іменем маршруту і не знаходить його.
Порівняння способів маршрутизації в Nginx
| Підхід | Де задається | Гнучкість | Типовий випадок |
|---|---|---|---|
| try_files | У location / | Середня, порядок фіксований | Віддача статики з фолбеком на index.php |
| rewrite | У server чи location | Висока, регулярні вирази | Старі посилання, ЧПУ без фреймворку |
| return | У server чи location | Низька, пряма відповідь | Редиректи, обрив з'єднання (444) |
| proxy_pass | У location | Висока, повний проброс на бекенд | Node.js, Python, зворотний проксі |
Глибший розбір пріоритету типів location і того, який блок спрацює за перетину правил, — у статті про пріоритет location у Nginx (там же розібрано приклади конфліктів конфігурації).
Коли голого Nginx-конфіга недостатньо
- Сайту потрібен HTTPS — сертифікат і редирект з HTTP додаються окремо, це описано в статті про встановлення SSL через Certbot.
- Багато однакових сайтів на одному сервері — замість копіювання конфіга під кожен домен зручніше винести спільні блоки в
include-файли. - Потрібен захист від перебору чи DDoS на рівні запитів — тут звичайний
server-блок не допоможе, потрібніlimit_req,limit_connчи зовнішній WAF. - Бекенд — не PHP-FPM, а окремий застосунок на своєму порту (Node.js, Python, Go) — замість
fastcgi_passвикористовуєтьсяproxy_passз окремим набором заголовків.
Перевірка результату
- Перевіряйте синтаксис перед кожною зміною:
nginx -tмає повернутиsyntax is okіtest is successful. - Відкрийте сайт за доменом і за IP окремо — якщо за доменом працює, а за IP видно чужий сайт, отже правильно налаштовано
server_name, а якщо за IP видно цей самий сайт безdefault_server, варто це перевірити. - Перевірте заголовки відповіді:
curl -I http://yourdomain.com— заголовокContent-Encoding: gzipпідтвердить, що стиснення реально працює, а не просто увімкнене в конфігу. - Перегляньте access-лог на предмет 404 і 502 одразу після деплою:
tail -f /var/log/nginx/access.logпокаже реальні запити користувачів, а не лише тестові з термінала.