Разберём, что такое Reverse Proxy. А также я покажу как настроить Nginx в качестве Reverse Proxy (обратного прокси сервера).

Теоретическая часть

Иногда бывает нужно чтобы различные url запросы обрабатывались на разных серверах, но первоначально приходили на один сервер. Например, вы пробросили порт на своем роутере на один веб-сервер в вашей внутренней сети. Но хотите чтобы каталог /xxx открывался на втором веб-сервере, а /yyy открывался на третьем, и не хотите пробрасывать порты на каждый web-сервер. Получается вот такая схема:

Схема работы Nginx - Reverse Proxy
Nginx — Reverse Proxy

То что мы хотим сделать из сервера web1, называется reverse proxy (обратный прокси).

В то время как forward proxy (прямой прокси) работают от имени клиентов, то есть нам нужно настраивать браузер (клиент), чтобы тот ходил через прямой прокси сервер. Обратные же прокси работают от имени серверов, то есть клиент не знает, что он обращается на прокси сервер.

Reverse proxy принимает запросы от клиентов для того чтобы проксировать их на проксируемые web-сервера (как показано на рисунке).

В качестве обратного прокси сервера для веб серверов может выступать apace или nginx. Есть и другие решения, но в этой статье разберём настройку nginx. На официальном сайте nginx есть небольшой раздел, посвящённый reverse proxy.

Схема сети

Я буду использовать операционную систему Devuan, это облегченный вариант Debian без systemd. Про установку Devuan я уже писал здесь.

У нас будет три сервера:

  • proxy — reverse proxy — установлен nginx;
  • web1 — backend web server — установлен apache2;
  • web2 — backend web server — установлен apache2;

Настраиваем Nginx (Revers Proxy)

Устанавливаем nginx на сервере proxy:

# apt install -y nginx

У меня установился nginx такой версией:

# nginx -v
nginx version: nginx/1.14.2

Для настройки прокси создадим новый виртуальный хост:

# nano /etc/nginx/sites-available/proxy
server {
        # Основные настройки
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name proxy;

        # Управление заголовками на прокси сервере
        proxy_set_header X-Scheme http;
        proxy_set_header X-Forwarded-Proto http;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Real-IP $remote_addr;

        # настройка буфера для прокси сервера
        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;

        location / {
                # вначале попытаемся обработать запрос как файл,
                # затем как каталог, затем вернём ошибку 404
                try_files $uri $uri/ =404;
        }
        
        # проксируем запрос /xxx на web1
        location /xxx {
                proxy_pass http://web1/test/;
        }
        
        # проксируем запрос /yyy на web2
        location /yyy {
                proxy_pass http://web2/test/;
        }
}

После чего отключим конфиг «default«, включим созданный нами конфиг «proxy» и перезагрузим службу сервера:

# rm /etc/nginx/sites-enabled/default
# ln -s /etc/nginx/sites-available/proxy /etc/nginx/sites-enabled/proxy
# service nginx restart

На основных настройках я не буду заострять внимание, потому как они не относятся к настройке reverse proxy. Если вкратце там мы указали какие порты слушает наш сервер, корень сайта, индексные страницы и имя сервера.

Далее рассмотрим те опции, которые относятся к самому проксированию.

Заголовки X-Scheme и X-Forwarded-Proto

При проксировании мы можем менять заголовки, чтобы backend сервер правильно обрабатывал запросы.

Вначале нужно поменять схему и протокол в запросе, если у вас backend сервер работает на одном протоколе (например http), а к proxy подключаются по другому протоколу (например https). То-есть в заголовках X-Scheme и X-Forwarded-Proto нужно указать протокол подключения к backend серверу.

Схема и протокол это разные понятия. То что вы пишите в браузере (http или https) — это схема. А уже схема указывает браузеру по какому протоколу подключаться к серверу.

В общем, чтобы поменять значения этих заголовков, мы используем следующие параметры в конфиге:

proxy_set_header X-Scheme http;
proxy_set_header X-Forwarded-Proto http;

Заголовок host

Заголовок host, один из самых важных заголовков. Он определяет адрес домена, который запрашивает браузер. Сервер, получив запрос, ищет у себя сайт с доменом из заголовка host, а также указанную страницу.

Подменить этот заголовок можно используя следующие переменные:

  • $http_host — это то что указано в адресной строке вместе с портом, если он указан в адресной строке. Это работает только если порт не стандартный, например 8080. В нашем случае это proxy;
  • $host — это имя прокси сервера. Оно записано в /etc/nginx/sites-available/proxy, в параметре server_name. В нашем случае proxy;
  • $proxy_host — это имя backend сервер на который мы проксируем. В нашем случае это web1 или web2.

В конфиге мы указали следующее:

proxy_set_header Host $http_host;

Заголовок X-Forwarded-For

Заголовок X-Forwarded-For содержит список прокси серверов по которым прошёлся клиент перед этим сервером, а переменная $proxy_add_x_forwarded_for содержит полученный заголовок X-Forwarder-For плюс добавляет свой сервер в этот список (это используется для передачи реального ip-клиента на backend).

В конфиге мы указали следующее:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Заголовки X-Real-PORT и X-Real-IP

Эти заголовки содержать содержат ip адрес и порт с которого подключается клиент. Чтобы передать эти значения backend серверу, мы указали следующие параметры:

proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Real-IP $remote_addr;

Параметры отвечающие за буферную память

А следующие строки отвечают за настройку работы буферной памяти для проксируемой информации:

  • proxy_buffering — позволяет включить или отключить буферную память для прокси сервера;
  • proxy_buffer_size — размер буфера для первой части ответа получаемого от проксируемого сервера, такая часть ответа включает в себя только заголовки и хранится отдельно от остальной информации;
  • proxy_buffers — число и размер буферов для одного соединения, а вот сюда помещается ответ от проксируемого сервера.
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 8k;

Что и куда проксируем

Теперь разберем часть конфига, где мы указываем что проксировать и куда проксировать:

location /xxx {
        proxy_pass http://web1/test/;
}

location /yyy {
        proxy_pass http://web2/test/;
}
  • /xxx — запрос который будем проксировать;
  • proxy_pass http://web1/test/ — куда будем проксировать, то есть на сервер web1 и на каталог /test.
  • Ниже подобный блок для /yyy .

Настраиваем Apache2 (backend сервера)

На обоих серверах проделаем одно и тоже! Во-первых установим apache2, затем создадим новый каталог /var/www/html/test. В этом каталоге сделаем индексную страничку index.html в которую запишем html код. Вдобавок поменяем владельца нового каталога на www-data.

Проделываем следующее на обоих серверах:

# apt install -y apache2
# mkdir /var/www/html/test

# nano /var/www/html/test/index.html #(на втором сервере поменяйте web1 на web2)
<!DOCTYPE HTML>
 <html>
  <head>
   <title>web1</title>
  </head>
 <body>
  <p>web1</p>
 </body>
</html>

# chown -R www-data:www-data /var/www/html/test/

Проверка

Используя адрес прокси сервера, откроем xxx:

web1

Используя адрес прокси сервера, откроем yyy:

web2

Как видим у нас открылись разные странички, которые лежат на разных серверах!

Резервирование серверов

Теперь сделаем так, чтобы один и тот же запрос ходил по следующим правилам:

  • /xxx — проксируем на http:/web1/test/ — если он доступен;
  • а если не доступен, то проксируем на http://web2/test/.

Для этого поправим наш конфиг /etc/nginx/sites-enabled/proxy и перед блоком server добавим блок upstream:

# nano /etc/nginx/sites-enabled/proxy
upstream backend {
        server web1:80 fail_timeout=120s max_fails=3;
        server web2:80 backup;
}

server {
        listen 80 default_server;

*** сократил ***

В этом блоке указываем оба web-сервера. При этом web1 будет основным сервером. Но если proxy в течении 120 секунд получит три ошибки при обращении к web1, то на 120 секунд все запросы пойдут на web2.

Ниже в блоке server, где мы указывали что на что проксировать (блоки location), поменяем записи. Вместо web1 укажем название апстрима, которое мы придумали выше (backend). Второй блок (location /yyy) — удаляем. Получится так:

# проксируем запрос /xxx
location /xxx {
         proxy_pass http://backend/test/;
}

Полностью конфиг будет выглядеть так:

upstream backend {
        server web1:80 fail_timeout=120s max_fails=3;
        server web2:80 backup;
}

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name proxy;

        proxy_set_header X-Scheme http;
        proxy_set_header X-Forwarded-Proto http;
        proxy_set_header Host $proxy_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;

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

        location /xxx {
                proxy_pass http://backend/test/;
        }
}

Перезапустим nginx:

# service nginx restart

Проверка резервирования

В браузере, используя адрес прокси сервера, открываем /xxx:

Запрос xxx идет на web1

Затем отключаем сервер web1 и обновляем страничку:

Запрос xxx идет на web2

Как видим мы перешли на второй сервер. Теперь включим обратно web1 и обновим страничку еще раз:

Запрос xxx идет на web1

Теперь мы вернулись на первый сервер.

Таким образом вы можете создать два одинаковых веб сайта и в случае неисправности первого сервера, ваш сайт продолжит работать на втором сервере. Клиенты могут даже ничего не заметить.

Проксирование и абсолютные ссылки

Теперь приведу одну проблему revers proxy серверов — абсолютные ссылки. Пусть наш сайт (на сервере web1) будет содержать ссылку на некоторую свою страничку. Создадим каталог и в нём новую страничку:

# mkdir /var/www/html/test/test/
# nano /var/www/html/test/test/test.html
<!DOCTYPE HTML>
<html>
 <body>
  <p>Hello WEB1</p>
 </body>
</html>

Там же поправим индексную страничку и добавим в неё ссылку:

# nano /var/www/html/test/index.html
<!DOCTYPE HTML>
 <html>
  <head>
   <title>web1</title>
  </head>
 <body>
  <p>web1</p>
  <p><a href="/test/test/test.html">test</a></p>
 </body>
</html>

Обратите внимание, ссылка не относительная, а абсолютная. То-есть начинается с корня сайта.

Откроем web1 напрямую и проверим работу ссылки:

Проверка работы перехода по ссылки на web1

Как видим, при нажатии на ссылку мы переходим по адресу http://web1/test/test/test.html. И у нас всё работает!

Теперь проверим работу на proxy:

Проверка работы перехода по ссылки на proxy

На proxy ссылка не работает. Обратите внимание, на каталог к которому мы пытаемся подключиться /test/test/test.html. А у нас на proxy есть только /xxx, который проксируется на /test. А самого /test нет, вот поэтому мы получили ошибку.

Для того, чтобы решить это, нужно на proxy придумывать одноимённые пути для проксирования. Для этого на proxy отредактируйте конфиг:

# nano /etc/nginx/sites-enabled/proxy
location /test {
        proxy_pass http://backend/test/;
}

# service nginx restart

Проверим работу ссылки на proxy ещё раз:

Проверка работы перехода по ссылки на proxy

Дополнительная информация

Вообще проксирование не простая тема.

Например, проксировать можно https на http или наоборот, но при этом могут возникать проблемы, так как приложение работающее на backend сервере может генерировать ссылки основываясь на схеме. Получается вы к прокси подключаетесь по https, передаёте на backend в заголовке схему http. Затем backend отвечает на http, и приложение генерирует другие ссылки на http и ваш браузер перескакивает на работу по http к proxy, а он допустим умеет только https (и редирект на https не настроен). Получается вот такая картина:

Проблема проксирования, когда proxy и backend работают на разных протоколах

Поэтому, в идеале, нужно чтобы Revers Proxy работал на том же протоколе, что и Backend сервер.

Или проблема может быть ещё хуже. Ссылки на Backend сервере абсолютные и помимо пути содержат адрес сервера, например такие: http://backend/test/ . Это приведёт к тому, что нажав на ссылку вы перескочите с Proxy сервера к Backend серверу, а он может быть напрямую и недоступен.

Поэтому revers proxy делают в основном к своим внутренним сервисам, которые тоже контролируются. А не на внешние сайты. Которые могут и не уметь работать через revers proxy. Для этой задачи применяют forward proxy, например Squid.

Итог

Здесь я разобрал многое, но не всё. Например, можно настроить не резервирование, а балансировку. Про неё, кстати, статей в интернете больше чем про резервный сервер. Я постарался использовать многие http заголовки, чтобы показать как их можно изменять при проксировании. Возможно не все заголовки вам будут нужны в реальной конфигурации. Это зависит от web-приложения, которое вы используете. Буфер для прокси сервера тоже нужно настраивать под конкретную задачу. Поэтому просто копировать приведённые выше конфиги не желательно, нужно анализировать свои действия и все перепроверять.

Сводка
Nginx. Reverse Proxy
Имя статьи
Nginx. Reverse Proxy
Описание
Разберём, что такое Reverse Proxy. А также я покажу как настроить Nginx в качестве Reverse Proxy (обратного прокси сервера)

9 Replies to “Nginx. Reverse Proxy”

  1. Здравствуйте,попытался реализовать по вашей инструкции доступ с локального nginx на внешний web сервер ,получаю (ERR_CONNECTION_TIMED_OUT)

    server {
    # Основные настройки
    listen 7222 default_server;
    listen [::]:7222 default_server;
    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;
    server_name proxy;

    # Управление заголовками на прокси сервере
    proxy_set_header X-Scheme http;
    proxy_set_header X-Forwarded-Proto http;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-PORT $remote_port;
    proxy_set_header X-Real-IP $remote_addr;

    # настройка буфера для прокси сервера
    proxy_buffering on;
    proxy_buffer_size 8k;
    proxy_buffers 8 8k;

    location / {
    # вначале попытаемся обработать запрос как файл,
    # затем как каталог, затем вернём ошибку 404
    try_files $uri $uri/ =404;
    }

    # проксируем запрос /xxx на web1
    location /xxx {
    proxy_pass http://my-site.ru;
    }

    # проксируем запрос /yyy на web2
    location /yyy {
    proxy_pass http://xx.xxx.xx.xx;
    }
    }

    1. На внешний ресурс, наверное, не обратный прокси нужно делать а обычный прокси сервер. Сам не пробовал ещё, если попробую, то отпишусь.

  2. Здравствуйте! У меня схема похоже на вашу но когда я проксирую к примеру nginx proxy manager 81 порт тогда у меня открывается белая страница, когда я смотрел в кансоле браузера он не может подгрузить js, img и.т.д. не могли бы подсказать в чем проблема

    1. Здравствуйте! Можете настроить логирование на прокси и на бекенде, возможно поймёте куда уходят запросы. И ещё от веб-приложения тоже может зависеть, тут нужно понять какие заголовки используются и какие дополнительные ссылки нужно проксировать. Кстати, попробую простенький js написать и проверить, будет ли он работать через обратный прокси.

  3. У меня вопрос
    Хорошо, когда у вас пути: example.ru/xxx и example.ru/yyy, а что если адреса разные и запрос нужно направить конкретно на порт?
    Что имею ввиду:
    Дано: также 3 машины: 1 приемник, 2 локальные связаны с приёмником,
    заходим на example1.ru, домен направляет на адрес 95.168.0.1 (адрес приёмника), прокси должно отправить на локальную машину 192.168.0.1 порт 80,
    заходим на example2.ru домен направляет на адрес 95.168.0.1 (адрес приёмника), прокси должно отправить на локальную машину 192.168.0.2 порт 8084

  4. Добрый день.
    Пытаюсь настроить обратный прокси по Вашей схеме. Почти всё работает, но!
    Есть прокси сервер за ним несколько доменов. Запрос к домену за прокси сервером выглядит следующим образом:
    proxy/domen_id/url
    Проблема в том, что при открытии ссылок «domen_id» вырезается и в результате ничего не работает… Бьюсь уже довольно много времени и не могу понять как обойти данную проблему.

    1. Не совсем понял какую схему вы реализуете, можете нарисовать и сюда добавить. Хотя сюда (в коменты) наверное не получится рисунок прикрепить, тогда можете в группе в ВК сообщение написать

      1. Схема простая. Есть локальная сеть виртуальных машин, без доступа наружу (ЛСВМ) на выходе из нее стоит прокси с nginx.
        Каждая виртуальная машина — определенный сервер с web интерфейсом и своим доменным именем (оно же «domen_id). Доступ к web интерфейсу предполагается сделать через прокси сервер. Для этого и делается следующий вид запроса:

        http://proxy_server/domen_id/

        Проблема в том, что при проксировании в чистую при открытии ссылки на web интерфейсе закономерно вырезается важный кусок запроса, а именно «domen_id» и понятное дело, что прокси сервер выдает ошибку 404.
        Вот как-то так обстоит проблема.

  5. Нужно сказать спасибо за этот ресурс, и вот почему:
    — сайт показывает рекламу? реклама это ещё и баннер? пока…
    — сайт просить(ТРЕБУЕТ) регистрацию? пока…
    — сайт где посты всё по делу и с примерами описанием: подожди что?
    — сайт где посты реально имеют проверенный код ЕДРИТЬ КАК ЭТО ВСТАВЛЯЕТ — ты прикинь скопировал и вставил — и оно работает ПРИКИНЬ

    Долго искал что бы отдельные страницы проксировались (хотя-бы с ошибками…)

    Осталось фильтр и защита от атак ботов, спам запросов типо (думаю легкче CIDr забанить))

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *