Nginx

Nginx

Nginx — веб-сервер, reverse proxy и балансировщик нагрузки. Он принимает HTTP/HTTPS-запросы, раздаёт статические файлы, передаёт запросы приложениям, сжимает и кеширует ответы.

Клиент → Nginx → приложение
              ├── backend-1:8000
              ├── backend-2:8000
              └── статические файлы

Установка и управление

Debian/Ubuntu

sudo apt update
sudo apt install nginx

Fedora/RHEL

sudo dnf install nginx

На старых системах может использоваться yum:

sudo yum install nginx

Arch Linux

sudo pacman -S nginx

Запуск и включение автозапуска:

sudo systemctl enable --now nginx
systemctl status nginx

Основные команды:

sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
sudo systemctl enable nginx
sudo systemctl disable nginx

Для обычного изменения конфигурации предпочтителен reload: Nginx перечитывает настройки без резкого завершения текущих соединений.

Перед применением изменений всегда проверяйте конфигурацию:

sudo nginx -t
sudo nginx -t && sudo systemctl reload nginx

Полная конфигурация с раскрытыми include:

sudo nginx -T

Версия и параметры сборки:

nginx -v
nginx -V

Файлы конфигурации

Основной файл:

/etc/nginx/nginx.conf

Распространённые пути:

/etc/nginx/conf.d/           дополнительные конфигурации
/etc/nginx/sites-available/  доступные сайты в Debian/Ubuntu
/etc/nginx/sites-enabled/    включённые сайты в Debian/Ubuntu
/var/log/nginx/              журналы
/usr/share/nginx/html/       стандартная статика
/var/www/                    каталоги сайтов

На Debian/Ubuntu сайт часто включают символической ссылкой:

sudo ln -s /etc/nginx/sites-available/example.conf \
  /etc/nginx/sites-enabled/example.conf
sudo nginx -t && sudo systemctl reload nginx

Упрощённый /etc/nginx/nginx.conf:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    keepalive_timeout 65;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Пользователь процесса может называться www-data, nginx или иначе — это зависит от дистрибутива. Файлы из conf.d и sites-enabled обычно уже подключаются внутри блока http, поэтому второй блок http в них создавать не нужно.


Блок server

server описывает виртуальный сервер: порт, домены, корневой каталог, журналы и обработчики URI.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example/public;
    index index.html;

    access_log /var/log/nginx/example.access.log;
    error_log /var/log/nginx/example.error.log warn;

    location / {
        try_files $uri $uri/ =404;
    }
}

Основные директивы:

Директива Назначение
listen Адрес и порт прослушивания
server_name Доменные имена виртуального сервера
root Корневой каталог сайта
index Индексные файлы
access_log Журнал запросов
error_log Журнал ошибок

Сервер по умолчанию:

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

default_server относится к паре адрес/порт. Имя _ лишь часто используется как заведомо неподходящее имя и не является специальной маской.


Блок location

location выбирает обработчик по URI.

Префикс

location /images/ {
    root /var/www/example/public;
}

Точное совпадение

location = /health {
    access_log off;
    default_type text/plain;
    return 200 "ok\n";
}

Приоритетный префикс

location ^~ /static/ {
    root /var/www/example/public;
}

^~ прекращает поиск среди регулярных location, если этот префикс выбран.

Регулярное выражение

location ~ \.php$ {
    # С учётом регистра
}

location ~* \.(jpg|jpeg|png|gif|svg|webp)$ {
    # Без учёта регистра
}

Упрощённый порядок выбора:

  1. Точное совпадение =.
  2. Самый длинный префикс.
  3. Если префикс имеет ^~, используется он.
  4. Иначе проверяются регулярные выражения по порядку записи.
  5. Если regex не совпал, используется лучший префикс.

root, alias, try_files

root добавляет URI к пути:

location /media/ {
    root /srv/www;
}

Запрос /media/photo.jpg ищется как /srv/www/media/photo.jpg.

alias заменяет совпавшую часть URI:

location /media/ {
    alias /srv/storage/;
}

Тот же запрос ищется как /srv/storage/photo.jpg. Для каталогов согласовывайте завершающий / у location и alias.

try_files проверяет существование файлов по порядку:

location / {
    try_files $uri $uri/ =404;
}

Для SPA:

location / {
    try_files $uri $uri/ /index.html;
}

Раздача статики

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

    location / {
        try_files $uri $uri/ =404;
    }

    location ~* \.(css|js|jpg|jpeg|png|gif|svg|webp|ico|woff2?)$ {
        try_files $uri =404;
        expires 30d;
        add_header Cache-Control "public";
    }
}

Nginx должен иметь право чтения файла и право прохода x для всех родительских каталогов. Типичные права для неизменяемой статики:

sudo find /var/www/static -type d -exec chmod 755 {} +
sudo find /var/www/static -type f -exec chmod 644 {} +

Не используйте chmod -R 777. На системах с SELinux также проверяйте контексты безопасности.


Reverse proxy

Минимальный пример:

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
    }
}

Практический вариант:

location / {
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_connect_timeout 5s;
    proxy_send_timeout 30s;
    proxy_read_timeout 30s;

    proxy_pass http://127.0.0.1:8000;
}
Заголовок Значение
Host Исходное имя хоста
X-Real-IP Адрес клиента для Nginx
X-Forwarded-For Цепочка адресов клиента и прокси
X-Forwarded-Proto Исходная схема http или https

Приложение должно доверять этим заголовкам только от известных прокси.

Завершающий / в proxy_pass

location /api/ {
    proxy_pass http://127.0.0.1:8000/;
}

Запрос /api/users обычно передаётся как /users.

location /api/ {
    proxy_pass http://127.0.0.1:8000;
}

Здесь upstream обычно получает /api/users.

WebSocket

В контексте http:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

В server:

location /ws/ {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_read_timeout 1h;
    proxy_pass http://127.0.0.1:8000;
}

Балансировка нагрузки: upstream

upstream объявляется в контексте http:

upstream app_backend {
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://app_backend;
    }
}

По умолчанию применяется round-robin.

Вес

upstream app_backend {
    server 10.0.0.11:8000 weight=3;
    server 10.0.0.12:8000 weight=1;
}

Наименьшее число соединений

upstream app_backend {
    least_conn;
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
}

Привязка по IP

upstream app_backend {
    ip_hash;
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
}

ip_hash имеет ограничения при NAT и прокси и не заменяет общее хранилище сессий.

Резерв и пассивная проверка

upstream app_backend {
    server 10.0.0.11:8000 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8000 max_fails=3 fail_timeout=30s;
    server 10.0.0.20:8000 backup;
}

Open Source Nginx обычно обнаруживает проблемы пассивно, во время реальных запросов. Активные health checks зависят от редакции и модулей.


Gzip-сжатие

Обычно настраивается в http:

gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_proxied any;

gzip_types
    text/plain
    text/css
    text/xml
    application/json
    application/javascript
    application/xml
    image/svg+xml;

text/html сжимается при включённом gzip по умолчанию. JPEG, PNG, WebP, архивы и видео уже сжаты, поэтому добавлять их обычно бессмысленно.

Проверка:

curl -I -H 'Accept-Encoding: gzip' https://example.com/app.js

Ожидаемый заголовок:

Content-Encoding: gzip

Кеширование статики в браузере

location ~* \.(css|js|jpg|jpeg|png|gif|svg|webp|ico|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public";
    try_files $uri =404;
}

Для файлов с хешем содержимого в имени:

location /assets/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    try_files $uri =404;
}

immutable безопасен, если URI меняется при изменении содержимого. HTML обычно не кешируют надолго:

location = /index.html {
    expires -1;
    add_header Cache-Control "no-cache";
}

no-cache разрешает хранение, но требует проверки актуальности. no-store запрещает хранение.


Proxy cache

Зона объявляется в http:

proxy_cache_path /var/cache/nginx/app
    levels=1:2
    keys_zone=app_cache:20m
    max_size=2g
    inactive=60m
    use_temp_path=off;

Использование:

location /public-api/ {
    proxy_cache app_cache;
    proxy_cache_methods GET HEAD;
    proxy_cache_valid 200 10m;
    proxy_cache_valid 404 1m;
    proxy_cache_lock on;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

    add_header X-Cache-Status $upstream_cache_status always;
    proxy_pass http://app_backend;
}

Основные значения $upstream_cache_status:

Значение Смысл
MISS Записи не было
HIT Ответ взят из кеша
BYPASS Кеш обойдён
EXPIRED Запись устарела
STALE Выдана устаревшая запись
UPDATING Запись обновляется

Не кешируйте без строгих правил личные кабинеты, корзины и ответы с персональными данными. Для запросов с авторизацией или сессионными cookie кеш обычно отключают:

map $http_authorization $skip_auth_cache {
    default 1;
    "" 0;
}

location /api/ {
    proxy_cache app_cache;
    proxy_cache_bypass $skip_auth_cache;
    proxy_no_cache $skip_auth_cache;
    proxy_pass http://app_backend;
}

Если ответ зависит от cookie, языка или заголовков, это должно учитываться условиями обхода и ключом кеша.


Логи Nginx

Стандартные журналы:

/var/log/nginx/access.log
/var/log/nginx/error.log

Собственный формат объявляется в http:

log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent" '
                'rt=$request_time urt=$upstream_response_time '
                'upstream=$upstream_addr cache=$upstream_cache_status';

access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;

Полезные переменные:

Переменная Значение
$remote_addr IP клиента с точки зрения Nginx
$request Строка запроса
$status HTTP-код ответа
$request_time Полное время обработки
$upstream_response_time Время ответа backend
$upstream_addr Выбранный backend
$upstream_cache_status Состояние кеша

Просмотр:

sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
sudo journalctl -u nginx -f
sudo journalctl -u nginx --since today

Не записывайте в логи пароли, токены, Authorization и чувствительные параметры URL. Ротация обычно настраивается через /etc/logrotate.d/nginx.


Диагностика

403 Forbidden

Проверьте права на файл и все родительские каталоги, root/alias, autoindex, правила allow/deny и SELinux:

namei -l /var/www/example/public/index.html
sudo tail -n 100 /var/log/nginx/error.log

404 Not Found

Проверьте существование файла, root, alias, try_files и преобразование URI в proxy_pass.

502 Bad Gateway

Nginx не смог получить корректный ответ от backend:

curl -v http://127.0.0.1:8000/
ss -lntp
sudo journalctl -u nginx -n 100

504 Gateway Timeout

Backend не ответил вовремя. Следует искать причину медленной работы приложения, а не только увеличивать proxy_read_timeout.


Полный пример

Файл подключается из контекста http:

upstream api_backend {
    least_conn;
    server 127.0.0.1:8001 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8002 max_fails=3 fail_timeout=30s;
}

proxy_cache_path /var/cache/nginx/api
    levels=1:2
    keys_zone=api_cache:20m
    max_size=1g
    inactive=30m
    use_temp_path=off;

server {
    listen 80;
    listen [::]:80;
    server_name example.com;

    root /var/www/example/public;
    index index.html;

    location = /health {
        access_log off;
        default_type text/plain;
        return 200 "ok\n";
    }

    location /assets/ {
        try_files $uri =404;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location /api/public/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_cache api_cache;
        proxy_cache_valid 200 5m;
        proxy_cache_lock on;
        add_header X-Cache-Status $upstream_cache_status always;

        proxy_pass http://api_backend;
    }

    location /api/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache off;
        proxy_pass http://api_backend;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

После сохранения:

sudo nginx -t && sudo systemctl reload nginx
curl -I http://example.com/

Базовые рекомендации

Пример ограничения загрузки:

client_max_body_size 10m;

Запрет доступа к скрытым файлам, кроме .well-known:

location ~ /\.(?!well-known/) {
    deny all;
}

Краткая памятка

sudo systemctl enable --now nginx
sudo systemctl status nginx
sudo nginx -t
sudo nginx -T
sudo systemctl reload nginx
sudo tail -f /var/log/nginx/error.log

Статический сайт:

location / {
    try_files $uri $uri/ =404;
}

Reverse proxy:

location / {
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://127.0.0.1:8000;
}

Главное правило: сначала nginx -t, затем systemctl reload nginx. Особенно внимательно проверяйте завершающий / в proxy_pass, сочетание root/alias и правила кеширования.