Nginx reverse-proxy для Bitrix VM

Введение

В данной статье я расскажу про опыт настройки Nginx в качестве прокси-сервера для сервера с bitrixVM. При типовой настройке сервера с bitrix и последующего конфигурирования Nginx в качестве прокси для него, где, казалось бы, все параметры типовые и нужно ли лишь минимально настроить proxy_pass до нужного адреса, часто могут возникать ошибки, которые всплывают в процессе эксплуатации.

Проблема подстановки 80 или 443 порта в URL

Суть ошибки понятна из её содержимого. Такое бывает из-за того, что терминация TLS-трафика происходит на стороне прокси (Nginx), т.е. запросы пользователей приходят на 443 порт, а backend с сайтом на битрикс работает на http – обычно 80 или иной другой порт, а потому от прокси до бэкенда трафик идёт уже незашифрованный. Сам apache, который является конечной точкой в схеме при проксировании, должен понимать, что сайт работает с использованием протокола https, т.е. при выводе информации в phpinfo значение переменной $_SERVER[‘SERVER_PORT’] должно быть 443, а не 80, и в таком случае никакой некорректной подстановки осуществляться не будет.

  • Самым простым решением является использовать 80 или 443 порт в зависимости от используемого протокола – реализация взята с сайта битрикс и настраивается через конструкцию map. Например, на сервере с битрикс создается файл /etc/nginx/bx/settings/schema.conf со следующим содержимым:
map $http_x_forwarded_proto $balancer_port {
    default 80;
    "https" 443;
}
 map $http_x_forwarded_proto $balancer_https {
     default "NO";
     "https" "YES";
}

Если переменная $http_x_forwarded_proto в Nginx содержит в себе https, то в новые переменные $balancer_port и $balancer_https записывается значение 443 и YES соответственно.

А в используемом сайте по пути, например, /etc/nginx/bx/site_enabled/bx_ext_test.example.org.conf заменить конфигурацию по умолчанию:

proxy_set_header Host $host:80;

На следующую:

proxy_set_header Host $host:$balancer_port; 
proxy_set_header HTTPS $balancer_https;

Таким образом, при работе сайта по https или http, всегда будет подставляться корректный порт в зависимости от используемого протокола.

  • В своё время по этой проблеме я также писал в поддержку битрикса и тогда они ещё предложили такое костыльное решение, как мне кажется. Но оно имеет право на жизнь, т.к. работает. В dbconn.php надо добавить:
if (($pos = strpos($_SERVER['HTTP_HOST'], ':')) !== false)
{
$HTTP_HOST = $_SERVER['HTTP_HOST'] = substr($_SERVER['HTTP_HOST'],0,$pos);
}

$_SERVER["HTTPS"] = "On";
$_SERVER['SERVER_PORT'] = 443;

По сути будет выполнено тоже самое, что и описанное ранее выше, только средствами PHP, и Apache будет понимать, что сайт работает через https.

  • И есть ещё один из самых правильных вариантов. Также можно внести правки на стороне nginx, который проксирует непосредственно на сам backend к PHP, т.е. в nginx на сервере с bitrix. Например, добавить в nginx.conf в секции HTTP:
http {
...
proxy_redirect    ~^http://([^:]+):443(/.+)$ https://$1$2;
...
}

Директива proxy_redirect заменит любой запрос заголовка location, который матчится с регуляркой при наличии 443 порта, на корректную схему уже с https. Более подробно в документации Nginx.

400 Bad Request, The plain HTTP request was sent to HTTPS port

Не совсем понятная на первый взгляд ошибка. При перенаправлении с http на https на вышестоящем Nginx proxy и обращении к имени сайта https://domain.ru/bitrix без слэша в конце, в URL подставлялся 443 порт и протокол менялся на http.

Проблема ошибки 400 Bad Request, The plain HTTP request was sent to HTTPS port заключается в модуле mod_dir у httpd. При настройке редиректа на вышестоящим прокси-сервере и открытии адреса вида domain.ru/bitrix без закрывающего слеша в конце получается проблема, когда domain.ru/bitrix – это директория. А для директорий требуется закрывающий слеш в конце. Т.е. если в Nginx даже указать proxy_set_header HTTPS YES, то для данного URL с директорией это не сработает, а потому нужно явно указать в httpd, что сайт работает по https, т.е. прописать схему в конфиг нужного virtualhost. 

  • в конфигурационном файле httpd по пути /etc/httpd/bx/conf/bx_ext_test.example.org.conf явно указать в директиве ServerName имя домена и схему https. Если используется дефолтный конфиг без отдельных сайтов, то вписать в дефолтный конфиг /etc/httpd/bx/conf/default.conf:
ServerName https://test.example.org

Проксирование websockets для Push&Pull

Ещё одна известная ошибка, которая возникает при работе сайта на битриксе через прокси – это не работает модуль Push&Pull. Точнее, не работает проверка системы, хотя казалось бы, что всё настроено: из menu.sh bx-push-server 2.0 установлен корректно штатными средствами, redis запущен, в логах ошибок нет. Но на сайте может быть красная полоса “Отсутствует соединение с сервером” и проваливается проверка системы, если обращаться через прокси-сервер.

Проблема в том, что Push server работает через wss, т.е. вебсокеты, а для этого со стороны прокси сервера необходимо проксировать дополнительные заголовки до backend, указывая явно, что клиент может сменить протокол на wss. “Upgrade” и “Connection” не передаются от клиента к проксируемому серверу, поэтому, для того чтобы проксируемый сервер узнал о намерении клиента сменить протокол на WebSocket, эти заголовки следует передать явно.

Для реализации необходимо на прокси-сервере добавить отдельный location:

location ~* ^/bitrix/subws/ {
     access_log off;
     proxy_pass http://BACKEND;
     proxy_max_temp_file_size 0;
     proxy_read_timeout  43800;
     proxy_http_version 1.1;
     proxy_set_header Upgrade $replace_upgrade;
     proxy_set_header Connection $connection_upgrade;

А в http секции должна быть конструкция из map, определяющая значение вышеописанных переменных:

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

map $http_upgrade  $replace_upgrade {
  default $http_upgrade;
  ''      "websocket";
}

Настройка basic auth

При настройке http-авторизации на прокси-сервере с Nginx может случиться проблема с авторизацией на сайте с битрикс.

Пример следующий: на стороне Nginx basic auth успешно проходит после ввода верного логина\пароля, и пускает пользователя дальше на проксируемый ресурс. Но битрикс почему-то принимает логин и пароль от basic auth в свою веб-форму авторизации и выдает ошибку “неверный логин или пароль”. Данная проблема по началу может смутить, т.к. на первый взгляд нет никакой связи между файлом для basic auth, созданным через htpasswd, и веб-формы авторизации сайта.

Но объяснение этого достаточно просто: при использовании модуля ngx_http_auth_basic_module формируется HTTP-заголовок “Authorization”, где зашиваются зашифрованные в base64 логин и пароль, которые пользователь вводит при появлении окна basic auth. А после того, как логин\пароль введён верно, запрос пользователя проксируется на сайт с битрикс, а вместе с ним и заголовок “Authorization”. И тут в игру вступает битрикс и веб-сервер apache – по умолчанию в файле .htaccess сайта, созданного через bitrix-env, есть следующая строка:

<IfModule mod_rewrite.c>
        Options +FollowSymLinks
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-l
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
        RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
        RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
</IfModule>

В переменную REMOTE_USER добавляется значение из HTTP-заголовка HTTP:Authorization, в котором содержится логин\пароль для basic auth. А дальше битрикс пробует авторизовать пользователя уже в своей форме на основе REMOTE_USER, делается это через код.

На сайте битры об этом написано и там даже предлагаются различные решения – правки для кода. Но вся проблема в том, что это нарушение исходной целостности кодовой базы и проблема решается куда проще двумя иными способами:

  1. В .htaccess можно просто закомментировать строку RewriteRule .* – [E=REMOTE_USER:%{HTTP:Authorization}]. Это допустимо при условии, что в дальнейшем не потребуется SSO через Kerberos или NTLM, т.к. как раз на основе переменной REMOTE_USER битрикс и производит сквозную авторизацию.
  2. И самое правильное решение, разумеется, находится на стороне Nginx. Для заголовка HTTP:Authorization просто подставляется пустое значение, а потому битриксу будет неоткуда взять значение для REMOTE_USER, и сайт попросит ввести логин\пароль уже в стандартную форму.
location / {
        proxy_set_header Authorization "";

        ...
        }

Итоговый пример конфигурационного файла

Ниже приведен пример конфигурационного файла nginx proxy для сервера с битрикс:

server {
        server_name {DOMAIN};
        listen 80;
        return 301 https://$host$request_uri;
}

server {
        listen 443 ssl http2;
        server_name {DOMAIN};
        ssl_certificate "/etc/nginx/ssl/cert.crt";
        ssl_certificate_key "/etc/nginx/ssl/key.key";
        ssl_prefer_server_ciphers on;

        access_log /var/log/nginx/{DOMAIN}.access.log;

        location / {
                proxy_ignore_client_abort on;
                proxy_pass http://{NODE-IP}:80;
                proxy_redirect http://{NODE-IP}:80 /;
                proxy_read_timeout 300;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-Port $server_port;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header HTTPS YES;
                proxy_set_header Authorization "";
                
                # for Push&Pull
                location /bitrix/subws {
                proxy_pass http://{NODE-IP}:80;
                proxy_set_header Upgrade $replace_upgrade;
                proxy_set_header Connection $connection_upgrade;
                proxy_redirect http://{NODE-IP}:80 /;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-Port $server_port;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header HTTPS YES;
                }
        }

Заключение

В статье рассмотрел основные проблемы, которые приходилось встречать и решать самостоятельно. В рунете по той или иной проблеме есть много советов, но всё хаотично разбросано и не всегда есть объяснения, как работает и как решить тот или иной вопрос. Поэтому если сделать всё по моей статье в комплексе, то ошибок возникать не должно.

Используемые источники

Понравилась статья? Поделиться с друзьями:
Комментарии: 2
  1. Sergio

    Очень полезная статья. Да и оформление грамотное, не только этой но и других статей. Приятно и леко читать. Бегпал пофорумам, искал решение, ваша статья очень помогла) Спасибо за помощь! :idea: :idea: :idea: :idea:

    1. admin (автор)

      И вам спасибо! Рад, что материал пригодился – для этого и было всё написано :)

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

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: