Перейти к основному содержимому

Nginx — конфиги под задачу


Для кого эта статья

Если в поиске «nginx конфиг для статики», «nginx reverse proxy node», «nginx react try_files», «nginx php-fpm» или «nginx ssl letsencrypt» — здесь готовые фрагменты с построчным разбором: что означает каждая строка, что увидит браузер и как проверить из терминала.

Статья рассчитана на:

  • школьников и студентов на первых лабораторных по администрированию, сетям и DevOps;
  • начинающих разработчиков, которые подняли сайт на VPS и ищут рабочий nginx.conf;
  • тех, кто нашёл чужой конфиг и хочет понять, зачем там try_files, proxy_pass и слэш в конце URL.
Сначала теория — потом копирование

Полный справочник директив — Nginx. Как веб-сервер вписан в цепочку «DNS → HTTP → приложение» — веб-серверы. Nginx в Docker — готовые стеки Compose. Проверка ответов — curl.

Nginx (читают «энджинкс») — программа на сервере, которая принимает HTTP-запросы от браузера и либо отдаёт файл с диска, либо передаёт запрос дальше (в Node.js, Python, PHP-FPM). Настройки лежат в текстовых файлах *.conf; после правки обязательно nginx -t, затем reload.


Как читать конфиг по частям

Nginx читает конфиг сверху вниз. Директива заканчивается ;, блок — { }. Запрос попадает в один блок server (по порту и домену), затем nginx выбирает один location (по URL).

Блоки (контексты)

БлокЗачем нужен
events { }Сколько соединений держит один рабочий процесс
http { }Всё про HTTP — MIME, gzip, upstream, include сайтов
server { }Один «виртуальный хост» — домен + порт + корень сайта
location /path { }Правило для URL, начинающегося с /path
upstream имя { }Список бэкендов для балансировки и proxy

Частые директивы

ДирективаСмысл простыми словами
listen 80Слушать порт 80 (обычный HTTP)
listen 443 sslСлушать HTTPS
server_name example.comЭтот блок срабатывает, если в запросе Host: example.com
root /var/www/siteПапка на диске: URL /img/a.png → файл /var/www/site/img/a.png
index index.htmlЕсли URL — каталог, отдать этот файл
try_files $uri …Порядок «найти файл на диске или сделать fallback»
proxy_pass http://…Не искать файл — отправить запрос другому серверу
return 301 URLСразу ответ «перейди по другому адресу»
include файлПодключить ещё один конфиг

Переменные — «подстановки» в конфиге

ПеременнаяОткуда берётсяПример значения
$uriПуть запроса без query/api/users
$request_uriПуть и query/api/users?page=1
$hostЗаголовок Hostshop.example.com
$remote_addrIP клиента203.0.113.5
$schemehttp или httpshttps
$document_rootЗначение root/var/www/site

Два адреса для proxy_pass — самая частая путаница на лабораторных:

СитуацияПишите в proxy_pass
Nginx и app на одной Linux-машинеhttp://127.0.0.1:3000
Nginx в Docker, app — другой контейнерhttp://api:3000 (имя сервиса из compose)

127.0.0.1 внутри контейнера nginx — это сам контейнер, не ваш ПК и не соседний сервис.


Как работать с примерами

  1. Конфиги на Ubuntu/Debian — /etc/nginx/, главный nginx.conf, сайты в conf.d/*.conf или sites-enabled/.
  2. Сохраните фрагмент, например /etc/nginx/conf.d/my-site.conf.
  3. Проверка и перезагрузка:
sudo nginx -t
sudo systemctl reload nginx
  1. Смотрите ответ:
curl -I http://localhost
curl -I -H "Host: shop.local" http://127.0.0.1
  1. Логи: ошибки — /var/log/nginx/error.log, все запросы — access.log.
Права и секреты

Правка /etc/nginx/ — через sudo. Ключ TLS (ssl_certificate_key) не кладите в Git. Домены и пути в примерах замените на свои.


Обязательный каркас

Перед любым примером ниже — минимальный nginx.conf (или один файл в conf.d/, если в главном уже есть include /etc/nginx/conf.d/*.conf;):

events {
worker_connections 1024;
}

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

sendfile on;
keepalive_timeout 65;

# server { } из примеров — сюда или в conf.d/
}
СтрокаЧто делает
events { worker_connections 1024; }Без блока events nginx не запустится
include mime.typesСопоставляет .csstext/css, .jsapplication/javascript
default_type application/octet-streamЕсли расширение неизвестно — отдать как «сырые байты»
sendfile onЯдро ОС отдаёт файл с диска быстрее, без лишнего копирования в память
keepalive_timeout 65Держать HTTP-соединение открытым до 65 с (меньше рукопожатий TCP)

Что делает команда проверки:

sudo nginx -t
  1. Nginx читает все include и склеивает конфиг в памяти.
  2. Проверяет синтаксис (скобки, точки с запятой, директивы в правильном блоке).
  3. Печатает syntax is ok и test is successful — только после этого безопасен reload.

Стартовые конфиги

Пять сценариев, которые чаще всего ищут на первых курсах — от HTML на диске до прокси на backend.


1. Статический сайт — «Hello»

Задача: отдать готовый index.html с диска. Подходит для лабораторной «поднять веб-сервер», для простой визитки или папки dist/ после сборки фронта без client-side роутинга.

Когда искать этот конфиг: «nginx отдача статики», «nginx index.html», «nginx root try_files».

server {
listen 80;
server_name localhost;

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

location / {
try_files $uri $uri/ =404;
}
}
СтрокаСмысл
server { … }Один виртуальный хост — набор правил для домена
listen 80Принимать HTTP на порту 80
server_name localhostБлок выбирается, если браузер шлёт Host: localhost
root /var/www/helloURL /logo.png → файл /var/www/hello/logo.png
index index.htmlЗапрос / или /about/ → попробовать index.html в каталоге
location /Правило для любого пути (префикс /)
try_files $uri $uri/ =4041) файл как есть 2) каталог + index 3) иначе ошибка 404

Что происходит по шагам (запрос GET /):

  1. Nginx выбирает этот server (порт 80, Host: localhost).
  2. Путь / попадает в location /.
  3. try_files: ищет файл / — нет; ищет каталог /var/www/hello/ — есть.
  4. Из-за index index.html отдаёт /var/www/hello/index.html со статусом 200.

Что происходит (запрос GET /missing.txt):

  1. Файла /var/www/hello/missing.txt нет, каталога /var/www/hello/missing.txt/ тоже нет.
  2. =404 — nginx отвечает 404 Not Found.
URLФайл на дискеОтвет
/index.html есть200, HTML
/style.cssstyle.css есть200, CSS
/nopeничего нет404

Подготовка и проверка:

sudo mkdir -p /var/www/hello
echo '<h1>Hello from Nginx</h1>' | sudo tee /var/www/hello/index.html
sudo nginx -t && sudo systemctl reload nginx
curl -i http://localhost/

Что делает код:

  1. mkdir -p — создаёт каталог для файлов сайта.
  2. tee — записывает HTML (от root, если нет прав у обычного пользователя).
  3. nginx -t — проверка конфига до reload.
  4. curl -i — показывает заголовки и тело; в теле должен быть ваш <h1>.

Частая ошибка: забыли index index.html — для / nginx вернёт 403 Forbidden (каталог есть, но «листинг» по умолчанию запрещён).


2. Редирект HTTP → HTTPS

Задача: весь трафик с порта 80 перенаправить на HTTPS. Браузер сам откроет https://…, пользователь не вводит протокол вручную.

Когда искать: «nginx redirect http to https», «nginx return 301 ssl».

server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
СтрокаСмысл
server_name example.com www.example.comОба домена — с www и без
return 301 https://…Ответ без тела: «навсегда переехали» (код 301)
$hostПодставится домен из запроса (example.com или www.example.com)
$request_uriПуть и ?query=… сохраняются

Что происходит по шагам (GET http://example.com/old-page?q=1):

  1. Запрос приходит на порт 80.
  2. Nginx не ищет файлы — сразу return 301.
  3. В заголовке Location: https://example.com/old-page?q=1.
  4. Браузер повторяет запрос уже на порт 443 (нужен отдельный блок с SSL — пример №8).
ЗапросЗаголовок Location
http://example.com/https://example.com/
http://www.example.com/blog/1https://www.example.com/blog/1

Проверка:

curl -I http://example.com/old-page?q=1

В выводе ищите строку HTTP/1.1 301 и Location: https://….

Частая ошибка: есть редирект с 80, но нет server &#123; listen 443 ssl; … &#125; — браузер после редиректа получит «connection refused» или ошибку сертификата.


3. SPA — fallback на index.html

Задача: React, Vue, Angular, Vite после npm run build — при обновлении страницы по адресу /users/42 nginx отдаёт index.html, а маршрутизацию делает JavaScript в браузере.

Когда искать: «nginx react router», «nginx vue history mode», «try_files index.html spa».

server {
listen 80;
server_name app.local;
root /var/www/spa/dist;
index index.html;

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

location ~* \.(js|css|png|jpg|svg|woff2)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
}
СтрокаСмысл
root …/distСюда копируют результат npm run build (папка dist/ или build/)
try_files … /index.htmlНет файла для пути — отдать один index.html (shell приложения)
`location ~* .(jscss…)$`
~*Регистронезависимое совпадение (.PNG тоже)
expires 7dБраузер может кэшировать файл 7 дней
Cache-Control "public, immutable"Файлы с хешем в имени (app.a1b2c3.js) не перезапрашивать зря

Что происходит по шагам (GET /dashboard после F5):

  1. Nginx ищет файл /var/www/spa/dist/dashboard — нет.
  2. Ищет каталог dashboard/ — нет.
  3. Внутренний переход на /index.html — отдаёт SPA, статус 200 (это важно: SPA ожидает 200, а не 404).

Что происходит (GET /assets/index-a1b2.js):

  1. Совпадает второй location (расширение .js).
  2. Отдаётся реальный файл из dist/assets/ с заголовками кэша.
URLЕсть файл в dist?Ответ
/index.html200, HTML
/dashboardтолько index.html200, тот же HTML
/assets/app.jsда200, JS + Cache-Control

Проверка:

curl -I http://app.local/dashboard
curl -I http://app.local/assets/index.js

Первый запрос — 200 и Content-Type: text/html. Второй — 200 и длинный Cache-Control.

Частая ошибка: забыли /index.html в try_files — F5 на /profile даёт 404, хотя в приложении страница открывалась по клику.


4. Reverse proxy на Node.js / Python / Go

Задача: снаружи пользователь ходит на порт 80, nginx передаёт запрос приложению на 127.0.0.1:3000 (Express, FastAPI, Flask, Gin и т.д.).

Когда искать: «nginx reverse proxy nodejs», «nginx proxy_pass localhost 3000», «nginx fastapi».

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

server {
listen 80;
server_name api.local;

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 Connection "";
}
}
СтрокаСмысл
upstream app_backendИменованная группа бэкендов (позже добавите второй server для балансировки)
keepalive 32До 32 idle-соединений nginx → app (меньше накладных расходов TCP)
proxy_pass http://app_backendЗапрос уходит на app, ответ nginx отдаёт клиенту
proxy_http_version 1.1HTTP/1.1 нужен для keepalive к бэкенду
Host $hostApp видит реальный домен (api.local), а не 127.0.0.1
X-Real-IPIP клиента (для логов и rate limit в приложении)
X-Forwarded-ForЦепочка IP через прокси
X-Forwarded-Protohttp или https — app строит правильные ссылки
Connection ""Сброс Connection: close — иначе keepalive к upstream ломается

Что происходит по шагам (GET http://api.local/api/users):

  1. Nginx принимает запрос на :80.
  2. location / — проксирует весь путь на 127.0.0.1:3000/api/users.
  3. Node/Python отвечает JSON.
  4. Nginx возвращает тот же статус и тело клиенту.

Схема:

Браузер → :80 nginx → :3000 приложение
↑ proxy_set_header добавляет заголовки

Проверка (app уже слушает 3000):

# Терминал 1 — простой backend
python -m http.server 3000

# Терминал 2 — через nginx
curl -i http://api.local/

Что делает код:

  1. python -m http.server 3000 — временный HTTP-сервер для проверки (на лабораторной вместо него — ваш Express/FastAPI).
  2. curl -i — если nginx настроен, увидите ответ app через прокси (в access.log nginx — запись запроса).

API под префиксом /api/ — отдельный location и слэш в конце proxy_pass:

location /api/ {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $host;
}
Запрос клиентаЧто получит backend
/api/users/users
/api/health/health

Слэш после 3000/ отрезает префикс /api/ — это самый частый «тихий» баг на лабораторных (backend ждёт /api/users, а приходит /users или наоборот).

Частая ошибка: 502 Bad Gateway — app не запущен или слушает другой порт. Проверка: curl http://127.0.0.1:3000 минуя nginx.


5. PHP через PHP-FPM

Задача: .html и картинки — как файлы, .php — выполнить через PHP-FPM (Laravel, Symfony, WordPress, учебные скрипты).

Когда искать: «nginx php-fpm config», «nginx laravel public», «fastcgi_pass unix sock».

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

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

location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
}

location ~ /\. {
deny all;
}
}
СтрокаСмысл
root …/publicУ Laravel/Symfony открыт только каталог public/ — там index.php
index index.php index.htmlСначала PHP, потом HTML
try_files … /index.php?$query_stringFront controller — все «красивые» URL идут в index.php
location ~ \.php$Любой URL, заканчивающийся на .php
include fastcgi_paramsСтандартный набор параметров для PHP
fastcgi_pass unix:…sockСокет процесса PHP-FPM (путь зависит от версии PHP и ОС)
SCRIPT_FILENAMEПолный путь к скрипту на диске — обязательная строка
fastcgi_intercept_errors onОшибки PHP можно обработать через error_page в nginx
location ~ /\.Запрет .env, .git, .htaccess по HTTP

Что происходит по шагам (GET /index.php):

  1. Совпадает location ~ \.php$.
  2. Nginx не отдаёт файл как текст — передаёт в FPM через сокет.
  3. FPM выполняет PHP, возвращает HTML.
  4. Nginx отдаёт HTML клиенту.

Что происходит (GET /users/list в Laravel):

  1. location /try_files не находит файл.
  2. Внутренний запрос /index.php? (query из $query_string).
  3. Laravel роутит /users/list внутри приложения.
URLРезультат
/index.phpВыполнение PHP
/style.cssСтатический файл
/.env403 Forbidden (блок ~ /\.)
/index.php скачивается как файлСломан location ~ \.php$ или FPM не запущен

Проверка:

sudo systemctl status php8.3-fpm
curl -i http://php.local/index.php
ls -la /run/php/php8.3-fpm.sock

Частая ошибка: root указывает на корень репозитория, а не на public/ — в браузере открывается структура проекта, .env может стать доступен без правильного location ~ /\..


Конфиги для типовых задач

Сценарии для VPS, нескольких сайтов, HTTPS, WebSocket и защиты.


6. Два сайта на одном IP

Задача: один сервер, один IP, разные домены — shop.local и blog.local с разными папками.

Когда искать: «nginx несколько сайтов», «nginx virtual host», «nginx server_name».

server {
listen 80;
server_name shop.local;
root /var/www/shop;
index index.html;
location / { try_files $uri $uri/ =404; }
}

server {
listen 80;
server_name blog.local;
root /var/www/blog;
index index.html;
location / { try_files $uri $uri/ =404; }
}
СтрокаСмысл
Два блока serverДва «сайта» на одном порту 80
Разный server_nameNginx смотрит заголовок Host и выбирает блок
Разный rootФайлы магазина и блога не смешиваются

Что происходит по шагам:

  1. Оба блока слушают :80.
  2. Запрос с Host: shop.local → первый server, файлы из /var/www/shop.
  3. Запрос с Host: blog.local → второй server.

Проверка без DNS/etc/hosts или просто curl):

curl -H "Host: shop.local" http://127.0.0.1/
curl -H "Host: blog.local" http://127.0.0.1/

Частая ошибка: оба домена показывают один сайт — в /etc/hosts на клиенте неверный IP или сработал default_server (первый listen 80 default_server ловит чужие Host).


7. Балансировка между двумя app-серверами

Задача: распределить нагрузку между app1:8080 и app2:8080, временно отключать «упавший» инстанс.

upstream web_pool {
least_conn;
server app1:8080 max_fails=3 fail_timeout=30s;
server app2:8080 max_fails=3 fail_timeout=30s;
}

server {
listen 80;
server_name balanced.local;

location / {
proxy_pass http://web_pool;
proxy_set_header Host $host;
proxy_next_upstream error timeout http_502 http_503;
}
}
СтрокаСмысл
least_connЗапрос на сервер с меньшим числом активных соединений
max_fails=3После 3 неудачных попыток сервер считается недоступным
fail_timeout=30s30 с не слать на него трафик, потом проверить снова
proxy_next_upstream …При 502/503 повторить запрос на другом сервере из pool

Что происходит: nginx по очереди или по least_conn выбирает backend; если app1 вернул 502, клиент может получить ответ от app2 (если включён proxy_next_upstream).


8. HTTPS с Let's Encrypt (Certbot)

Задача: шифрование TLS после получения сертификата (certbot certonly или certbot --nginx).

server {
listen 443 ssl http2;
server_name example.com;

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

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

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

location / {
try_files $uri $uri/ =404;
}
}
СтрокаСмысл
listen 443 ssl http2HTTPS + мультиплексирование HTTP/2
fullchain.pemСертификат сайта + промежуточные CA
privkey.pemЗакрытый ключ (доступ только root/nginx)
ssl_protocols TLSv1.2 TLSv1.3Старые небезопасные версии SSL отключены

Сертификат Let's Encrypt живёт ~90 дней; certbot ставит timer для продления. После продления: sudo nginx -t && sudo systemctl reload nginx.

Проверка:

curl -I https://example.com
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates

9. WebSocket через reverse proxy

Задача: чат, live-лenta, Socket.io — соединение «апгрейдится» с HTTP до WebSocket.

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

server {
listen 80;
server_name ws.local;

location /ws {
proxy_pass http://127.0.0.1:3000;
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 86400;
}
}
СтрокаСмысл
map $http_upgrade …Если клиент шлёт Upgrade: websocketConnection: upgrade, иначе close
Upgrade / ConnectionОбязательные заголовки для WebSocket handshake
proxy_read_timeout 8640024 ч — прокси не рвёт «висящее» WS-соединение по умолчанию (60 с)

Что происходит: обычный GET с Upgrade: websocket nginx проксирует на backend; дальше идёт двусторонний поток сообщений.


10. Rate limit — защита от флуда

Задача: ограничить число запросов с одного IP на /login (brute-force, DDoS на уровне приложения).

# Эту строку — один раз внутри http { }, не внутри server { }
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=10r/s;

server {
listen 80;
server_name secure.local;

location /login {
limit_req zone=login_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}
СтрокаСмысл
limit_req_zoneСоздаёт «карту» лимитов в памяти (10 МБ под IP)
$binary_remote_addrКлюч — IP клиента в компактном виде
rate=10r/sВ среднем 10 запросов в секунду с IP
burst=20 nodelayРазрешить «всплеск» до 20, без очереди
limit_req zone=…Применить лимит в конкретном location

При превышении — 503 (можно сменить на 429: limit_req_status 429; в location).

Частая ошибка: limit_req_zone внутри servernginx -t выдаст directive is not allowed here.


11. Gzip и заголовки безопасности

Задача: уменьшить размер HTML/JSON/CSS и добавить базовые заголовки защиты браузера.

http {
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 256;

server {
listen 80;
server_name safe.local;
root /var/www/site;

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

location / {
try_files $uri $uri/ =404;
}
}
}
СтрокаСмысл
gzip onСжимать ответы, если клиент шлёт Accept-Encoding: gzip
gzip_min_length 256Мелкие ответы не сжимать (накладные расходы)
X-Frame-Options SAMEORIGINЗапрет встраивать сайт в <iframe> на чужих доменах (clickjacking)
X-Content-Type-Options nosniffБраузер не «угадывает» MIME
add_header … alwaysЗаголовок даже при 404/500

Проверка gzip:

curl -H "Accept-Encoding: gzip" -I http://safe.local/

В ответе должно быть Content-Encoding: gzip (для достаточно большого HTML).


12. Nginx в Docker — свой conf через volume

Задача: свой default.conf без пересборки образа. Дополнение к стекам Compose.

nginx-proxy/
compose.yaml
conf/
default.conf
html/
index.html

compose.yaml:

services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./conf/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./html:/usr/share/nginx/html:ro
СтрокаСмысл
"8080:80"localhost:8080 на ПК → порт 80 в контейнере
./conf/default.conf:…:roВаш конфиг поверх дефолтного в образе
./html:…:roСтатика с хоста, read-only

conf/default.conf:

server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;

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

Что делает код:

docker compose up -d
curl -I http://localhost:8080/
docker compose exec web nginx -t
  1. Поднимается контейнер с вашим conf и html.
  2. curl проверяет проброс порта.
  3. exec web nginx -t — синтаксис внутри контейнера (удобно при отладке mount).

Шпаргалка — слэш в proxy_pass

Самый частый вопрос на Stack Overflow и лабораторных:

locationproxy_passЗапрос клиентаURI на backend
/api/http://127.0.0.1:3000//api/users/users
/api/http://127.0.0.1:3000/api/users/api/users
/http://127.0.0.1:3000/users/users

Правило: если в proxy_pass есть URI (хотя бы / в конце) — nginx заменяет совпавший префикс location на этот URI. Если URI в proxy_pass нет — на backend уходит полный путь клиента.


Шпаргалка — root и alias

ДирективаURLПуть на диске
root /var/www; + location /img//img/a.png/var/www/img/a.png
alias /var/static/; + location /img//img/a.png/var/static/a.png

alias отрезает префикс location из пути; для /static/ часто пишут именно alias, а не root.


Каркас проверки — скрипт для лабораторной

Сохраните и подставляйте свой домен:

#!/bin/bash
DOMAIN="${1:-localhost}"
URL="http://${DOMAIN}/"

echo "=== nginx -t ==="
sudo nginx -t || exit 1

echo "=== HTTP HEAD ==="
curl -sI "$URL" | head -n 15

echo "=== последние ошибки ==="
sudo tail -n 5 /var/log/nginx/error.log

Что делает код:

  1. $1 — домен из аргумента (./check.sh shop.local) или localhost.
  2. nginx -t — выход с ошибкой, если конфиг битый.
  3. curl -sI — только заголовки, первые 15 строк (статус, Content-Type, Location).
  4. tail error.log — последние причины 502/403/permission denied.

Шпаргалка команд

ЗадачаКомандаПояснение
Проверка синтаксисаsudo nginx -tОбязательно перед reload
Перезагрузкаsudo systemctl reload nginxБез обрыва текущих соединений
Статусsystemctl status nginxactive (running) / failed
Ошибкиsudo tail -f /var/log/nginx/error.log502, permission denied, unknown directive
Запросыsudo tail -f /var/log/nginx/access.logIP, код, URL, User-Agent
Весь конфигsudo nginx -T 2>/dev/null | lessС комментариями # configuration file
Виртуальный Hostcurl -H "Host: shop.local" http://127.0.0.1/Тест vhost без DNS
Заголовки HTTPScurl -I https://example.comСертификат, редиректы, HSTS

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

ОшибкаСимптомКак исправить
Пропустили nginx -treload failed, сайт «старый» или downвсегда -t, читать stderr
Слэш в proxy_pass404 на backend, «route not found»таблица выше, сверить с API
root вместо alias/static/static/app.jsalias для префикса или поправить путь
PHP скачиваетсябраузер предлагает сохранить .phplocation ~ \.php$, запущен php-fpm
502 Bad Gatewayбелая страница nginxcurl напрямую на backend-порт
Permission denied403, в error.log «permission denied»www-data должен читать root
Два default_serverпо IP открывается «чужой» сайтодин listen 80 default_server
limit_req_zone в servernginx -t падаетзона только в http { }
SPA без fallbackF5 на /page → 404try_files … /index.html
127.0.0.1 в Docker502 из контейнера nginxимя сервиса compose

Практика — три мини-задания

  1. Статика. Пример №1: свой index.html, curl -i, в access.log должна появиться строка с кодом 200.
  2. Proxy. Python http.server 3000 + пример №4: сравните curl :3000 и curl через nginx — тело одинаковое, в proxy-варианте в access.log nginx есть запись.
  3. SPA. Положите dist/ Vite/React, пример №3: curl -I /random/path — 200 и text/html.

Подробный справочник директив — Nginx.


Чек-лист перед сдачей лабораторной

  • nginx -t — ok.
  • Для proxy есть Host, X-Real-IP, X-Forwarded-ForProto при HTTPS).
  • PHP — root на public/, .env закрыт.
  • Сертификаты на месте, ключ не в репозитории.
  • В отчёте — скрин или вывод curl -I, фрагмент конфига, что делает каждый блок.

Связанные материалы

МатериалЗачем открыть
Справочник по Nginxвсе директивы, кэш, FastCGI
Веб-серверыHTTP, виртуальные хосты
Docker Compose — готовые стекиnginx + volumes + proxy
curlзаголовки, POST, отладка API
SPAclient-side routing
Шаблоныкаркасы для других инструментов

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").