С чего начать Первые шаги трек · 15 ступеней
Linux Labs

nginx — веб-сервер и обратный прокси

11 мин Ubuntu 24.04 · nginx 1.24

nginx отдаёт сайты с диска и пересылает запросы приложениям; сайт выбирает по Host, конфиг проверяет nginx -t.

Шпаргалка

8 команд Версия для печати
  • sudo nginx -t проверить конфиг (только с sudo)
  • sudo systemctl reload nginx применить без обрыва соединений
  • sudo nginx -T | grep -n server_name какие сайты видит nginx
  • ls -l /etc/nginx/sites-enabled/ включённые сайты
  • curl -i -H 'Host: docs.lab.test' http://127.0.0.1/ кто ответит
  • sudo tail -n 5 /var/log/nginx/error.log почему 404, 403, 502
  • sudo tail -n 5 /var/log/nginx/access.log что спрашивали и код
  • sudo ss -tlnp | grep nginx на каких портах слушает

Обзор

nginx — веб-сервер: отдаёт файлы сайта с диска и работает обратным прокси — пересылает запрос приложению и возвращает его ответ клиенту. Служба nginx под systemd: главный процесс от root читает конфиг, рабочие процессы от www-data обслуживают запросы. Всё, что nginx открывает на диске или в сокете, проверяется правами www-data.

Как nginx выбирает сайт

Каждый сайт — блок server. Запрос сначала попадает к блокам, которые слушают его адрес и порт (listen), потом nginx сравнивает заголовок Host с server_name этих блоков.

nginx
server {
    listen 80;
    server_name docs.lab.test;
    root /var/www/docs;
}

Совпало имя — отвечает этот блок. Не совпало ни одно — отвечает сервер по умолчанию: блок с listen 80 default_server, а без такой пометки — первый блок для этого порта. В Ubuntu сервер по умолчанию на 80 — сайт default со страницей «Welcome to nginx!». Поэтому опечатка в имени или забытый сайт дают не ошибку, а чужую страницу.

Путь файла: root и location

Файл nginx ищет так: root + путь из запроса. При root /var/www/docs запрос /guide/a.html — это файл /var/www/docs/guide/a.html. На запрос каталога (/) отдаётся файл из списка index (по умолчанию index.html).

Блок location задаёт правила для части путей. Из префиксов, с которых начинается путь, выигрывает самый длинный: location /api/ перебивает location / для /api/users.

Директива Путь для запроса /files/x.txt
location /files/ { root /srv/data; } /srv/data/files/x.txt
location /files/ { alias /srv/data/; } /srv/data/x.txt

root дописывает путь целиком, alias заменяет совпавший префикс.

Прокси: proxy_pass

nginx
location / {
    proxy_pass http://127.0.0.1:3000;
}

nginx открывает соединение к приложению — бэкенду — и пересылает запрос. Не дозвонился — клиент получает 502 Bad Gateway, не дождался ответа — 504 Gateway Time-out. Это ответы самого nginx: в журнал приложения такие запросы не попадают. Бэкенд может слушать и unix-сокет: proxy_pass http://unix:/run/app/app.sock;.

Конфигурация

17

Раскладка Debian и Ubuntu:

  • /etc/nginx/nginx.conf — общие настройки, внизу include сайтов;
  • /etc/nginx/sites-available/ — файлы сайтов;
  • /etc/nginx/sites-enabled/ — ссылки на включённые сайты; nginx читает только их;
  • /var/log/nginx/access.log и error.log — журналы.

Включить сайт

bash
sudo ln -s /etc/nginx/sites-available/docs /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Файл в sites-available без ссылки в sites-enabled nginx не видит. Выключить сайт — удалить ссылку, файл остаётся.

nginx -t проверяет весь конфиг и называет файл и строку ошибки. reload перечитывает конфиг без обрыва соединений; с ошибкой в конфиге nginx оставляет старый. Команда systemctl reload возвращается сразу, а новые рабочие процессы берут соединения чуть позже: curl в той же строке может увидеть старое.

Журналы

access.log — строка на каждый запрос, error.log — почему запрос не удался и что сломалось при старте. Формат access.log по умолчанию — combined:

text
127.0.0.1 - - [20/Oct/2026:10:03:58 +0000] "POST / HTTP/1.1" 405 166 "-"

По полям: $remote_addr (адрес клиента), -, $remote_user, [$time_local], "$request" (метод, путь, версия), $status (код), $body_bytes_sent (размер тела), "$http_referer", "$http_user_agent" — в примере user-agent "curl/8.5.0" не поместился. Свой формат задаёт log_format — в файле сайта вне блока server:

nginx
log_format timed '$remote_addr [$time_local] "$request" '
                 '$status $body_bytes_sent $request_time';
access_log /var/log/nginx/app.log timed;

$request_time — сколько секунд nginx обрабатывал запрос. Файлы с .1 и .2.gz рядом с журналом — прошлые, их по расписанию отодвигает и сжимает logrotate (/etc/logrotate.d/nginx).

Прокси: путь и заголовки

Есть ли URI после адреса в proxy_pass, решает, какой путь получит приложение:

location /api/ и proxy_pass Запрос /api/x уходит как
http://127.0.0.1:3003 /api/x — путь без изменений
http://127.0.0.1:3003/ /x — /api/ заменено на /

По умолчанию приложение видит Host: 127.0.0.1:3003 — адрес из proxy_pass. Имя сайта и адрес клиента передают заголовками:

nginx
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

HTTPS и перенаправление с HTTP

nginx
server {
    listen 443 ssl;
    server_name shop.lab.test;
    ssl_certificate     /etc/nginx/tls/fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/shop.key;
    root /var/www/shop;
}
server {
    listen 80;
    server_name shop.lab.test;
    return 301 https://$host$request_uri;
}

ssl_certificate — файл цепочки: сначала сертификат сайта, за ним промежуточный. $request_uri — путь вместе с запросом после ?: http://shop.lab.test/x?y=1 уводит на https://shop.lab.test/x?y=1. Сертификаты и их проверка — openssl-tls.

Директива Что делает Пример
listen server Адрес и порт блока; default_server делает его сервером по умолчанию, ssl включает TLS. listen 443 ssl;
server_name server Имена сайта, с которыми nginx сравнивает заголовок Host. server_name docs.lab.test www.docs.lab.test;
root server, location Каталог сайта; путь файла = root + путь из запроса. root /var/www/docs;
index server, location Какой файл отдать на запрос каталога. index index.html;
location server Правила для путей с этим префиксом; самый длинный совпавший префикс выигрывает. location /api/ { … }
alias location Каталог вместо совпавшего префикса location (root дописывает путь целиком). alias /srv/data/;
try_files server, location Проверить файлы по очереди и отдать первый существующий, иначе последний вариант. try_files $uri $uri/ =404;
proxy_pass location Переслать запрос бэкенду; URI после адреса заменяет совпавший префикс location. proxy_pass http://unix:/run/app/app.sock;
proxy_set_header server, location Заголовок запроса к бэкенду; без него Host — адрес из proxy_pass. proxy_set_header Host $host;
proxy_read_timeout server, location Сколько ждать ответа бэкенда, потом 504. proxy_read_timeout 30s;
ssl_certificate server Файл сертификата; с промежуточным CA — сертификат сайта первым, за ним промежуточный. ssl_certificate /etc/nginx/tls/fullchain.pem;
ssl_certificate_key server Закрытый ключ к первому сертификату файла ssl_certificate. ssl_certificate_key /etc/nginx/tls/site.key;
return server, location Сразу ответить кодом; 301 с адресом — перенаправление. return 301 https://$host$request_uri;
client_max_body_size http, server, location Наибольшее тело запроса; больше — 413. client_max_body_size 20m;
error_log http, server Куда писать ошибки; уровень warn, error, crit и выше. error_log /var/log/nginx/docs.error.log;
access_log http, server, location Куда и в каком формате писать строку на каждый запрос. access_log /var/log/nginx/app.log timed;
log_format http Свой формат журнала из переменных — $status, $request_time и других. log_format timed '$remote_addr "$request" $status $request_time';

Ничего не найдено. Попробуй запрос короче.

Рецепты

2

Кто ответит на имя

bash
curl -i -H 'Host: docs.lab.test' http://127.0.0.1/
sudo nginx -T | grep -n 'server_name\|listen'

Первая команда — ответ глазами клиента, вторая — какие listen и server_name nginx на самом деле прочитал, в порядке файлов.

Найти, где сломан прокси

bash
curl -s -o /dev/null -w '%{http_code}\n' http://app.lab.test/
sudo tail -n 1 /var/log/nginx/error.log
curl -i http://127.0.0.1:3000/        # приложение напрямую

Строка error.log называет upstream — куда nginx шёл. Приложение напрямую отвечает — дело в proxy_pass или правах; не отвечает — в приложении.

Неполадки

11

Отвечает чужой сайт

Host запроса не совпал ни с одним server_name — ответил сервер по умолчанию. Проверь имя в server_name и что ссылка на сайт есть в sites-enabled: sudo nginx -T | grep server_name.

404 Not Found

В error.log — путь, который собрал nginx: open() "/var/www/portal/nope.html" failed (2: No such file or directory). Сравни его с настоящим путём файла: обычно ошибка в root или alias.

403 Forbidden

(13: Permission denied) — www-data не может прочитать файл или войти в каталог по пути; права по всему пути покажет namei -l. directory index of "…" is forbidden — в каталоге нет файла из index.

502 Bad Gateway

nginx не открыл соединение с бэкендом. Причину называет строка error.log:

В error.log Что значит
(111: Connection refused) никто не слушает адрес или сокет
(13: Permission denied) у www-data нет права на сокет
(2: No such file or directory) нет файла сокета по этому пути

Полностью: connect() failed (111: Connection refused) while connecting to upstream, … upstream: "http://127.0.0.1:3001/". Для сокета — connect() to unix:/run/app/app.sock failed (13: Permission denied). Ошибки 13 и 2 nginx пишет с уровнем [crit], 111 — [error]: поиск только по [error] их пропустит. Сокет остался после остановки приложения — тоже 111. Права и пути сокетов — в статье unix-sockets.

504 Gateway Time-out

Соединение есть, ответа нет: upstream timed out (110: Connection timed out) while reading response header from upstream. Приложение зависло или отвечает дольше proxy_read_timeout (по умолчанию 60 с).

nginx -t: test failed

  • unexpected "}" in …:5 — в строке перед } нет ;;
  • a duplicate default server for 0.0.0.0:80 — default_server стоит в двух блоках одного порта;
  • open() "/run/nginx.pid" failed (13: Permission denied) — nginx -t запущен без sudo.

conflicting server name

[warn] conflicting server name "docs.lab.test" on 0.0.0.0:80, ignored — одно имя стоит в двух блоках одного порта. Проверку это не роняет, но запросы с этим именем получает блок, загруженный первым, а второй его не получает никогда. Найти оба:

bash
grep -n 'docs.lab.test' /etc/nginx/sites-enabled/*

grep -r по каталогу здесь не годится: ссылки в sites-enabled он пропускает.

key values mismatch

SSL_CTX_use_PrivateKey(…) failed (… key values mismatch) — первый сертификат в файле ssl_certificate не пара ключу. Либо ключ чужой, либо в файле цепочки первым стоит промежуточный сертификат. Сертификат сайта — первым.

Новый сертификат не виден

nginx читает сертификаты при старте и reload. Заменил файл — sudo nginx -t && sudo systemctl reload nginx, проверяй через секунду.

Служба не стартует: порт занят

sudo ss -tlnp | grep ':80 ' покажет, кто держит порт. Останови его или смени listen.

Новый listen не применился

Смена адреса на том же порту (listen 80 → listen 127.0.0.1:80) через reload не проходит: systemctl reload возвращает 0, nginx остаётся на старых сокетах, а в error.log — bind() to 127.0.0.1:80 failed (98: Address already in use). Такую правку применяет только sudo systemctl restart nginx; что слушается на самом деле — sudo ss -tlnp | grep nginx.

Опасно

1

restart с ошибкой в конфиге

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

Термины

6
виртуальный HTTP-сайт HTTP virtual host
Сайт, выбираемый по имени при совместном использовании адреса и порта. Ещё встречается в: HTTP
обратный прокси reverse proxy
Сервер, который принимает запросы клиентов, пересылает их приложению за собой и отдаёт клиенту ответ приложения. Так работает nginx с proxy_pass. Ещё встречается в: HTTP, AppArmor, Grafana, HAProxy и ещё 2
бэкенд (upstream) upstream (backend)
Приложение за обратным прокси, которому nginx пересылает запрос, — адрес и порт или unix-сокет из proxy_pass. В error.log nginx его называет поле upstream. Ещё встречается в: docker compose, HAProxy, fail2ban, restic и ещё 5
сервер по умолчанию default server
Блок server, который nginx выбирает, когда Host запроса не совпал ни с одним server_name на этом адресе и порту: помеченный default_server, а без пометки — первый. Ещё встречается в: HTTP, TLS и сертификаты, ssh, scp
корень сайта (root) document root
Каталог, от которого nginx строит путь файла: к root дописывается путь из запроса — root /var/www/docs и /a.html дают /var/www/docs/a.html. Ещё встречается в: Capabilities: права root по частям, PAM: проверка входа, политика паролей и блокировка, Ограничения службы: User=, ProtectSystem, NoNewPrivileges, Docker и ещё 64
блок location location block
Блок внутри server с правилами для части путей: location /api/ ловит запросы, чей путь начинается с /api/. Из подходящих префиксов выигрывает самый длинный.

См. также

  • http — запрос, ответ, коды и заголовок Host.
  • unix-sockets — бэкенд на сокете: права, пути, ошибки 13 и 2.
  • openssl-tls — сертификаты, цепочка, ошибки проверки.
  • curl — запросы к сайту из консоли.
  • systemctl — reload, restart, status.
  • journalctl — журнал службы, если nginx не стартует.
  • ss — кто слушает порты 80 и 443.
  • Починить nginx — лаба: nginx не запускается.
  • Настроить reverse proxy на nginx — лаба: прокси на приложение.
Загрузка…