Шаг 9 · Web · 30 минут

Несколько сайтов на одном VPS: Nginx и server_name

Один VPS вполне выдерживает несколько небольших проектов одновременно — если правильно развести их в конфигурации Nginx.

Когда одного сайта недостаточно

Ресурсов среднего VPS обычно хватает на 3-5 небольших проектов: блог, лендинг, панель мониторинга, тестовый стенд для клиента. Держать под каждый отдельный сервер бессмысленно — большую часть времени такие сайты простаивают, а не грузят процессор. Проблема не в мощности, а в организации: если конфиги Nginx свалены в один файл или названы как попало, рано или поздно один сайт случайно перекроет другой либо конфиг перестанет открываться вовсе.

Дальше предполагается, что базовая настройка Nginx и HTTPS для одного домена уже сделана — если ещё нет, сначала пройдите гайд по Nginx и HTTPS. Здесь мы просто размножаем ту же схему на несколько доменов и следим, чтобы они не мешали друг другу.

Структура конфигов

Правило простое: один сайт — один файл. Он живёт в /etc/nginx/sites-available/, а в /etc/nginx/sites-enabled/ кладётся только симлинк на него. Активными считаются лишь те конфиги, у которых есть симлинк — это удобно, чтобы временно «выключить» сайт, не удаляя его настройки.

sudo nano /etc/nginx/sites-available/site1.example.com
sudo nano /etc/nginx/sites-available/site2.example.com

sudo ln -s /etc/nginx/sites-available/site1.example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site2.example.com /etc/nginx/sites-enabled/

Называйте файлы по домену, а не «site1», «site2» — через полгода вы забудете, какой файл за что отвечает. Симлинк без флага -s создаст копию файла вместо ссылки, и правки в sites-available перестанут применяться — это частая причина «я поправил конфиг, а ничего не изменилось».

server_name и виртуальные хосты

Все сайты на сервере слушают одни и те же порты 80 и 443. Nginx понимает, какой конфиг отдавать конкретному запросу, по заголовку Host, который присылает браузер — за это отвечает директива server_name.

server {
  listen 80;
  server_name site1.example.com;
  root /var/www/site1;
  index index.html;
}

server {
  listen 80;
  server_name site2.example.com;
  proxy_pass http://127.0.0.1:3001;
}

Каждый блок server обслуживает свой домен: один отдаёт статические файлы, другой проксирует запрос на приложение. Если запрос пришёл с Host, которого нет ни в одном server_name, Nginx отдаёт его первому попавшемуся серверу в порядке чтения конфигов — обычно это не то, что вы хотите. Явно пометьте один сайт как сервер по умолчанию, чтобы поведение было предсказуемым.

server {
  listen 80 default_server;
  server_name _;
  return 444;
}

Такой блок с пустым server_name _ и return 444 тихо обрывает соединение для запросов без известного домена — например от сканеров, которые ходят по IP-адресу напрямую.

Поддомены vs отдельные домены

Технически Nginx не видит разницы между app1.example.com, app2.example.com и совершенно другим доменом — в обоих случаях это просто значение server_name. Разница — в организации проектов и в DNS.

Поддомены одного домена удобны, когда сайты логически связаны: основной сайт, панель администратора, API. Для них достаточно создать несколько A-записей у одного домена, указывающих на один и тот же IP VPS:

app1.example.com. A 203.0.113.10
app2.example.com. A 203.0.113.10
api.example.com. A 203.0.113.10

Отдельные домены имеют смысл, когда проекты никак не связаны — например личный сайт и сайт клиента. Тогда у каждого домена своя A-запись, но конечная точка та же самая:

site-one.ru. A 203.0.113.10
site-two.com. A 203.0.113.10

В обоих случаях в Nginx просто появляется ещё один блок server с соответствующим server_name — разницы в конфигурации нет, разница в том, как вы объясняете структуру самому себе через полгода.

HTTPS для каждого сайта

Let's Encrypt не ограничивает число сертификатов на один сервер — на одном IP спокойно уживаются десятки независимых сертификатов, по одному на домен (или один на группу доменов, если явно перечислить их через несколько -d). certbot запускается отдельно для каждого сайта:

sudo certbot --nginx -d site1.example.com
sudo certbot --nginx -d site2.example.com
sudo certbot --nginx -d app1.example.com -d app2.example.com

certbot сам найдёт нужный блок server по server_name, допишет в него listen 443 ssl, пути к сертификату и редирект с 80 на 443. Проверить, какие домены уже покрыты сертификатами, можно так:

sudo certbot certificates

Автопродление одно на весь сервер — отдельный systemd-таймер обновляет все выпущенные сертификаты разом, ничего дополнительно настраивать для каждого нового сайта не нужно:

sudo systemctl status certbot.timer

Изоляция проектов

Когда сайтов несколько, важно, чтобы падение одного не утаскивало за собой остальные. Nginx перед ними в любом случае устоит — сам веб-сервер не падает от ошибки одного проксируемого приложения, — но стоит подстраховаться и на уровне самих приложений.

Простой и надёжный вариант — разносить проекты по портам upstream-приложений. Каждое приложение (свой Docker Compose проект или systemd-сервис) слушает только localhost на своём порту, а Nginx проксирует к нему трафик по домену:

Проект 1: 127.0.0.1:3001
Проект 2: 127.0.0.1:3002
Проект 3: 127.0.0.1:3003
server {
  listen 443 ssl;
  server_name app1.example.com;
  location / {
    proxy_pass http://127.0.0.1:3001;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

Приложения при этом не видят друг друга и не могут случайно обратиться не в свою базу данных или занять чужой порт. Дополнительно стоит держать каждый проект в своей папке (например /srv/app1, /srv/app2) и, если сервер обслуживает разных клиентов, заводить под каждый проект отдельного системного пользователя — тогда утечка в одном приложении не даёт доступа к файлам остальных.

Проверка конфигурации

Перед любым применением изменений Nginx проверяет синтаксис конфигов, но не проверяет его автоматически — это нужно делать вручную командой nginx -t:

sudo nginx -t

Если проверка прошла успешно, применяйте изменения через reload, а не restart. Reload перечитывает конфиги и подхватывает новые сайты без разрыва уже открытых соединений на других сайтах — restart на секунду обрывает все сервисы на сервере, включая те, которые вы не трогали.

sudo systemctl reload nginx
Всегда выполняйте nginx -t перед reload или restart. Один синтаксически неверный конфиг — например забытая точка с запятой в блоке одного сайта — не даст Nginx запуститься вообще, и это положит сразу все сайты на сервере, а не только тот, где была ошибка.

Типовые ошибки

  • Два блока с default_server на одном порту — Nginx откажется стартовать, пока не останется только один.
  • Забытый симлинк в sites-enabled — конфиг лежит в sites-available, но не активен, и правки в нём как будто ничего не меняют.
  • Сертификат выписан не на тот домен или без нужного поддомена — certbot подставит его не в тот блок server, и HTTPS у сайта не заработает.
  • Совпадающий порт upstream-приложения у двух разных проектов — второе приложение либо не запускается, либо трафик неожиданно уходит не туда.
  • Общий server_name у двух конфигов сразу — Nginx использует только первый найденный блок, а второй сайт становится недостижим.

Что дальше

Несколько открытых в интернет сайтов на одном сервере — это уже несколько точек входа для атак, поэтому часть сервисов разумно вообще спрятать от посторонних глаз: настроим VPN, чтобы часть сервисов оставалась вообще недоступна из открытого интернета →