Skip to content
EN
← Все статьи

Настройка nginx с нуля: где лежат конфиги, контексты, server-блок и проверка

Коротко. Главный конфиг nginx — /etc/nginx/nginx.conf; он подключает остальные файлы через include: в Debian и Ubuntu это sites-enabled/ с симлинками на sites-available/, в RHEL-подобных и сборках nginx.org — только conf.d/*.conf. Директивы живут в контекстах main → events → http → server → location и наследуются вниз. Любую правку проверяют командой nginx -t и применяют через systemctl reload nginx.

Что такое nginx и когда всё ещё уместен Apache

nginx — веб-сервер и обратный прокси с событийной моделью обработки соединений. Он запускает один мастер-процесс (читает конфиг, держит привилегированные порты, управляет остальными) и несколько рабочих процессов — обычно по числу ядер. Каждый рабочий процесс в одном потоке обслуживает тысячи одновременных соединений: он не ждёт медленного клиента, а переключается между готовыми к работе сокетами через epoll в Linux или kqueue в BSD. Отсюда главное свойство nginx — потребление памяти почти не зависит от числа открытых соединений, а зависит от того, сколько данных реально передаётся. Это делает его удобным на входе: раздача статики, TLS-терминация, кеш, балансировка, ограничение скорости и защита бэкенда от медленных клиентов.

Apache исторически устроен иначе: соединение обслуживает отдельный процесс или поток. При модели prefork каждый запрос — это форк процесса весом в десятки мегабайт, поэтому тысяча одновременных медленных клиентов упирается в оперативную память, а не в процессор. Современный Apache умеет событийный MPM event и по нагрузке на статике сближается с nginx, но остаётся разница в философии конфигурации.

Apache всё ещё уместен там, где нужны .htaccess — децентрализованные правила на уровне каталога, которые может править владелец сайта без доступа к главному конфигу, — и там, где приложение исторически завязано на mod_php с его внутрипроцессным исполнением PHP. nginx намеренно не поддерживает файлы конфигурации на уровне каталога: это стоило бы проверки файловой системы на каждый запрос. В nginx любое правило описывается централизованно и применяется перезагрузкой конфига.

Практический вывод: на типовом VPS (например, на виртуальной машине у Selectel или любого другого провайдера) nginx ставят фронтом, а приложение — PHP-FPM, Node.js, Python, Java — держат за ним по сокету или порту. Иногда за nginx оставляют и Apache: тогда nginx отдаёт статику и TLS, а Apache обрабатывает .htaccess-логику легаси-сайта. Как устроена такая связка в общем виде, разобрано в материале что такое обратный прокси.

Схема сравнения событийной модели nginx с одним рабочим процессом на множество соединений и процессной модели Apache prefork
Событийная модель nginx против процесс-на-соединение: откуда берётся разница в потреблении памяти

Где лежат конфиги nginx и как они подключаются

Единственный файл, который nginx читает сам, — главный конфиг. Всё остальное попадает в конфигурацию только через директиву include. Путь к главному файлу задаётся при сборке, поэтому не гадайте — спросите у самого бинарника:

nginx -V 2>&1 | tr ' ' '\n' | grep -E 'conf-path|prefix|error-log-path'
# --prefix=/etc/nginx
# --conf-path=/etc/nginx/nginx.conf
# --error-log-path=/var/log/nginx/error.log

Дальше раскладка зависит от того, откуда поставлен nginx. Это самая частая причина, почему инструкция из интернета не работает: в ней есть sites-available, а на вашем сервере такого каталога нет.

Debian и Ubuntu, пакет из репозитория дистрибутива

/etc/nginx/nginx.conf          # главный конфиг
/etc/nginx/conf.d/*.conf       # подключается из http-контекста
/etc/nginx/sites-available/    # все описания сайтов, хранилище
/etc/nginx/sites-enabled/      # симлинки на включённые сайты
/etc/nginx/snippets/           # переиспользуемые куски (ssl-params и т.п.)
/var/www/html                  # корень сайта по умолчанию
/var/log/nginx/access.log
/var/log/nginx/error.log

В nginx.conf внутри блока http стоят две строки:

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

Сайт включают симлинком, а не копией — иначе получите две расходящиеся версии одного конфига:

ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf
# выключить сайт — удалить симлинк, файл остаётся на месте
rm /etc/nginx/sites-enabled/example.conf

RHEL, Rocky, AlmaLinux и официальные пакеты nginx.org

Здесь схемы sites-available нет вообще — её придумали мейнтейнеры Debian. Есть только:

/etc/nginx/nginx.conf
/etc/nginx/conf.d/*.conf       # сюда кладут описания сайтов
/etc/nginx/conf.d/default.conf # дефолтный сайт из коробки
/usr/share/nginx/html          # корень сайта по умолчанию

Чтобы «выключить» сайт, файл переименовывают так, чтобы он перестал попадать под маску: mv example.conf example.conf.disabled. Если вы привыкли к Debian-схеме, её можно воспроизвести вручную: создать каталоги и добавить include /etc/nginx/sites-enabled/*; в http. Но помните, что обновление пакета может перезаписать nginx.conf, и include исчезнет.

Никогда не гадайте, какой файл реально подключён. Команда nginx -T (заглавная T) печатает итоговую конфигурацию — весь дерево include, развёрнутое в один текст, с комментариями вида # configuration file /etc/nginx/conf.d/example.conf:. Если вашего server-блока нет в выводе nginx -T, значит nginx о нём не знает, и правки в нём ничего не изменят.

nginx -T | grep -n 'configuration file'      # список всех подключённых файлов
nginx -T | grep -n -A5 'server_name example.com'  # где описан конкретный сайт

Контексты и директивы: иерархия и наследование

Конфиг nginx — это дерево. Директива действует в том контексте, где объявлена, и, как правило, наследуется во вложенные контексты, пока её не переопределят. Понимание иерархии снимает половину вопросов «почему у меня не работает».

user www-data;                  # main
worker_processes auto;          # main
error_log /var/log/nginx/error.log warn;   # main

events {                        # events
    worker_connections 1024;
}

http {                          # http
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout 65;
    gzip on;

    server {                    # server
        listen 80;
        server_name example.com;
        root /var/www/example;

        location / {            # location
            try_files $uri $uri/ =404;
        }
    }
}
КонтекстГде объявляетсяТипичные директивыНаследуется вниз
mainВерхний уровень nginx.conf, вне всяких блоковuser, worker_processes, pid, error_log, include, load_moduleЧастично: error_log — да, остальное глобально и не переопределяется
eventsВнутри events { }, ровно один блокworker_connections, use, multi_acceptНет, вложенных контекстов не имеет
httpВнутри http { }, ровно один блокinclude mime.types, gzip, sendfile, keepalive_timeout, log_format, access_log, upstream, proxy_cache_pathДа — во все server и location
serverВнутри http, сколько угодно блоковlisten, server_name, root, index, ssl_certificate, return, error_pageДа — во все свои location
locationВнутри server, допускается вложенностьtry_files, alias, proxy_pass, fastcgi_pass, expires, limit_exceptДа — во вложенные location
upstreamВнутри http, рядом с serverserver, keepalive, least_conn, zoneНе наследует и не наследуется, ссылаются по имени
ifВнутри server или locationreturn, rewrite, setИспользовать по минимуму: внутри location поведение неочевидно

Наследование работает по правилу «директива переопределяется целиком, а не дополняется». Если в http стоит add_header X-Frame-Options SAMEORIGIN;, а во вложенном location вы добавили add_header Cache-Control "no-store";, то первый заголовок в этом location исчезнет: набор add_header нижнего уровня полностью заменяет набор верхнего. Это одна из самых болезненных ловушек — подробности и рабочие обходы в разборе заголовков безопасности.

Минимальный рабочий server-блок, server_name и default_server

Минимальный конфиг сайта на статике

Ниже полный файл для отдельного сайта. Положите его в /etc/nginx/sites-available/example.conf (Debian) или /etc/nginx/conf.d/example.conf (RHEL).

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;
    root /var/www/example;
    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;
    }

    # статика с длинным кешем
    location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|woff2|ico)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # закрыть служебные каталоги
    location ~ /\.(?!well-known) {
        deny all;
    }
}

Разберём обязательные директивы:

  • listen 80; — порт и опционально адрес. listen [::]:80; добавляет IPv6. Без второй строки сайт не откроется по IPv6, даже если AAAA-запись в DNS есть.
  • server_name — список имён, по которым nginx выбирает этот блок. Отсутствие имени в списке не запрещает доступ, но отправит запрос в другой server-блок.
  • root — корень файловой системы, к которому дописывается URI запроса. Задавайте его в server, а не в каждом location: так меньше шансов забыть.
  • index — что отдавать при запросе каталога. Если файла нет и autoindex выключен, получите 403, а не 404.
  • try_files — по очереди пробует варианты и отдаёт первый существующий; последний аргумент =404 задаёт код ответа при неудаче.

server_name, default_server и запросы на неизвестный домен

Для каждого сокета listen nginx выбирает server-блок так: сначала по точному совпадению server_name, затем по маске вида *.example.com, затем по маске вида www.example.*, затем по регулярным выражениям в порядке их появления в конфиге. Если не подошло ничего — запрос уходит в блок с флагом default_server, а если такого нет — в первый по порядку server-блок для этого порта.

Отсюда типичная неприятность: чужой домен направляют A-записью на ваш IP, и на нём открывается ваш сайт. Или сканер стучится по IP с Host: 1.2.3.4 и попадает в первый попавшийся блок, засоряя логи и статистику. Лечится явным «заглушечным» сервером:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;   # nginx-специфичный код: закрыть соединение без ответа
}

Для HTTPS то же самое, но потребуется сертификат — хоть самоподписанный, иначе TLS-рукопожатие не состоится и nginx не успеет применить правило:

server {
    listen 443 ssl default_server;
    http2 on;
    server_name _;
    ssl_certificate     /etc/nginx/ssl/dummy.crt;
    ssl_certificate_key /etc/nginx/ssl/dummy.key;
    ssl_reject_handshake on;   # доступно в свежих версиях: отклонить рукопожатие
    return 444;
}

Флаг default_server можно указать только один раз на каждую пару «адрес:порт». Две строки listen 80 default_server; в разных файлах дадут ошибку a duplicate default server for 0.0.0.0:80, и nginx откажется стартовать. Ищите дубликат в conf.d/default.conf или в sites-enabled/default — их часто забывают отключить.

Если у вас много длинных доменов и вы видите ошибку could not build server_names_hash, увеличьте server_names_hash_bucket_size в контексте http до следующей степени двойки (обычно 64 или 128).

Диаграмма выбора server-блока по listen и server_name с ветвью default_server для неизвестного домена
Как nginx выбирает server-блок: точное имя, маски, регулярные выражения, затем default_server

Порядок выбора location, root против alias и try_files

Это место, где новички ошибаются чаще всего. Порядок объявления в файле почти не важен — важен тип префикса.

  1. Сначала проверяются точные совпадения location = /path. Нашлось — поиск немедленно прекращается.
  2. Затем ищется самый длинный совпадающий префикс. Он запоминается.
  3. Если у найденного префикса стоит модификатор ^~, поиск прекращается, регулярные выражения не проверяются.
  4. Иначе проверяются регулярные выражения: ~ (с учётом регистра) и ~* (без учёта) — в порядке появления в конфиге. Первое совпавшее выигрывает.
  5. Если ни одно регулярное выражение не подошло, используется запомненный на шаге 2 префикс.
location = /health        { return 200 "ok\n"; }        # 1) точное
location ^~ /static/      { root /var/www; }            # 3) префикс без регэкспов
location ~* \.(jpg|png)$  { expires 30d; }              # 4) регулярное
location /images/         { root /var/www/legacy; }     # 2) обычный префикс
location /                { try_files $uri $uri/ =404; }# 2) fallback

В этом примере запрос /static/logo.png обслужит блок ^~ /static/, а не регулярное выражение по расширению — потому что ^~ останавливает поиск. А запрос /images/logo.png уйдёт в регулярное выражение, потому что у /images/ модификатора нет. Именно так «пропадает» кеш или наоборот перестаёт работать отдача файлов.

root против alias

root дописывает полный URI к указанному пути. alias заменяет собой совпавшую часть location. Разница видна на примере запроса /files/report.pdf:

location /files/ { root  /var/data; }   # → /var/data/files/report.pdf
location /files/ { alias /var/data/; }  # → /var/data/report.pdf

Правило безопасности: при использовании alias слеш на конце location и на конце пути должны совпадать. Конструкция location /files { alias /var/data/; } без слеша в location открывает путь к обходу каталога — запрос /files../etc/passwd склеится в /var/data/../etc/passwd. Если location задан регулярным выражением, alias обязан содержать группы захвата. По возможности используйте root — он безопаснее по конструкции.

try_files и типовой шаблон для PHP-приложения

server {
    listen 80;
    server_name app.example.com;
    root /var/www/app/public;
    index index.php index.html;

    client_max_body_size 32m;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;          # ключевая строка: не выполнять несуществующие файлы
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php-fpm.sock;   # точный путь смотрите в конфиге пула PHP-FPM
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_read_timeout 60s;
    }

    location ~ /\.ht { deny all; }
}

Строка try_files $uri =404; внутри PHP-локации обязательна. Без неё при определённых настройках PHP запрос вида /uploads/avatar.jpg/x.php может привести к исполнению загруженного файла как PHP-кода. Второй обязательный элемент — корнем должен быть каталог public (или web, httpdocs) фреймворка, а не корень репозитория: иначе .env, composer.json и каталог vendor станут доступны по HTTP. Больше подобных правил собрано в чеклисте харденинга веб-сервера.

Проксирование на бэкенд: proxy_pass и обязательные заголовки

Когда за nginx стоит приложение на Node.js, Python или Java, конфиг сводится к proxy_pass плюс набор заголовков. Голый proxy_pass без заголовков — рабочий, но почти всегда неправильный.

upstream app_backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

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

    location / {
        proxy_pass http://app_backend;

        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_set_header X-Forwarded-Host  $host;

        # websocket
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_connect_timeout 5s;
        proxy_read_timeout    60s;
        proxy_buffering       on;
    }
}

Что ломается без каждого из заголовков:

  • Host. По умолчанию nginx подставляет в Host имя из proxy_pass, то есть app_backend или 127.0.0.1:3000. Приложение видит не тот домен и генерирует ссылки, редиректы и письма с внутренним адресом. Мультидоменные приложения вообще не понимают, какой сайт запрошен.
  • X-Real-IP и X-Forwarded-For. Без них бэкенд видит в качестве клиента только 127.0.0.1. Ломаются логи, гео-определение, антифрод, ограничение частоты запросов и бан по IP. Разница между двумя заголовками и правила доверия к ним разобраны в статье про заголовок X-Forwarded-For.
  • X-Forwarded-Proto. Если TLS терминируется на nginx, бэкенд получает обычный HTTP и считает, что сайт работает без шифрования. Результат — ссылки вида http:// на HTTPS-странице, смешанный контент и бесконечный цикл редиректов, когда фреймворк сам пытается принудительно уводить на HTTPS.

Слеш в конце proxy_pass меняет семантику. proxy_pass http://backend; передаёт URI как есть. proxy_pass http://backend/; отрезает совпавший префикс location. Для location /api/ и запроса /api/users первый вариант отправит на бэкенд /api/users, второй — /users. Обе формы законны, но выбирать нужно осознанно.

Если после включения прокси вы видите 502 или 504, начните с /var/log/nginx/error.log: там будет точная причина — connect() failed (111: Connection refused) при неподнятом бэкенде, upstream timed out при долгом ответе, no live upstreams при выбитых из ротации серверах. Разбор по шагам — в материале об ошибке 502 Bad Gateway.

HTTPS: редирект с HTTP, HSTS и вынос повторяющихся кусков

Каноническая схема — два server-блока: один слушает 80-й порт и только редиректит, второй обслуживает 443-й.

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

    # оставить ACME-проверку доступной по HTTP
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    include /etc/nginx/snippets/ssl-params.conf;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

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

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

Здесь важны три детали. Первая: используйте return 301, а не rewrite — это дешевле и не порождает неожиданных петель. Вторая: $host вместо жёстко прописанного домена сохраняет и example.com, и www.example.com, поэтому один блок обслуживает оба имени; каноникализацию www делайте отдельным явным редиректом, если она нужна. Третья: параметр always у add_header заставляет отдавать заголовок и на ошибочных ответах — без него HSTS пропадёт на страницах 404 и 500.

Повторяющиеся TLS-параметры выносят в отдельный файл и подключают через include:

# /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;

HSTS с большим max-age — это обязательство. Браузер запомнит, что домен доступен только по HTTPS, и откажется открывать его по HTTP до истечения срока; отозвать это раньше нельзя, можно лишь выкатить max-age=0 и ждать, пока каждый клиент зайдёт снова. Включайте директиву, когда HTTPS уже работает стабильно на всех поддоменах, и начинайте с небольшого значения. Порядок перехода описан в руководстве по миграции сайта на HTTPS.

Проверка конфига и перезагрузка: nginx -t, reload и restart

Правило простое: никогда не перезагружайте nginx, не проверив конфиг.

nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

systemctl reload nginx        # применить без разрыва соединений
systemctl status nginx --no-pager
journalctl -u nginx -n 50 --no-pager

Разница между reload и restart принципиальна:

ДействиеЧто происходитРазрыв соединенийЕсли конфиг сломан
systemctl reload nginx (SIGHUP)Мастер перечитывает конфиг, запускает новые рабочие процессы, старым даёт доработать текущие запросыНетПерезагрузка не применяется, мастер продолжает работать со старым конфигом, ошибка пишется в error.log — сайт остаётся живым
systemctl restart nginxПроцесс останавливается полностью и запускается зановоДа, все активные соединения обрываютсяnginx не поднимется, сайт лежит до починки конфига
nginx -s reloadТо же, что SIGHUP, но мимо systemdНетАналогично reload
nginx -t / nginx -TТолько проверка синтаксиса / печать итогового конфигаНетПоказывает файл и номер строки с ошибкой

restart нужен только тогда, когда меняются вещи, не подхватываемые на лету: набор загружаемых модулей, права на файлы сокетов, некоторые параметры listen и переменные окружения юнита systemd. Во всех остальных случаях — reload.

Что делать при «некорректном конфиге»

Вывод nginx -t при ошибке всегда содержит путь к файлу и номер строки. Читайте именно его, а не пересматривайте конфиг глазами:

nginx: [emerg] unknown directive "prox_pass" in /etc/nginx/sites-enabled/app.conf:14
nginx: configuration file /etc/nginx/nginx.conf test failed

Расшифровка частых сообщений:

  • unknown directive — опечатка в имени директивы либо директива из невключённого модуля.
  • unexpected end of file, expecting "}" — незакрытая скобка; номер строки указывает на конец файла, ищите выше.
  • directive is not allowed here — директива поставлена не в тот контекст (например, proxy_pass в server вместо location).
  • bind() to 0.0.0.0:80 failed (98: Address already in use) — порт занят другим процессом; смотрите ss -tlnp | grep :80.
  • cannot load certificate ... No such file or directory — неверный путь к сертификату или он ещё не выпущен.
  • invalid number of arguments — забыта точка с запятой в предыдущей строке.
Схема цикла безопасного применения изменений: правка конфига, nginx -t, reload, проверка ответа сервера, откат при ошибке
Безопасный цикл правки: изменить, проверить nginx -t, перезагрузить, убедиться в ответе

Типовые ошибки новичка и как их чинить

Конфиг не подключён

Самая частая ситуация в Debian и Ubuntu: файл создан в sites-available, но симлинка в sites-enabled нет. Или наоборот — в RHEL файл назван example.config вместо example.conf и не попадает под маску *.conf. Проверка одна: nginx -T | grep 'configuration file'.

Работает не тот server-блок

Если server_name совпадает в двух блоках, nginx при старте предупреждает conflicting server name "example.com" on 0.0.0.0:80, ignored и использует первый. Часто виноват дефолтный сайт дистрибутива: sites-enabled/default или conf.d/default.conf. Отключите его до отладки.

403 Forbidden на статике

Три причины по частоте. Первая: рабочие процессы работают под пользователем из директивы user (в Debian www-data, в RHEL nginx), и у них нет прав на чтение файла или на x хотя бы на одном родительском каталоге пути. Вторая: запрошен каталог, в нём нет файла из index, а autoindex выключен. Третья: на RHEL с включённым SELinux у файлов неверный контекст.

# под каким пользователем работают воркеры
ps -o user,comm -C nginx

# проверить доступность пути именно этим пользователем
sudo -u www-data test -r /var/www/example/index.html && echo readable

# типовые права
chown -R root:www-data /var/www/example
find /var/www/example -type d -exec chmod 755 {} +
find /var/www/example -type f -exec chmod 644 {} +

# SELinux (RHEL/Rocky/Alma)
ls -Z /var/www/example
restorecon -Rv /var/www/example

Изменения не применились

Проверьте по порядку: сделан ли reload вообще; попал ли файл в вывод nginx -T; не отдаётся ли ответ из кеша самого nginx (proxy_cache, fastcgi_cache) или из кеша браузера; не стоит ли перед сервером CDN. Быстрый способ обойти клиентский кеш — запрос через curl с явным отключением кеша, а не обновление страницы в браузере.

curl -sI -H 'Cache-Control: no-cache' https://example.com/ | head -n 20
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/   # мимо DNS и CDN

Порядок location дал не тот результат

Когда непонятно, какой блок сработал, добавьте временно диагностический заголовок в подозрительные локации и посмотрите ответ. Это надёжнее рассуждений о приоритетах.

location ~* \.(css|js)$ {
    add_header X-Debug-Location "static-regex" always;
    expires 30d;
}

Диагностические заголовки удаляйте сразу после отладки. Любой лишний заголовок с внутренней информацией — это подсказка для того, кто изучает вашу инфраструктуру, и повод для замечания в аудите.

Слишком большой файл или долгий ответ

413 Request Entity Too Large лечится директивой client_max_body_size (по умолчанию 1 МБ). 504 Gateway Time-out — увеличением proxy_read_timeout или fastcgi_read_timeout, но сначала стоит понять, почему бэкенд отвечает так долго. Тонкая настройка буферов, воркеров и keepalive вынесена в отдельный разбор — тюнинг производительности nginx.

Схема типовых ошибок конфигурации nginx: отсутствующий симлинк, дубль default_server, конфликт server_name, нехватка прав
Четыре узла, где чаще всего ломается конфигурация: подключение файла, default_server, server_name, права доступа

Как проверить результат снаружи

Локальный curl показывает, что сервер отвечает вам. Но правки в nginx влияют на то, что видят браузеры и роботы из интернета — через CDN, промежуточные прокси и балансировщики. После каждой значимой правки прогоняйте внешние проверки:

  • Заголовки ответа. Инструмент проверки HTTP-заголовков показывает, что реально уходит клиенту после правки: не потерялся ли Strict-Transport-Security из-за вложенного add_header, применился ли Cache-Control к статике, не остались ли отладочные заголовки и версия сервера.
  • SSL и цепочка сертификатов. Проверка SSL-сертификата покажет срок действия, полноту цепочки и набор протоколов. Отсутствующий промежуточный сертификат — классическая ошибка: браузер на десктопе открывает сайт, а мобильное приложение или curl ругаются. Если ошибка уже есть, помогает разбор в типовых ошибках SSL.
  • Редиректы. Трассировка цепочки редиректов проверяет, что HTTP уходит на HTTPS одним переходом с кодом 301, а не через два-три хопа и не в цикл. Каждый лишний хоп — это лишний RTT для пользователя и размытый сигнал для поиска.
  • Скорость. Замер времени ответа до и после правки покажет, не подорожал ли ответ: включение gzip или brotli на динамике, отключение sendfile, лишний прокси-хоп или неудачные таймауты видны именно здесь.
  • Постоянный контроль. Разовая проверка ловит ошибку выката, но не ловит истёкший сертификат через три месяца. Подключите мониторинг доступности, чтобы получать уведомление раньше пользователей.

Частые вопросы

Где именно лежит конфиг nginx?

Главный файл — /etc/nginx/nginx.conf на подавляющем большинстве дистрибутивов. Точный путь для вашей сборки покажет nginx -V 2>&1 | tr ' ' '\n' | grep conf-path. Описания сайтов лежат в /etc/nginx/conf.d/ либо, в Debian и Ubuntu, в /etc/nginx/sites-available/ с симлинками в sites-enabled/.

Как перезапустить nginx, чтобы сайт не лёг?

Используйте nginx -t && systemctl reload nginx. Reload не разрывает установленные соединения: мастер поднимает новые рабочие процессы с новым конфигом, а старые дорабатывают текущие запросы и завершаются. Полный restart обрывает всё и при сломанном конфиге оставляет сайт лежать.

Что значит «некорректный конфиг nginx»?

Это результат nginx -t, когда парсер не смог принять файл. Сообщение всегда указывает файл и номер строки. Чаще всего это забытая точка с запятой, незакрытая фигурная скобка, опечатка в имени директивы, директива в неподходящем контексте или ссылка на несуществующий файл сертификата.

nginx или Apache — что выбрать для нового проекта?

Для нового проекта разумно ставить nginx фронтом: он экономнее по памяти на большом числе соединений и удобнее как прокси и TLS-терминатор. Apache остаётся оправданным выбором, когда приложение опирается на .htaccess или на mod_php, а переписывать правила некому. Схема «nginx впереди, Apache позади» — рабочий компромисс для легаси.

Почему после правки конфига ничего не изменилось?

По убыванию частоты: не выполнен reload; файл не подключён (нет симлинка или неверное расширение) — проверьте nginx -T; сработал другой server-блок из-за конфликта server_name; ответ отдан из кеша nginx, браузера или CDN.

Нужно ли перезагружать nginx после обновления сертификата?

Да. nginx читает файлы сертификата и ключа при старте и при перезагрузке конфига, а дальше держит их в памяти. После выпуска нового сертификата нужен systemctl reload nginx — обычно это вешают хуком на ACME-клиент, чтобы не забывать вручную.

Чеклист перед выкатом

  • Определён реальный путь к конфигу через nginx -V, а не по памяти.
  • Файл сайта подключён: он виден в выводе nginx -T | grep 'configuration file'.
  • В listen есть строка для IPv6, если у домена настроена AAAA-запись.
  • Ровно один default_server на каждую пару «адрес:порт», заглушка отдаёт 444.
  • Нет предупреждений conflicting server name при nginx -t.
  • root указывает на публичный каталог приложения, а не на корень репозитория.
  • В PHP-локации есть try_files $uri =404;.
  • Понятен приоритет location: точное, ^~, регулярные, префикс.
  • При прокси заданы Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.
  • HTTP отдаёт один 301 на HTTPS; путь /.well-known/acme-challenge/ остаётся доступным.
  • add_header с HSTS имеет параметр always и не перекрывается вложенными блоками.
  • Повторяющиеся TLS-параметры вынесены в snippets/ и подключены через include.
  • nginx -t выполнен до перезагрузки; применение — через reload, не restart.
  • Права на файлы соответствуют пользователю из директивы user; на RHEL проверен контекст SELinux.
  • Отладочные заголовки удалены, версия сервера скрыта server_tokens off;.
  • После выката проверены заголовки, сертификат, редиректы и время ответа.

Официальная документация по всем директивам — на nginx.org; для каждой директивы там указан контекст, значение по умолчанию и версия, начиная с которой она доступна. Это единственный источник, которому стоит верить, когда конфиг из чужой статьи не работает.

Проверьте ваш сайт прямо сейчас

Следить за своим сервером →
Другие статьи: Инфраструктура
Инфраструктура
Почта на своём домене: Яндекс 360, VK WorkSpace или свой сервер — настройка, отправка с сайта и переезд
21.07.2026 · 302 просм.
Инфраструктура
Алгоритмы балансировки нагрузки: Round Robin, Least Connections и другие
16.03.2026 · 265 просм.
Инфраструктура
Стратегии версионирования API: URL, заголовки и параметры запроса
16.03.2026 · 262 просм.
Инфраструктура
Rate Limiting в API: зачем и как настроить
14.03.2026 · 255 просм.