Коротко. Главный конфиг 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 и как они подключаются
Единственный файл, который 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, рядом с server | server, keepalive, least_conn, zone | Не наследует и не наследуется, ссылаются по имени |
if | Внутри server или location | return, 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).

Порядок выбора location, root против alias и try_files
Это место, где новички ошибаются чаще всего. Порядок объявления в файле почти не важен — важен тип префикса.
- Сначала проверяются точные совпадения
location = /path. Нашлось — поиск немедленно прекращается. - Затем ищется самый длинный совпадающий префикс. Он запоминается.
- Если у найденного префикса стоит модификатор
^~, поиск прекращается, регулярные выражения не проверяются. - Иначе проверяются регулярные выражения:
~(с учётом регистра) и~*(без учёта) — в порядке появления в конфиге. Первое совпавшее выигрывает. - Если ни одно регулярное выражение не подошло, используется запомненный на шаге 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— забыта точка с запятой в предыдущей строке.

Типовые ошибки новичка и как их чинить
Конфиг не подключён
Самая частая ситуация в 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.

Как проверить результат снаружи
Локальный 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; для каждой директивы там указан контекст, значение по умолчанию и версия, начиная с которой она доступна. Это единственный источник, которому стоит верить, когда конфиг из чужой статьи не работает.