1. 1
  2. 2
  3. 3
  4. 4

# теория · шаг 1 из 4

Ставим nginx и открываем первую страницу

Коротко

Рабочий nginx нужен под рукой с самого начала - контейнером или пакетом, пять минут в обе стороны.

  • Свой сайт кладётся ОТДЕЛЬНЫМ файлом в conf.d, чужие файлы не правятся.
  • Ежедневный набор: nginx -t (проверить), nginx -s reload (применить), nginx -T (посмотреть собранный конфиг целиком).
  • Проверять результат надёжнее curl -i, а не браузером: браузер кеширует и подставляет своё.
  • Две ловушки первого дня: duplicate default server из-за дефолтного файла Debian и 403 из-за прав на КАТАЛОГИ пути, а не на сам файл.

Если nginx уже стоит и nginx -t знаком - листай до «Теперь сам».

Конфиг положил, а страница старая

Типичная первая минута: человек кладёт свой server в /etc/nginx/conf.d/Разбирается в разделе 1, глава «Конфиг: где лежит и как не уронить прод»: Где живёт конфиг - каталог, куда nginx сам заглядывает за чужими кусками конфига, - обновляет браузер и видит стандартную страницу «Welcome to nginx».

Что из этого следует? Ровно ничего однозначного - и в этом проблема. Такую картинку дают четыре разные причины: конфиг не перечитан, файл лежит не там, где nginx его ищет, ответил другой server-блок, или браузер показывает свою копию страницы. Дальше в уроке - как отличать их за секунды, а не перебором.

Способ первый: контейнер

Годится всем и ничего не ломает в системе. Docker - программа, которая запускает готовый образ в отдельной песочнице: nginx в системе не появится, а удалить всё можно одной командой. Проверить, стоит ли он: docker --version. Если команды нет - либо поставь его по инструкции с docs.docker.com, либо иди вторым способом, дальше по уроку они равноценны.

$  команда
mkdir -p ~/nginx-lab/conf.d ~/nginx-lab/www
echo '<h1>привет из nginx</h1>' > ~/nginx-lab/www/index.html
$  команда
docker run -d --rm --name lab -p 8080:80 \
  -v ~/nginx-lab/conf.d:/etc/nginx/conf.d:ro \
  -v ~/nginx-lab/www:/var/www/site:ro \
  nginx:1.31-alpine

Строка читается так. -d - запустить в фоне и вернуть терминал; без него консоль будет занята выводом nginx, и следующую команду набрать станет некуда. -p 8080:80 - слева порт на твоей машине, справа порт внутри контейнера. -v путь_снаружи:путь_внутри:ro подставляет твой каталог внутрь контейнера, ro - только для чтения. nginx:1.31-alpine - имя образа и версия; если его нет локально, Docker скачает сам. Логи смотреть docker logs -f lab, убрать всё - docker rm -f lab.

Открывай http://localhost:8080 - увидишь стандартную страницу nginx. Теперь добавь свой сайт:

~/nginx-lab/conf.d/lab.confhttp
server {
    listen 80 default_server;
    server_name _;

    root /var/www/site;
    index index.html;
}

Файл лежит в примонтированной папке, значит внутри контейнера он уже есть. Про то, как эта запись устроена - почему фигурные скобки, зачем точка с запятой и значит ли что-нибудь отступ, - есть отдельный урокРазбирается в разделе 2, глава «Директивы, блоки, контексты»: Весь синтаксис за один урок; сейчас достаточно скопировать. Две строки внутри всё же назову: default_server помечает блок как «отвечает, когда имя не совпало ни с чьим», а server_name _ - это условная заглушка вместо доменного имени, потому что домена у стенда пока нет. Обе разбираются в главе про сервер по умолчаниюРазбирается в разделе 3, глава «default_server и чужие запросы»: Кто ответит на неизвестное имя. Осталось сказать nginx перечитать конфиг:

$  команда
docker exec lab nginx -t && docker exec lab nginx -s reload

Обнови страницу - на ней твой заголовок. Это уже полноценный веб-сервер: тот же бинарник и тот же конфиг, что на проде.

Почему монтируем каталог, а не копируем файл

Правишь файл обычным редактором на своей машине, в контейнер копировать нечего. Пересоздавать контейнер после каждой правки тоже не надо: reload применяет конфиг, не роняя сервер.

Способ второй: пакет в системе

Если под рукой сервер или виртуалка - ставится одной строкой.

$  команда
sudo apt install nginx        # Debian, Ubuntu
sudo dnf install nginx        # Fedora, RHEL, Rocky

После установки nginx уже запущен и слушает 80-й порт:

$  команда
systemctl status nginx
curl -I http://localhost

Каталог сайта надо создать - в отличие от способа с контейнером, где он приехал из твоей домашней папки:

$  команда
sudo mkdir -p /var/www/site
echo '<h1>привет из nginx</h1>' | sudo tee /var/www/site/index.html

Свой сайт кладётся отдельным файлом - и не трогая ничего вокруг:

/etc/nginx/conf.d/lab.confhttp
server {
    listen 80 default_server;
    server_name _;

    root /var/www/site;
    index index.html;
}
$  команда
sudo nginx -t && sudo systemctl reload nginx

Механизм: откуда nginx собирает конфиг

Файл, который читает nginx, ровно один - /etc/nginx/nginx.conf. Всё остальное попадает туда через include, и понимать это дерево полезно с первого дня: половина вопросов «почему не применилось» - про то, что файл лежит вне дерева.

/etc/nginx/nginx.conf conf.d/*.conf sites-enabled/* твой файл lab.conf включается всегда только в Debian и Ubuntu сюда кладём свой server nginx -T печатает всё это дерево уже собранным в один текст
Файл вне дерева включений не читается вовсе, и ошибки при этом не будет никакой - страница просто останется прежней.

Правило

Свой сайт - отдельным файлом в conf.d, чужие файлы не правим. Дефолтные конфиги перезаписываются при обновлении пакета, и правка в них исчезнет молча.

nginx -t перед каждым reload. Битый конфиг nginx не применит, но узнать об этом лучше до, а не после.

nginx -T при любом «почему не применилось». Он печатает собранный конфиг целиком, со всеми include, - то есть ровно то, что nginx видит на самом деле.

Разбор: duplicate default server

Ставим nginx из пакета на Debian, кладём свой lab.conf с listen 80 default_server, проверяем:

$  команда
sudo nginx -t
↳  выводnginx -t
nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/conf.d/lab.conf:2
nginx: configuration file /etc/nginx/nginx.conf test failed

Читаем сообщение буквально: дубликат сервера по умолчанию для пары 0.0.0.0:80. Значит такой уже есть, и это не наш. Ищем:

$  команда
grep -rn default_server /etc/nginx/
↳  выводgrep -rn default_server /etc/nginx/
/etc/nginx/sites-enabled/default:12:      listen 80 default_server;
/etc/nginx/conf.d/lab.conf:2:      listen 80 default_server;

В сборках Debian и Ubuntu рядом лежит sites-enabled/default с тем же признаком. Признак default_server означает «этот блок отвечает, когда имя не совпало ни с одним другим», и такой блок на пару адрес-порт может быть только один.

Лечится удалением ссылки, а не правкой чужого файла:

$  команда
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

Обрати внимание: nginx -t назвал файл и строку. Это общее свойство его диагностики, и оно экономит больше времени, чем любые догадки.

Разбор: 403 и права на путь целиком

Конфиг применился, а вместо страницы 403 Forbidden. Смотрим в лог ошибок - всегда туда, а не в конфиг:

$  команда
sudo tail -n 3 /var/log/nginx/error.log
↳  выводtail -n 3 /var/log/nginx/error.log
2026/09/02 21:14:07 [error] 1240#1240: *3 open() "/home/evgen/site/index.html" failed
(13: Permission denied), client: 127.0.0.1, server: _, request: "GET / HTTP/1.1"

Первая мысль - выдать права на файл. Она не сработает, и вот почему: чтобы открыть файл, процессу нужно право на ВХОД в каждый каталог по пути, от корня до последнего. Смотрим всю цепочку разом:

$  команда
namei -l /home/evgen/site/index.html
↳  выводnamei -l /home/evgen/site/index.html
drwxr-xr-x root  root  /
drwxr-xr-x root  root  home
drwx------ evgen evgen evgen      ← вот здесь
drwxr-xr-x evgen evgen site
-rw-r--r-- evgen evgen index.html

Домашний каталог закрыт от всех, кроме владельца, а воркеры nginx работают под www-data - это служебный пользователь, заведённый пакетом специально для веб-сервера: без пароля, без домашнего каталога и без права входить в систему. Для файловой системы он такой же «чужой», как случайный человек.

Столбец слева читается тройками: владелец, группа, все остальные. У строки drwx------ первая буква d значит «каталог», дальше rwx владельцу и по три прочерка группе и остальным. Ровно на этой строке www-data и упирается. Файл при этом читается всеми - и это ничего не даёт.

Отсюда два честных выхода: держать сайты в /var/www (каталог для того и существует) либо открыть на вход именно каталоги пути:

$  команда
sudo chmod o+x /home/evgen

o+x читается как «остальным (o) добавить (+) право на вход в каталог (x)». Для каталога x - это не «выполнить», а «пройти насквозь»: содержимое такой каталог не покажет, но открыть файл по известному пути внутри разрешит.

Первый способ лучше: домашний каталог не должен становиться проходным двором ради одной страницы.

Что ломается без этого

«Правлю конфиг, ничего не меняется». Файл вне дерева включений, или забыт reload. Проверяется одной командой nginx -T | grep свой_домен: если строки там нет, nginx твой файл не видит.

«Работало на моей машине, на сервере 403». Разные пути и разные владельцы каталогов. namei -l показывает всю цепочку за один раз.

«Браузер показывает старое». Кеш. Поэтому проверяем curl -i, а не браузером: он покажет ровно то, что отдал сервер, и покажет заголовки.

Зачем это в работе

Три команды - nginx -t, nginx -s reload, nginx -T - это весь ежедневный набор администратора, и почти любая правка конфига проходит через них в этом порядке. Привычка запускать -t до reload стоит одной секунды и снимает целый класс инцидентов: битый конфиг просто не применяется.

Второй случай - чужой сервер, который надо разобрать за пять минут. nginx -T даёт весь конфиг одним текстом, независимо от того, в какие пятнадцать файлов его разложил предыдущий администратор.

Три команды и два лога

$  команда
nginx -t          # проверить синтаксис, ничего не применяя
nginx -s reload   # применить конфиг без разрыва соединений
nginx -T          # напечатать собранный конфиг целиком

В способе с пакетом выше стоит systemctl reload nginx, а здесь - nginx -s reload. Разница в том, кто передаёт сигнал: во втором случае это делает сам nginx, в первом - служба systemd, которая под капотом вызывает то же самое. На системе со службой привычнее systemctl: он заодно запишет событие в журнал. В контейнере systemd нет вовсе, поэтому там остаётся nginx -s reload.

$  команда
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

В контейнере логи по умолчанию уходят в вывод самого контейнера, так что там это docker logs -f lab.

Проверять результат надёжнее curl, а не браузером:

$  команда
curl -i http://localhost:8080/
↳  выводcurl -i http://localhost:8080/
HTTP/1.1 200 OK
Server: nginx/1.31.5
Content-Type: text/html
Content-Length: 33
Last-Modified: Wed, 09 Sep 2026 19:02:46 GMT
Connection: keep-alive
ETag: "6aa1ad56-21"
Accept-Ranges: bytes

<h1>привет из nginx</h1>

Флаг -i показывает заголовки вместе с телом - тело идёт после пустой строки, и в нём ровно то, что ты положил в index.html. -I - только заголовки, -H 'Host: shop.local' позволяет притвориться другим доменом. Этими тремя приёмами проверяется примерно всё, что будет дальше в курсе.

Тридцать три байта, а не двадцать три. Строка <h1>привет из nginx</h1> кажется короче, но русские буквы весят по два байта, плюс перевод строки от echo. Это первый случай в курсе, когда число в ответе не сходится с интуицией; сверять его надо командой wc -c, а не на глаз.

Чтобы имя вроде shop.local открывалось и в браузере, а не только в curl, допиши строку 127.0.0.1 shop.local в /etc/hosts - своей машины это касается так же, как сервера.

Теперь сам

После правки конфига nginx -t молчит и говорит syntax is ok, reload проходит, а сайт по-прежнему отдаёт стандартную страницу nginx. Какие три проверки сделать и в каком порядке?

Ответ. Первая: nginx -T | grep -n server_name - виден ли твой блок в собранном конфиге. Если его там нет, файл лежит вне дерева включений, и всё остальное неважно. Вторая: curl -i -H 'Host: shop.local' http://localhost/ - запрос с нужным именем и без браузерного кеша; если ответ верный, дело было в браузере. Третья: tail /var/log/nginx/error.log - если ответ всё ещё чужой, значит запрос попал в другой server-блок, и это уже разговор про выбор блока, о котором будет отдельная глава. Порядок именно такой: сначала «видит ли nginx мой конфиг», потом «дошёл ли до него запрос», и только потом «почему ответил не тот».

Главное

Рабочий nginx под рукой - контейнером или пакетом, свой сайт отдельным файлом в conf.d. Ежедневный набор: nginx -t перед каждым reload, nginx -T при любом «почему не применилось». Ответ проверять curl -i. Две ловушки первого дня - duplicate default server из-за дефолтного файла Debian и 403 из-за прав на каталоги пути, а не на сам файл.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.