Публикация 1С через Apache, Nginx и VPN: как решить проблему подмены ServerName и некорректных редиректов в Веб-клиенте

Системный администратор 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При развертывании инфраструктуры для удаленной работы с 1С:Предприятие системные администраторы часто используют связку из обратного прокси-сервера (Reverse Proxy) на базе Nginx, веб-сервера Apache на целевом компьютере с базой данных и VPN-туннеля для безопасного объединения этих узлов. Такая схема обеспечивает высокий уровень безопасности и гибкости, однако при ее настройке можно столкнуться с классической проблемой веб-разработки: подменой внешнего доменного имени на внутренний IP-адрес VPN-клиента при попытке входа в веб-клиент 1С.

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

Симптомы и суть проблемы

Рассмотрим типичную топологию сети, в которой возникает данная неполадка:

  1. Внешний пользователь обращается по доменному имени http://test.1cvpn.ru/test, где test — это имя опубликованной базы 1С.
  2. Запрос принимает фронтенд-сервер Nginx (установленный на VPS под управлением Debian) с внешним IP-адресом. На этом же сервере поднят VPN-сервер (например, с IP-адресом 172.16.0.1).
  3. Запрос перенаправляется через VPN-туннель на бэкенд-сервер — локальный компьютер с ОС Windows, на котором запущен веб-сервер Apache с VPN-адресом 172.16.0.11. На этом компьютере опубликована база 1С.
  4. Если пользователь вводит URL без закрывающего слэша в конце (http://test.1cvpn.ru/test), его браузер внезапно перенаправляется на внутренний адрес http://172.16.0.11/test/, который из внешней сети Интернет, разумеется, недоступен. В результате пользователь видит ошибку тайм-аута соединения.
  5. При этом, если вручную ввести адрес с закрывающим слэшем (http://test.1cvpn.ru/test/) или сразу указать локаль (http://test.1cvpn.ru/test/ru_RU/), веб-клиент 1С успешно загружается и работает. Тонкий клиент 1С также функционирует без нареканий.

Выясним причину такого странного поведения и разберем детально физику процесса.

Почему происходит подмена адреса? Разбор механизмов HTTP

Для того чтобы понять, кто виноват в подмене адреса, проанализируем по шагам путь прохождения HTTP-запроса и поведение участвующих в цепочке веб-серверов.

1. Поведение веб-сервера Apache и директива DirectorySlash

Когда Apache получает запрос вида GET /test, он сопоставляет запрошенный ресурс со своими внутренними каталогами и алиасами. Обнаружив, что test — это каталог (в нашем случае — каталог публикации 1С), Apache понимает, что для корректной работы относительных путей к файлам (картинкам, скриптам, стилям) внутри этого каталога URL обязательно должен оканчиваться слэшем. По умолчанию в Apache включена директива DirectorySlash On.

Вместо отдачи контента Apache формирует HTTP-ответ с кодом состояния 301 Moved Permanently (или 302 Found) и указывает в заголовке Location новый адрес, куда клиенту следует перейти. Этот адрес должен быть абсолютным согласно спецификации HTTP.

2. Формирование самоадресующегося URL (Self-Referential URL)

Для построения абсолютного URL в заголовке Location веб-серверу Apache необходимо знать внешнее имя хоста и порт. Он пытается определить их на основе доступной ему информации:

3. Роль Nginx по умолчанию

По умолчанию директива proxy_pass в Nginx не сохраняет оригинальный заголовок Host, отправленный клиентом. Вместо этого Nginx заменяет значение заголовка Host на имя хоста или IP-адрес, указанный в самой директиве proxy_pass (в нашем случае — 172.16.0.11). Apache получает запрос, думает, что клиент обратился к нему напрямую по IP-адресу, и генерирует соответствующий редирект.

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

Решение 1. Передача оригинального заголовка Host через Nginx (Рекомендуемый способ)

Самый простой, правильный и изящный способ решить проблему подмены адреса — это заставить Nginx передавать оригинальный заголовок Host, полученный от браузера пользователя, дальше на бэкенд (Apache).

Для этого в конфигурационный файл виртуального хоста Nginx необходимо добавить директиву proxy_set_header Host $host;. Проанализируем пример настройки секции server в конфигурации Nginx:


server {
    listen 80;
    server_name test.1cvpn.ru;

    location / {
        proxy_pass http://172.16.0.11/;
        
        # Передаем оригинальный заголовок Host бэкенду
        proxy_set_header Host $host;
        
        # Передаем реальный IP-адрес клиента для логирования на бэкенде
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Передаем схему запроса (http или https)
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Разберем подробнее, что происходит при такой конфигурации:

  1. Клиент запрашивает http://test.1cvpn.ru/test.
  2. Nginx перенаправляет запрос на http://172.16.0.11/test, но при этом в HTTP-заголовках указывает Host: test.1cvpn.ru.
  3. Apache, видя, что к нему обращаются по имени test.1cvpn.ru, генерирует корректный редирект 301 с заголовком Location: http://test.1cvpn.ru/test/.
  4. Браузер клиента успешно выполняет редирект по внешнему доменному имени, и веб-клиент 1С загружается без ошибок.

Решение 2. Тонкая настройка веб-сервера Apache

Если по какой-то причине вы не хотите или не можете изменять заголовки на стороне Nginx, можно заставить Apache правильно определять свое внешнее имя самостоятельно. Для этого используются директивы в конфигурационном файле Apache (обычно это httpd.conf или конфигурационный файл виртуального хоста).

Рассмотрим директиву UseCanonicalName. По умолчанию она может находиться в режиме On или Off. Нам необходимо перевести ее в состояние Off, а также отключить определение физического порта:


# Указываем Apache использовать имя хоста и порт, предоставленные клиентом
UseCanonicalName Off
UseCanonicalPhysicalPort Off

Если эти директивы установлены в Off, Apache будет строить самоадресующиеся URL, используя заголовок Host, присланный клиентом. Однако, обратите внимание: если Nginx все равно перезаписывает заголовок Host на IP-адрес бэкенда, эта мера сама по себе не поможет без настройки Nginx.

В качестве альтернативы можно жестко прописать внешнее доменное имя в конфигурации виртуального хоста Apache на Windows-машине:


<VirtualHost *:80>
    ServerName test.1cvpn.ru
    # Остальные настройки публикации базы 1С
</VirtualHost>

В этом случае, даже если запрос придет на IP-адрес 172.16.0.11 без заголовка Host, Apache будет знать, что его «каноническое» имя — test.1cvpn.ru, и сгенерирует редирект на правильный домен.

Решение 3. Перезапись заголовков редиректа на стороне Nginx (proxy_redirect)

Бывают ситуации, когда бэкенд-сервер Apache администрируется сторонними специалистами, и у вас нет доступа к его конфигурационным файлам, либо на нем опубликовано множество баз с динамическими адресами. В этом случае Nginx может перехватывать некорректные заголовки Location, приходящие от Apache, и модифицировать их «на лету» перед отправкой пользователю.

Для этого предназначена директива proxy_redirect. Проанализируем ее использование:


location / {
    proxy_pass http://172.16.0.11/;
    
    # Заменяем внутренний адрес VPN в заголовках ответа на относительный путь
    proxy_redirect http://172.16.0.11/ /;
}

Как это работает по шагам:

  1. Apache возвращает ответ 301 с заголовком Location: http://172.16.0.11/test/.
  2. Nginx перехватывает этот заголовок, находит совпадение с шаблоном http://172.16.0.11/ и заменяет его на относительный путь /.
  3. Пользователю отправляется заголовок Location: /test/.
  4. Браузер пользователя, получив относительный редирект, автоматически преобразует его в абсолютный, используя текущий домен обращения, то есть переходит на http://test.1cvpn.ru/test/. Задача решена!

Решение 4. Использование абсолютных редиректов (absolute_redirect off)

В современных версиях веб-сервера Nginx (начиная с версии 1.11.8) появилась возможность полностью запретить использование абсолютных путей при генерации редиректов самим прокси-сервером. За это отвечает директива absolute_redirect.

Если редирект инициируется конфигурацией Nginx, добавление следующей строки в контекст http, server или location решит проблему:

absolute_redirect off;

Это заставит Nginx всегда выдавать относительные редиректы вида Location: /test/, предотвращая подстановку портов и локальных IP-адресов.

Особенности работы Веб-клиента 1С:Предприятие

Почему же веб-клиент 1С так чувствителен к наличию слэша и корректной обработке заголовка Host?

Веб-клиент 1С представляет собой высокотехнологичное SPA-приложение (Single Page Application), работающее на базе сложных JavaScript-сценариев. При обращении к публикации веб-сервер (в данном случае модуль расширения веб-сервера 1С) выполняет инициализацию сессии, определяет языковые стандарты (добавляя к пути ту самую локаль /ru_RU/) и подгружает статические ресурсы.

Если на этапе первоначального запроса происходит сбой адресации, JavaScript-код веб-клиента теряет верный базовый URI (Base URI) для отправки последующих AJAX и WebSocket запросов. Именно поэтому правильная трансляция заголовка Host критически важна: на его основе платформа 1С строит внутренние ссылки для загрузки метаданных конфигурации.

Дополнительно отметим важность заголовка X-Forwarded-Proto. Если в будущем вы решите перевести ваш внешний сайт Nginx на защищенный протокол HTTPS (что настоятельно рекомендуется для защиты учетных данных 1С), а связка Nginx с Apache внутри VPN-туннеля продолжит работать по незащищенному HTTP, без передачи заголовка протокола веб-клиент 1С попытается загружать скрипты по протоколу HTTP. Это приведет к блокировке смешанного содержимого (Mixed Content) современными браузерами. Решением является следующая строка в Nginx:

proxy_set_header X-Forwarded-Proto $scheme;

Резюме

Мы проанализировали классическую проблему подмены доменного имени внутренним IP-адресом VPN при публикации 1С через связку Nginx и Apache. Выяснилось, что наиболее эффективным и рекомендуемым решением является настройка передачи оригинального заголовка хоста со стороны Nginx с помощью директивы proxy_set_header Host $host;. Это позволяет сохранить прозрачность адресации для бэкенда Apache и обеспечивает стабильную и быструю работу веб-клиента 1С.

← На главную