Настройка обратного прокси Nginx для сайта: руководство 2026

Обратный прокси — одна из тех вещей, без которых я уже не представляю нормальную серверную архитектуру. За 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-стека. На нагруженных сайтах это ощутимо.

⚠️
Версия PHP-FPM: путь к сокету зависит от версии PHP. Если у тебя PHP 8.1 — путь будет /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, смотришь на заголовки ответа и сразу видишь, работает ли кеш.

💡
Совет по инвалидации кеша: Для WordPress можно использовать плагин Nginx Helper, который автоматически сбрасывает кеш при публикации или обновлении поста. Для Bitrix есть отдельные решения через события ORM. Не забудь про это — иначе контент будет обновляться только когда истечёт время кеша.

Настройка балансировки нагрузки

Если проект вырос и один сервер уже не справляется — 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, который закрывает соединение без отправки ответа. Полезно против сканеров, которые не читают ответы.

ℹ️
Про блокировку по User-Agent: Это не серьёзная защита — любой нормальный злоумышленник поменяет User-Agent. Но это отсекает самых примитивных ботов и скриптов. Серьёзную защиту от DDoS обеспечивают Cloudflare, Qrator или аппаратные решения — 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 быстро и безопасно, обеспечив максимальную производительность вашего сайта.

П
Павел
Веб-разработчик · 10+ лет опыта · Bitrix, WordPress, Laravel

Читайте также

Настройка cron jobs: автоматические задачи на сайте в 2026 Корпоративный чат на сайте: настройка и интеграция 2026 Битрикс или WordPress: подробное сравнение