Настройка Nginx server blocks для нескольких проектов на одном VPS
Практический гайд: структура sites-available, один конфиг с SSL, HTTP→HTTPS, HSTS, OCSP stapling, отдельные логи на сайт. Типичная 404, reload vs restart, готовый конфиг с комментариями.
Требования
- VPS с Nginx (Ubuntu/Debian или CentOS/Rocky)
- Доступ по SSH, права на редактирование конфигов
- Сертификаты SSL (Let's Encrypt или свои)
Настройка Nginx server blocks для нескольких проектов на одном VPS
Один VPS, несколько доменов — каждый проект в своём server block: отдельный root, свои логи, HTTPS с редиректом, HSTS и OCSP stapling. Ниже — структура каталогов конфигов, полный пример конфига с комментариями, типичная ошибка 404 и когда использовать reload, а когда restart.
Структура /etc/nginx/sites-available
На Ubuntu/Debian конфиги сайтов хранят в sites-available, активные — симлинки в sites-enabled. Так удобно включать и отключать сайты без удаления файлов.
/etc/nginx/
├── nginx.conf
├── sites-available/
│ ├── default # default_server, редирект или 444
│ ├── site1.example.com.conf
│ ├── site2.example.com.conf
│ └── api.site3.com.conf
└── sites-enabled/
├── default -> ../sites-available/default
├── site1.example.com.conf -> ../sites-available/site1.example.com.conf
└── site2.example.com.conf -> ../sites-available/site2.example.com.conf
В nginx.conf в блоке http должен быть:
include /etc/nginx/sites-enabled/*;
Включение и отключение сайта:
# Включить
sudo ln -sf /etc/nginx/sites-available/site1.example.com.conf /etc/nginx/sites-enabled/
# Отключить
sudo rm /etc/nginx/sites-enabled/site1.example.com.conf
# Проверка и применение
sudo nginx -t && sudo systemctl reload nginx
На CentOS/Rocky обычно используют /etc/nginx/conf.d/: один файл — один сайт, отключение через переименование (например, .conf.disabled) или удаление файла.
Реальный конфиг с комментариями (один сайт, SSL, редирект, HSTS, OCSP, логи)
Один файл на сайт, например /etc/nginx/sites-available/site1.example.com.conf:
# Редирект HTTP -> HTTPS
server {
listen 80;
listen [::]:80;
server_name site1.example.com www.site1.example.com;
# Отдельные логи для этого виртуального хоста
access_log /var/log/nginx/site1.example.com.access.log;
error_log /var/log/nginx/site1.example.com.error.log;
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name site1.example.com www.site1.example.com;
# SSL: сертификаты (Let's Encrypt или свои)
ssl_certificate /etc/letsencrypt/live/site1.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/site1.example.com/privkey.pem;
# Современные протоколы и шифры
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# OCSP stapling — браузер не ходит к CA за статусом сертификата
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/site1.example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# HSTS — браузер год ходит только по HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Логи только для этого сайта
access_log /var/log/nginx/site1.example.com.access.log;
error_log /var/log/nginx/site1.example.com.error.log;
root /var/www/site1.example.com/public;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2)$ {
expires 30d;
access_log off;
}
}
Кратко по блокам:
- Первый server (80) — редирект всего HTTP на HTTPS с сохранением хоста и URI; логи пишутся в отдельные файлы по домену.
- Второй server (443) — SSL-ключ и сертификат, протоколы TLS 1.2/1.3, OCSP stapling и HSTS; те же отдельные логи;
rootнаpublic, PHP через FPM (если не нужен PHP — убери блокlocation ~ \.php$и поправьtry_files).
Пути к сертификатам замени на свои (Let’s Encrypt: fullchain.pem, privkey.pem, chain.pem). Для второго и третьего сайта копируй этот файл, меняй server_name, пути к SSL и root, и имена логов.
HTTP → HTTPS редирект
Отдельный server block на порту 80 только редиректит:
server {
listen 80;
listen [::]:80;
server_name site1.example.com www.site1.example.com;
access_log /var/log/nginx/site1.example.com.access.log;
error_log /var/log/nginx/site1.example.com.error.log;
location / {
return 301 https://$host$request_uri;
}
}
$host — значение заголовка Host, $request_uri — URI с query string. Итог: любой запрос по HTTP уходит на тот же путь по HTTPS.
HSTS
Заголовок говорит браузеру «год ходить на этот домен только по HTTPS»:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
max-age=31536000— 1 год.includeSubDomains— правило для поддоменов.preload— можно подать сайт в список preload браузеров.always— добавлять заголовок и в ответах с кодом 4xx/5xx (по умолчанию в таких ответах Nginx его не ставит).
Добавляй только когда уверен, что HTTPS везде и надолго.
OCSP stapling
Браузер проверяет валидность сертификата по OCSP. Без stapling он сам ходит к серверу CA; со stapling Nginx сам получает ответ OCSP и отдаёт его в handshake — быстрее и меньше утечек по посещениям.
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/site1.example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
ssl_trusted_certificate — цепочка до корня (у Let’s Encrypt подойдёт chain.pem). resolver нужен Nginx для запроса OCSP по имени. После смены сертификата сделай systemctl reload nginx.
Отдельный лог для каждого сайта
Не пиши все сайты в один access.log/error.log. В каждом server block задавай свои файлы:
server {
listen 443 ssl http2;
server_name site1.example.com;
access_log /var/log/nginx/site1.example.com.access.log;
error_log /var/log/nginx/site1.example.com.error.log;
# ...
}
server {
listen 443 ssl http2;
server_name site2.example.com;
access_log /var/log/nginx/site2.example.com.access.log;
error_log /var/log/nginx/site2.example.com.error.log;
# ...
}
Так проще искать ошибки и анализировать трафик по проекту. Права: Nginx должен иметь право создавать/писать в эти файлы (обычно пользователь nginx или www-data).
Типичная ошибка 404
Частая причина — несовпадение root и реального пути или неправильный try_files/index.
Пример 1: root без public, запрос к директории
root /var/www/site1.example.com; # в каталоге лежит public/
index index.html;
location / {
try_files $uri $uri/ =404;
}
Запрос / → Nginx ищет /var/www/site1.example.com/index.html. Если index.html лежит в public/, его нет по этому пути — 404. Решение: указать root на каталог с index.html, например root /var/www/site1.example.com/public;.
Пример 2: SPA или Laravel — всё на index
Если один входной файл (например, index.html или index.php), нужно отдавать его для ненайденных путей:
root /var/www/site1.example.com/public;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA
}
# или для Laravel/PHP:
# try_files $uri $uri/ /index.php?$query_string;
Иначе запрос к /about даёт 404, потому что файла about нет. Кратко: проверь, что root указывает туда, где реально лежат файлы, и что try_files в конце ведёт на нужный index.
systemctl reload vs restart
reload (systemctl reload nginx)
Nginx перечитывает конфиг и применяет его без обрыва соединений: новые worker’ы поднимаются с новой конфигурацией, старые дорабатывают текущие запросы и завершаются. Подходит после правки server blocks, добавления сайтов, смены SSL.
restart (systemctl restart nginx)
Процесс полностью перезапускается. Все активные соединения обрываются. Нужен только если reload не срабатывает или после смены глобальных настроек (например, в nginx.conf что-то, что не подхватывается при reload). Обычно достаточно:
sudo nginx -t && sudo systemctl reload nginx
После изменения конфигов всегда делай nginx -t; при ошибке Nginx не будет применять конфиг даже при reload.
Default server (защита от запросов по IP и левых Host)
Чтобы запросы по IP или с неизвестным Host не попадали в первый попавшийся сайт, задай явный default для портов 80 и 443. Например, в sites-available/default:
# Порт 80 — редирект или закрытие
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
access_log off;
return 444;
}
# Порт 443 — закрытие без сертификата
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/default-dummy.crt;
ssl_certificate_key /etc/nginx/ssl/default-dummy.key;
access_log off;
return 444;
}
return 444 — соединение закрывается без ответа. Для 443 нужен любой валидный сертификат (можно сгенерировать самоподписанный «заглушку»). Тогда только запросы с известными server_name обрабатываются твоими сайтами.
Чеклист по одному сайту на VPS
- Файл конфига в
sites-available/, симлинк вsites-enabled/(Ubuntu/Debian). - Отдельные
access_logиerror_logна сайт. - HTTP → HTTPS редирект (отдельный server на 80).
- SSL: корректные пути к
fullchain.pemиprivkey.pem. - HSTS и OCSP stapling в HTTPS server block.
-
rootуказывает на каталог, где лежитindex;try_filesведёт на нужный index. -
nginx -tбез ошибок, затемsystemctl reload nginx. - Default server настроен для 80 и 443.
Итог
На одном VPS несколько проектов разводятся по разным server blocks: свой server_name, свой root, свои логи. Для production добавь в каждый HTTPS-блок редирект с 80, SSL, HSTS и OCSP stapling. Храни конфиги в sites-available, включай через sites-enabled, после правок проверяй nginx -t и делай reload. Так структура остаётся понятной и безопасной при росте числа сайтов.



Комментарии