Иногда нужно опубликовать внутренний сервис наружу — админку, API для партнеров, технический backend. Открывать его по обычному паролю через интернет страшновато: брутфорс, утечки, неаккуратные интеграторы. Хороший компромисс — пропускать трафик только через reverse proxy с жесткими правилами: TLS, mTLS и IP-whitelist.
В этой статье разберем, как настроить reverse proxy на nginx, который:
- терминирует TLS на границе;
- по необходимости требует клиентский сертификат (mTLS);
- ограничивает доступ по списку доверенных IP (IP-whitelist);
- аккуратно проксирует запросы на внутренний backend.
Ориентируемся на Linux-сервер (чаще всего это будет VDS), nginx из официальных пакетов дистрибутива и уже выпущенный серверный TLS-сертификат.
Что такое reverse proxy и зачем он здесь нужен
Reverse proxy (обратный прокси) в классической схеме стоит перед вашим приложением и принимает все внешние HTTP(S)-запросы, а уже затем пересылает их на внутренний backend (обычно по HTTP). Для нас это удобная точка концентрации безопасности.
Идея проста: весь трафик снаружи видит только nginx, а backend видит только nginx. Все проверки доступа, TLS и логирование делаются на границе.
Плюсы такого подхода:
- Централизация TLS. Сертификаты, шифросuites и протоколы настраиваются один раз в nginx, backend работает по простому HTTP.
- Дополнительные уровни защиты. IP-whitelist, mTLS, лимиты, WAF — все поднимается на уровне reverse proxy.
- Упрощение миграций. Можно менять backend, не трогая внешнюю точку входа.
Дальше будем исходить из схемы:
- nginx слушает 443/tcp;
- backend слушает, например, на 127.0.0.1:8080 или внутреннем IP сети;
- к внешнему порту 443 применяем TLS, mTLS и IP-whitelist, как потребуется.
Базовая конфигурация reverse proxy в nginx
Начнем с простого HTTPS reverse proxy без mTLS и IP-фильтрации, чтобы была опорная точка.
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
# Минимальные параметры TLS (упрощены для примера)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
После этого достаточно перезагрузить nginx и вы уже получили корректный reverse proxy. Но по безопасности это пока обычный HTTPS-сайт.
Если вы только поднимаете инфраструктуру под такой сценарий, рационально сразу разместить его на управляемом виртуальном хостинге или отдельном VDS, чтобы разделить фронт безопасности и остальной прод.
IP-whitelist: доступ только с доверенных адресов
Самый простой барьер — ограничить доступ списком IP-адресов. Для внутренних админок и B2B-интеграций это часто уже дает большой выигрыш: даже если логин/пароль или токен утекут, сторонний клиент с произвольного IP зайти не сможет.
Базовый вариант через allow/deny
Классический способ — директивы allow и deny внутри server или location:
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
# Сначала доступные IP/подсети
allow 192.0.2.10; # офис
allow 198.51.100.0/24; # сеть партнера
allow 203.0.113.5; # отдельный сервер
# Все остальные запретить
deny all;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
Порядок важен: allow оцениваются сверху вниз, в конце — deny all. Если одновременно нужны IPv4 и IPv6, добавьте отдельные строки с соответствующими подсетями.
Где ставить IP-фильтрацию: server или location
Типичный вопрос — вешать IP-whitelist на весь виртуальный хост или только на некоторые пути:
- На уровне server — все запросы к домену будут жестко фильтроваться по IP.
- На уровне location — можно, например, открыть
/public/всем, а/admin/— только доверенным IP.
Пример смешанного варианта:
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/nginx/ssl/app.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/app.example.com.key;
location /public/ {
proxy_pass http://127.0.0.1:8080;
}
location /admin/ {
allow 192.0.2.0/24;
allow 203.0.113.10;
deny all;
proxy_pass http://127.0.0.1:8081;
}
}
Так вы выносите только критические участки за отдельный IP-barrier.
Особенности с прокси и CDN
Если перед nginx уже стоит внешний прокси или CDN, непосредственно к нему приходит не клиентский IP, а IP этого внешнего узла. В таком случае allow/deny должны применяться не к $remote_addr, а к реальному клиентскому IP, который передается в заголовке (например, X-Real-IP или X-Forwarded-For).
В классическом nginx это решается через модуль realip, но это отдельная тема; важно помнить: если вы фильтруете IP, убедитесь, что фильтруете именно то значение, которое соответствует реальному клиенту, а не адресу предыдущего прокси.

mTLS: контроль доступа по клиентским сертификатам
IP-whitelist хорошо защищает от случайных атак, но не контролирует, кто именно подключается из доверной сети. Для B2B-интеграций и машинного доступа намного надежнее требовать клиентский сертификат — это и есть mTLS (mutual TLS), взаимная TLS-аутентификация.
Схема:
- nginx предъявляет свой серверный сертификат клиенту;
- клиент предъявляет свой сертификат в ответ;
- nginx проверяет его подпись корневым/промежуточным CA, срок действия и, при необходимости, CN/SAN;
- при успехе запрос пропускается к backend.
Минимальный конфиг mTLS на входе
Предположим, у вас есть:
- серверный сертификат для домена (его можно оформить через коммерческие SSL-сертификаты или собственный PKI);
- CA-файл (или цепочка CA), которым подписаны клиентские сертификаты.
Простейшая настройка mTLS в server выглядит так:
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# mTLS: доверяем только сертификатам, выпущенным этим CA
ssl_client_certificate /etc/nginx/ssl/client_ca.pem;
ssl_verify_client on;
# Опционально: глубина цепочки
ssl_verify_depth 2;
location / {
proxy_pass http://127.0.0.1:8080;
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;
# Прокидываем инфу о клиентском сертификате в backend, если нужно
proxy_set_header X-Client-Cert-Subject $ssl_client_s_dn;
proxy_set_header X-Client-Cert-Issuer $ssl_client_i_dn;
proxy_set_header X-Client-Cert-Verify $ssl_client_verify;
}
}
Директива ssl_verify_client on говорит nginx: «без валидного клиентского сертификата запрос не пропускать». В таком режиме браузер, не имеющий подходящего сертификата, увидит ошибку TLS ещё до того, как дойдет до HTTP-уровня.
Гибкий режим: optional и валидация по переменным
Иногда надо, чтобы mTLS не был обязан для всего трафика, а только для части путей (например, для /partner-api/). Для этого есть режим optional:
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_client_certificate /etc/nginx/ssl/client_ca.pem;
ssl_verify_client optional;
location /public/ {
proxy_pass http://127.0.0.1:8080;
}
location /partner-api/ {
if ($ssl_client_verify != "SUCCESS") {
return 403;
}
proxy_pass http://127.0.0.1:8081;
}
}
Режим optional позволяет nginx принимать как соединения с сертификатом, так и без него, а уже в конкретном location вы можете проверять переменные типа $ssl_client_verify, $ssl_client_s_dn и решать, пускать ли запрос дальше.
Проверка конкретных сертификатов по DN или SAN
Если у вас несколько клиентов и вы хотите давать разный уровень доступа в зависимости от сертификата, можно использовать значения $ssl_client_s_dn (Subject DN) или $ssl_client_s_dn_legacy и сопоставлять их через map или if.
Пример: даем доступ только сертификатам, у которых CN равен partner-a или partner-b:
map $ssl_client_s_dn $partner_allowed {
default 0;
~CN=partner-a 1;
~CN=partner-b 1;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_client_certificate /etc/nginx/ssl/client_ca.pem;
ssl_verify_client on;
location /partner-api/ {
if ($partner_allowed = 0) {
return 403;
}
proxy_pass http://127.0.0.1:8080;
}
}
Такой подход удобен, если CA общая, а конкретные клиенты различаются по DN.
Для сценариев миграции старых API на новые домены и схемы аутентификации полезно заранее продумать редиректы и HSTS — об этом подробнее в материале про миграцию домена с 301, HSTS и SSL.
Комбинируем mTLS и IP-whitelist
На практике часто хочется не выбирать одно из двух, а совместить: пустить трафик только из конкретной сети (офис, VPN, площадка партнера) и при этом потребовать валидный клиентский сертификат. Это заметно снижает риск, что сертификат утечет и будет использован с произвольного IP.
Комбинированный пример:
server {
listen 443 ssl http2;
server_name secure-api.example.com;
ssl_certificate /etc/nginx/ssl/secure-api.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/secure-api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
# mTLS
ssl_client_certificate /etc/nginx/ssl/client_ca.pem;
ssl_verify_client on;
# IP-whitelist
allow 192.0.2.0/24;
allow 203.0.113.10;
deny all;
location / {
proxy_pass http://127.0.0.1:8080;
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-Client-Cert-Subject $ssl_client_s_dn;
proxy_set_header X-Client-Cert-Verify $ssl_client_verify;
}
}
Такой reverse proxy уже довольно хорошо защищен, особенно если:
- корневой CA надежно хранится и используется только для выпуска клиентских сертификатов;
- доступ к внутренней сети ограничен (VPN, firewall);
- есть мониторинг ошибок доступа (403 и TLS-ошибок) в логах.

Логирование и отладка доступа
Когда вы вводите несколько уровней защиты (IP, mTLS, авторизация на backend), становится важно быстро понимать, где именно отказ. Для этого полезно:
- отдельный access_log для защищенного виртуального хоста;
- расширенный формат логов с указанием IP и статуса валидации сертификата;
- понятные коды ответов (403/401 вместо 500).
Пример расширенного формата:
log_format secure_api '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'client_verify=$ssl_client_verify '
'subject="$ssl_client_s_dn"';
server {
listen 443 ssl http2;
server_name secure-api.example.com;
access_log /var/log/nginx/secure-api.access.log secure_api;
# ... остальная конфигурация ...
}
В таких логах сразу видно, прошел ли клиент проверку сертификата и какой DN у него был. Это сильно помогает при разборе, почему партнер внезапно перестал ходить в API.
Типичные грабли и как их обойти
1. Клиент не отправляет сертификат
Если вы включили ssl_verify_client on, но клиент не умеет работать с клиентскими сертификатами (например, это браузер без настроек или библиотека без mTLS), соединение просто не установится. Частый симптом — «SSL handshake failed» в логах, а на стороне клиента — ошибка TLS без понятного HTTP-кода.
Решения:
- явно проговорить с интегратором, что нужен mTLS, и выдать инструкцию по настройке;
- если частичный трафик должен ходить без mTLS, использовать
optionalплюс проверки вlocation.
2. Проблемы с цепочкой CA
Если указать в ssl_client_certificate не тот CA, которым на самом деле подписаны клиентские сертификаты, nginx будет считать их невалидными. Важно, чтобы этот файл содержал корректную цепочку доверия вплоть до корня (или того уровня, который вы реально используете).
Проверьте сертификат клиента через openssl, чтобы убедиться, кем он подписан, и сопоставьте это с содержимым файла CA.
3. Сложные IP-схемы и реальный клиентский IP
Когда сеть сложна (VPN, несколько уровней прокси, CDN), легко начать фильтровать по неправильному IP-адресу. Убедитесь, что:
- на крайний внешний reverse proxy смотрит интернет;
- если перед ним еще что-то, то механизм подстановки реального IP уже настроен (realip, заголовки, доверенные адреса);
- вы точно понимаете, какая переменная в nginx отражает реальный клиентский IP (
$remote_addrили что-то на основеX-Forwarded-For).
4. Граница между TLS-входом и TLS к backend
Иногда хотят включить TLS не только снаружи, но и между nginx и backend. В этом случае mTLS можно сделать и на этом участке (см. варианты с proxy_ssl_*, которые позволяют nginx аутентифицироваться к upstream по сертификату клиента). Главное — не спутать два разных уровня: mTLS на входе к reverse proxy и mTLS от reverse proxy к backend — это независимые механизмы.
Минимальный чеклист по безопасности для reverse proxy
Чтобы такой reverse proxy не превратился в источник проблем, держите под рукой короткий чеклист:
- Используйте актуальную версию nginx с поддержкой современных TLS-функций.
- Отключите устаревшие протоколы и шифросuites, оставьте TLS 1.2 и TLS 1.3.
- Храните серверные и клиентские ключи в защищенной директории с минимальными правами доступа.
- Отдельно логируйте события отказа доступа (403, 401, ошибки TLS) и мониторьте их динамику.
- Регулярно ревизируйте IP-whitelist: удаляйте неиспользуемые подсети/адреса, проверяйте актуальность.
- Ведите учет выданных клиентских сертификатов: кто получил, когда истекает, как отзывать.
Выводы
Reverse proxy на nginx — удобная и относительно несложная точка управления доступом к вашим внутренним сервисам. Комбинируя TLS, mTLS и IP-whitelist, вы можете построить многоуровневую защиту без радикальной переделки backend-приложения.
Практическая стратегия может выглядеть так:
- Сначала поднимаем базовый HTTPS reverse proxy.
- Добавляем IP-whitelist для реально критичных путей (админка, служебные API).
- Для внешних партнерских интеграций вводим mTLS, по возможности в связке с IP-ограничениями.
- Настраиваем подробное логирование и мониторинг, чтобы видеть попытки несанкционированного доступа.
Такой подход хорошо масштабируется: вы можете начинать с простого фильтра по IP, а затем постепенно усиливать безопасность, не ломая существующих клиентов. Главное — аккуратно документировать все договоренности по сертификатам и адресам, чтобы и вы, и ваши интеграторы понимали, как устроен доступ к сервису.


