При развертывании инфраструктуры для удаленной работы с 1С:Предприятие системные администраторы часто используют связку из обратного прокси-сервера (Reverse Proxy) на базе Nginx, веб-сервера Apache на целевом компьютере с базой данных и VPN-туннеля для безопасного объединения этих узлов. Такая схема обеспечивает высокий уровень безопасности и гибкости, однако при ее настройке можно столкнуться с классической проблемой веб-разработки: подменой внешнего доменного имени на внутренний IP-адрес VPN-клиента при попытке входа в веб-клиент 1С.
В этой статье мы подробно проанализируем, почему возникает эта ошибка, рассмотрим механизм работы редиректов в веб-серверах и разберем несколько надежных способов устранения проблемы как на стороне Nginx, так и на стороне Apache.
Рассмотрим типичную топологию сети, в которой возникает данная неполадка:
http://test.1cvpn.ru/test, где test — это имя опубликованной базы 1С.172.16.0.1).172.16.0.11. На этом компьютере опубликована база 1С.http://test.1cvpn.ru/test), его браузер внезапно перенаправляется на внутренний адрес http://172.16.0.11/test/, который из внешней сети Интернет, разумеется, недоступен. В результате пользователь видит ошибку тайм-аута соединения.http://test.1cvpn.ru/test/) или сразу указать локаль (http://test.1cvpn.ru/test/ru_RU/), веб-клиент 1С успешно загружается и работает. Тонкий клиент 1С также функционирует без нареканий.Выясним причину такого странного поведения и разберем детально физику процесса.
Для того чтобы понять, кто виноват в подмене адреса, проанализируем по шагам путь прохождения 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 необходимо знать внешнее имя хоста и порт. Он пытается определить их на основе доступной ему информации:
Host, пришедшего в HTTP-запросе.Host, Apache видит в заголовках запроса только IP-адрес, по которому к нему обратился прокси-сервер — то есть внутренний VPN-адрес 172.16.0.11.Location: http://172.16.0.11/test/. Этот заголовок возвращается обратно через Nginx клиенту, и браузер пользователя пытается выполнить переход по адресу, находящемуся в закрытой VPN-сети.3. Роль Nginx по умолчанию
По умолчанию директива proxy_pass в Nginx не сохраняет оригинальный заголовок Host, отправленный клиентом. Вместо этого Nginx заменяет значение заголовка Host на имя хоста или IP-адрес, указанный в самой директиве proxy_pass (в нашем случае — 172.16.0.11). Apache получает запрос, думает, что клиент обратился к нему напрямую по IP-адресу, и генерирует соответствующий редирект.
Теперь, когда причина ясна, рассмотрим пошагово методы устранения этой проблемы.
Самый простой, правильный и изящный способ решить проблему подмены адреса — это заставить 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;
}
}
Разберем подробнее, что происходит при такой конфигурации:
http://test.1cvpn.ru/test.http://172.16.0.11/test, но при этом в HTTP-заголовках указывает Host: test.1cvpn.ru.test.1cvpn.ru, генерирует корректный редирект 301 с заголовком Location: http://test.1cvpn.ru/test/.Если по какой-то причине вы не хотите или не можете изменять заголовки на стороне 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, и сгенерирует редирект на правильный домен.
Бывают ситуации, когда бэкенд-сервер Apache администрируется сторонними специалистами, и у вас нет доступа к его конфигурационным файлам, либо на нем опубликовано множество баз с динамическими адресами. В этом случае Nginx может перехватывать некорректные заголовки Location, приходящие от Apache, и модифицировать их «на лету» перед отправкой пользователю.
Для этого предназначена директива proxy_redirect. Проанализируем ее использование:
location / {
proxy_pass http://172.16.0.11/;
# Заменяем внутренний адрес VPN в заголовках ответа на относительный путь
proxy_redirect http://172.16.0.11/ /;
}
Как это работает по шагам:
Location: http://172.16.0.11/test/.http://172.16.0.11/ и заменяет его на относительный путь /.Location: /test/.http://test.1cvpn.ru/test/. Задача решена!В современных версиях веб-сервера Nginx (начиная с версии 1.11.8) появилась возможность полностью запретить использование абсолютных путей при генерации редиректов самим прокси-сервером. За это отвечает директива absolute_redirect.
Если редирект инициируется конфигурацией Nginx, добавление следующей строки в контекст http, server или location решит проблему:
absolute_redirect off;
Это заставит Nginx всегда выдавать относительные редиректы вида Location: /test/, предотвращая подстановку портов и локальных IP-адресов.
Почему же веб-клиент 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С.