Обратный прокси — одна из тех вещей, без которых я уже не представляю нормальную серверную архитектуру. За 10+ лет я настраивал его десятки раз: для WordPress, Bitrix, Laravel-проектов, Node.js-сервисов — и каждый раз убеждаюсь, что правильно сконфигурированный Nginx как обратный прокси решает сразу кучу задач одним файлом конфига.
Что такое обратный прокси и зачем он нужен
Грубо говоря, обратный прокси — это посредник между клиентом и вашим приложением. Клиент (браузер) стучится на Nginx, а Nginx уже передаёт запрос дальше — на Apache, PHP-FPM, Node.js, Gunicorn, или вообще на другой сервер в локальной сети. Клиент при этом ничего не знает про то, что творится за кулисами.
Зачем это нужно? Во-первых, безопасность: бэкенд-приложение вообще не торчит в интернет напрямую, снаружи виден только Nginx. Во-вторых, производительность: Nginx умеет отдавать статику молниеносно, кешировать ответы, сжимать трафик. В-третьих, гибкость: можно за одним IP-адресом спрятать несколько приложений на разных портах, распределять нагрузку между несколькими бэкендами, делать A/B-тестирование.
У меня был клиент с интернет-магазином на Bitrix, который жил на Apache. Сайт при нагрузке 200+ одновременных пользователей начинал тормозить — Apache плодил процессы как бешеный, память заканчивалась. Поставили Nginx перед Apache как обратный прокси, настроили кеширование статики и FastCGI-кеш — PageSpeed вырос с 54 до 87 баллов, потребление памяти упало вдвое. Вот что умеет правильно настроенный reverse proxy.
Если вас интересует тема производительности в целом, советую почитать мою статью про настройку HTTP/2 и HTTP/3 — там много пересекается с тем, о чём я буду говорить здесь.
Установка и базовая структура конфига Nginx
Я работаю преимущественно на Ubuntu 22.04 и 24.04. На них Nginx ставится элементарно:
sudo apt update
sudo apt install nginx
sudo systemctl enable nginx
sudo systemctl start nginx
Но я почти никогда не беру Nginx из стандартных репозиториев Ubuntu — там версии устаревшие. Лучше подключить официальный репозиторий nginx.org, там будет актуальная стабильная ветка (сейчас это 1.26.x). Разница может быть существенная: поддержка HTTP/3, улучшенный TLS, новые директивы.
Структура конфигов у меня всегда одна и та же. Главный файл — /etc/nginx/nginx.conf, отдельные сайты — в /etc/nginx/sites-available/, оттуда симлинки в /etc/nginx/sites-enabled/. Никогда не редактируй nginx.conf напрямую под каждый сайт — это плохая идея, которую потом сложно распутать.
В nginx.conf я обычно выставляю глобальные параметры: количество воркеров, размеры буферов, таймауты. Вот минимальный рабочий nginx.conf для сервера с 4 ядрами и 8 ГБ RAM:
user www-data;
worker_processes 4;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Логи
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml
application/xml+rss text/javascript image/svg+xml;
include /etc/nginx/sites-enabled/*;
}
server_tokens off — обязательно. Зачем показывать миру версию Nginx? Это лишняя информация для потенциального злоумышленника.
Настройка основного reverse proxy блока
Теперь самое главное — конфиг виртуального хоста с обратным прокси. Покажу на примере типичной задачи: у нас есть PHP-приложение (Laravel или Bitrix), которое работает через PHP-FPM на порту 9000, и нам нужно, чтобы Nginx проксировал запросы к нему.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Редирект на HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
# SSL
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_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Заголовки безопасности
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Статика — отдаём напрямую, без проксирования
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|webp|avif)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
# Проксирование PHP через PHP-FPM
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
}
# Для Laravel / одностраничных приложений
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Запрет доступа к скрытым файлам
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
Обрати внимание на fastcgi_pass unix:/run/php/php8.2-fpm.sock — я использую Unix-сокет вместо TCP-порта 127.0.0.1:9000. Unix-сокет быстрее примерно на 10-15% за счёт отсутствия накладных расходов TCP-стека. На нагруженных сайтах это ощутимо.
/run/php/php8.1-fpm.sock, для PHP 8.3 — /run/php/php8.3-fpm.sock. Проверяй через ls /run/php/ перед тем, как писать конфиг. Половина проблем "Nginx не работает с PHP" — именно из-за неверного пути к сокету.Проксирование на сторонние сервисы и другие порты
Это, пожалуй, самый частый сценарий, с которым ко мне приходят. Клиент говорит: "У нас Node.js-приложение крутится на порту 3000, нам нужно, чтобы оно было доступно на example.com без порта в URL". Или: "У нас несколько микросервисов на разных портах — нужно раздать их по путям /api/, /admin/, /app/".
Вот конфиг для проксирования на Node.js/Python/Java-приложение, которое слушает на локальном порту:
upstream nodejs_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name app.example.com;
# SSL конфиг (аналогично предыдущему примеру)
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Размеры буферов для проксирования
proxy_buffers 16 4k;
proxy_buffer_size 2k;
location / {
proxy_pass http://nodejs_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 Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Статика отдаётся напрямую
location /static/ {
alias /var/www/app/static/;
expires 30d;
add_header Cache-Control "public";
}
}
Директива upstream — это не просто синтаксический сахар. Она позволяет указать несколько бэкендов для балансировки нагрузки. Добавишь ещё одну строку server 127.0.0.1:3001; — и Nginx начнёт round-robin между двумя инстансами. Это уже элементарная балансировка нагрузки без дополнительного ПО.
Заголовки X-Real-IP и X-Forwarded-For — обязательны. Без них твоё приложение будет видеть в логах IP-адрес 127.0.0.1 вместо реального IP клиента. Я однажды потратил час, разбираясь, почему у клиента все запросы в Bitrix показывают один и тот же IP — оказалось, именно эти заголовки не были настроены на проксирующем Nginx.
Заголовки Upgrade и Connection: upgrade нужны для WebSocket-соединений. Если у тебя чат, live-уведомления или что-то похожее — без них WebSocket не заработает через прокси.
Кеширование на уровне Nginx
Вот где начинается настоящая магия. Nginx умеет кешировать ответы бэкенда прямо на диске — это называется proxy_cache или FastCGI cache. При правильной настройке можно обслуживать тысячи запросов в секунду вообще без обращения к PHP или базе данных.
По теме кеширования у меня есть отдельная статья про Redis и Memcached — там про кеш на уровне приложения. Здесь же поговорим про кеш на уровне Nginx.
Сначала нужно объявить зону кеша в блоке http в nginx.conf:
http {
# Объявляем зону кеша: путь, название, размер ключей в памяти, макс. размер на диске
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=PROXY_CACHE:100m
max_size=10g
inactive=60m
use_temp_path=off;
fastcgi_cache_path /var/cache/nginx/fastcgi
levels=1:2
keys_zone=FASTCGI_CACHE:100m
max_size=10g
inactive=60m
use_temp_path=off;
}
Теперь используем в конфиге сайта:
server {
# ... остальной конфиг ...
# Переменная для обхода кеша
set $skip_cache 0;
# Не кешируем POST-запросы
if ($request_method = POST) {
set $skip_cache 1;
}
# Не кешируем запросы с параметрами (корзина, поиск)
if ($query_string != "") {
set $skip_cache 1;
}
# Не кешируем для авторизованных пользователей
if ($http_cookie ~* "wordpress_logged_in|BITRIX_SM_LOGIN|laravel_session") {
set $skip_cache 1;
}
# Не кешируем страницы корзины и аккаунта
if ($request_uri ~* "/cart|/checkout|/my-account|/wp-admin|/bitrix/admin") {
set $skip_cache 1;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
# FastCGI кеш
fastcgi_cache FASTCGI_CACHE;
fastcgi_cache_valid 200 301 302 1h;
fastcgi_cache_valid 404 1m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout updating invalid_header http_500;
fastcgi_cache_lock on;
# Заголовок для дебага — видно, попали ли в кеш
add_header X-FastCGI-Cache $upstream_cache_status;
}
}
Заголовок X-FastCGI-Cache в ответе будет показывать HIT, MISS или BYPASS — очень удобно при отладке. Открываешь DevTools, смотришь на заголовки ответа и сразу видишь, работает ли кеш.
Настройка балансировки нагрузки
Если проект вырос и один сервер уже не справляется — Nginx обратный прокси легко превращается в балансировщик нагрузки. Это одна из ситуаций, где я особенно ценю такую архитектуру: не нужно ставить отдельный HAProxy или платить за облачный балансировщик.
У меня был проект — новостной портал на Laravel, около 50 000 посетителей в сутки с пиками во время крупных событий. Один сервер не справлялся. Мы сделали два одинаковых application-сервера за Nginx-балансировщиком. Конфиг выглядел так:
upstream app_servers {
# Метод балансировки — least_conn (наименее загруженный сервер)
least_conn;
server 10.0.0.10:80 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.11:80 weight=1 max_fails=3 fail_timeout=30s;
server 10.0.0.12:80 backup; # Запасной сервер, включается при падении основных
keepalive 64;
}
server {
listen 443 ssl http2;
server_name example.com;
# SSL конфиг...
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
# Retry при ошибках
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 3;
}
}
Параметр weight задаёт вес сервера при балансировке. В примере первый сервер получает вдвое больше запросов — это полезно, если серверы неодинаковые по мощности. max_fails и fail_timeout настраивают автоматическое исключение упавшего сервера из ротации.
Nginx поддерживает несколько алгоритмов балансировки: round_robin (по умолчанию), least_conn (к наименее загруженному), ip_hash (один клиент всегда идёт на один сервер — нужно для сессий), random. Для большинства задач я использую least_conn — он лучше распределяет нагрузку при неравномерных запросах.
Тема отказоустойчивости тесно связана с балансировкой. Если тебе нужно копнуть глубже, почитай мою статью о настройке отказоустойчивости и failover — там разбираю более сложные схемы с резервированием.
Защита и лимитирование запросов
Обратный прокси — отличное место для первой линии защиты. Nginx умеет ограничивать частоту запросов, блокировать ботов, скрывать внутреннюю структуру приложения. Это не замена файрволу, но существенное дополнение к нему.
Про более комплексную защиту я писал в статье про настройку Firewall для сайта — рекомендую прочитать в паре с этой статьёй.
Rate limiting — первое, что я настраиваю на любом новом сервере:
http {
# Зоны для rate limiting
# Лимит для API — 10 запросов в секунду на IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# Лимит для логина — 5 запросов в минуту на IP
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
# Лимит для общих запросов
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s;
# Лимит соединений
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
server {
# Применяем лимиты
limit_conn conn_limit 20;
location / {
limit_req zone=general_limit burst=50 nodelay;
# ... proxy_pass ...
}
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://api_backend;
}
location ~* /wp-login\.php|/admin/login|/bitrix/admin {
limit_req zone=login_limit burst=5;
# ... fastcgi_pass ...
}
# Блокировка по User-Agent (грубые боты)
if ($http_user_agent ~* (scrapy|python-requests|curl|wget|nikto|sqlmap)) {
return 403;
}
# Блокировка прямых обращений по IP без Host-заголовка
if ($host = "") {
return 444;
}
}
Параметр burst задаёт максимальный всплеск запросов сверх лимита. nodelay означает, что запросы в пределах burst обрабатываются немедленно, а не ставятся в очередь. Код ответа 444 — это специальный код Nginx, который закрывает соединение без отправки ответа. Полезно против сканеров, которые не читают ответы.
Типичные ошибки и отладка
За годы практики я собрал личную коллекцию граблей, на которые наступаю или вижу, как наступают другие.
502 Bad Gateway — самая частая ошибка при настройке обратного прокси. Причины: бэкенд не запущен, неверный сокет или порт, бэкенд упал под нагрузкой, истёк fastcgi_read_timeout. Диагностика: tail -f /var/log/nginx/error.log и смотришь, что пишет Nginx. Потом systemctl status php8.2-fpm или ps aux | grep node — проверяешь, живо ли приложение.
Бесконечный редирект — часто случается, когда приложение само делает редирект с HTTP на HTTPS, не зная, что Nginx уже это сделал. Решение: передавать заголовок X-Forwarded-Proto и настроить приложение его читать. В Laravel это делается через $request->isSecure() с проверкой этого заголовка. В Bitrix — через конфиг сайта в административной панели.
Большие файлы не загружаются — нужно увеличить client_max_body_size. По умолчанию Nginx ограничивает тело запроса до 1 МБ. Для сайтов с загрузкой файлов я обычно ставлю:
server {
client_max_body_size 256m;
client_body_timeout 120s;
client_header_timeout 120s;
}
Медленные ответы при проксировании — проверь, включён ли proxy_buffering. Если бэкенд отправляет ответ медленно, Nginx с буферизацией сначала получит весь ответ, а потом отдаст клиенту. Без буферизации (proxy_buffering off) Nginx передаёт данные потоком — это нужно для SSE (Server-Sent Events) и стриминга, но для обычных запросов буферизация лучше.
Проверка синтаксиса конфига — всегда перед перезагрузкой делай:
# Проверка синтаксиса
sudo nginx -t
# Если всё OK — мягкая перезагрузка без разрыва соединений
sudo nginx -s reload
# Посмотреть текущий конфиг с include-файлами
sudo nginx -T | less
Никогда не делай sudo systemctl restart nginx на продакшне без крайней необходимости. Команда nginx -s reload выполняет graceful reload — старые воркеры дообрабатывают текущие запросы, новые уже работают с новым конфигом. Restart же убивает всё мгновенно.
Настройка для Docker-контейнеров
Сейчас всё больше проектов живёт в Docker, и обратный прокси тут становится ещё важнее. Nginx на хост-машине (или в отдельном контейнере) проксирует запросы к контейнерам с приложениями.
Если используешь Docker Compose, типичная схема: контейнер с Nginx слушает порты 80/443, контейнеры с приложениями — только во внутренней сети Docker, наружу не торчат. Вот пример для Laravel-приложения в Docker:
upstream laravel_app {
server app:9000; # "app" — имя сервиса в docker-compose.yml
}
server {
listen 80;
server_name example.com;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass laravel_app;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
}
Подробнее про Docker-окружение я разбираю в статье про настройку Docker-контейнеризации — там есть полный docker-compose.yml с Nginx, PHP-FPM и MySQL.
Отдельно стоит упомянуть Traefik как альтернативу Nginx для Docker-окружений. Traefik умеет автоматически обнаруживать контейнеры и настраивать проксирование через лейблы. Но по производительности и гибкости я всё равно предпочитаю Nginx — у него больше возможностей для тонкой настройки кеширования и rate limiting.
Мониторинг и логи обратного прокси
Правильно настроенные логи — это половина диагностики. По умолчанию Nginx пишет довольно скудно. Я обычно настраиваю расширенный формат логов, который включает время обработки запроса, статус кеша и апстрим-сервер:
http {
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time '
'cs=$upstream_cache_status us=$upstream_addr';
access_log /var/log/nginx/access.log detailed;
}
Поля upstream_connect_time, upstream_header_time, upstream_response_time — бесценны при диагностике тормозов. Смотришь лог и сразу видишь: если urt большое — тормозит бэкенд, если rt большое при маленьком urt — тормозит сеть или сам Nginx.
Для анализа логов в реальном времени я использую GoAccess — лёгкий и быстрый анализатор. Запускается прямо в терминале: goaccess /var/log/nginx/access.log --log-format=COMBINED -o report.html. За 10 секунд получаешь полную картину трафика.
Если хочешь серьёзный мониторинг — смотри в сторону Prometheus + nginx-exporter + Grafana. Но это уже отдельная большая тема, которую я разбираю в статье про настройку логов и мониторинг ошибок.
Если тебе нужна помощь с настройкой серверной инфраструктуры или ты хочешь отдать это на аутсорс — посмотри на поддержку Bitrix или поддержку WordPress. Я занимаюсь не только CMS, но и серверной частью — Nginx, PHP-FPM, Redis, весь стек.
Обратный прокси Nginx — это не rocket science, но требует понимания того, что происходит на каждом уровне стека. Потрать время на правильную настройку один раз — и получишь стабильный, быстрый и защищённый сервер, который не будет будить тебя в 3 ночи с ошибкой 502.
Нужна помощь с настройкой Nginx для вашего сервера?
Наши специалисты настроят обратный прокси Nginx быстро и безопасно, обеспечив максимальную производительность вашего сайта.