Вопрос или проблема
Я испытываю некоторые проблемы с HAProxy, работающим в качестве обратного прокси и перенаправляющим трафик на два разных приложения на порту 443. Приложение 2 работает нормально с конфигурацией ниже. Приложение 1 вызывает некоторые незначительные проблемы. При вводе в браузере адреса домена: “domain.com/application1” я получаю ошибку Time Out, и адрес в браузере меняется на domain.com:82/application1, где 82 — это желаемый порт бэкенда, на котором слушает приложение 1. Теперь это становится довольно странным для меня. Удаление “:82” из “domain.com:82/application1” позволяет мне получить доступ к приложению 1 в веб-браузере. Я предполагаю, что я ошибся в какой-то строке команды перенаправления, прокси-протокола или SSL-выгрузки, но не вижу, где именно. Одним из ограничений является то, что мне нужно вести учет исходящего IP-адреса. Ниже моя конфигурация HAProxy и Nginx:
HAProxy:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
tune.ssl.default-dh-param 2048
defaults
log global
mode tcp
option tcplog
option dontlognull
option forwardfor header X-Forwarded-For
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend http-in
bind :80
mode http
option forwardfor header X-Forwarded-For
option httplog
acl is_letsencrypt path_beg -i /.well-known/acme-challenge/
redirect scheme https code 301 if !{ ssl_fc } !is_letsencrypt
use_backend be_letsencrypt if is_letsencrypt
frontend https_in
bind *:443
option http-server-close
acl tls req.ssl_hello_type 1
acl is_application1 req.ssl_sni -i domain.com
tcp-request inspect-delay 5s
tcp-request content accept if tls
use_backend recir_application1 if is_application1
use_backend recir_application2 if !{ req.ssl_hello_type 1 } !{ req.len 0 }
backend be_letsencrypt
mode http
server localhost 127.0.0.1:81
backend recir_application1
server loopback-for-tls abns@haproxy-application1 send-proxy
backend recir_application2
server loopback-for-tls abns@haproxy-default send-proxy
frontend ssl-default
bind abns@haproxy-default accept-proxy
use_backend be_application2
frontend application1
mode http
bind abns@haproxy-application1 accept-proxy ssl crt /etc/letsencrypt/domain.com/haproxy.pem
use_backend be_application1
backend be_application2
server localhost 127.0.0.1:4545
backend be_application1
mode http
server localhost 127.0.0.1:82 send-proxy
и Nginx:
upstream php-handler {
server unix:/run/php/php8.3-fpm.sock;
}
server {
listen 127.0.0.1:82 proxy_protocol;
listen [::]:82 proxy_protocol;
server_name localhost mydomain.com;
# Настройки HSTS
# ВНИМАНИЕ: добавляйте опцию preload только после того, как ознакомитесь с
# последствиями на https://hstspreload.org/. Эта опция
# добавит домен в жестко закодированный список, который распространен
# во всех основных браузерах, и удаление из этого списка
# может занять несколько месяцев.
# add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;" всегда;
# Путь к корню вашей установки
root /var/www/;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Следующие 2 правила нужны только для приложения user_webfinger.
# Раскомментируйте, если планируете использовать это приложение.
# rewrite ^/.well-known/host-meta /webapplication1/public.php?service=host-meta last;
# rewrite ^/.well-known/host-meta.json /webapplication1/public.php?service=host-meta-json last;
location ^~ /.well-known {
# Правила в этом блоке - это адаптация правил
# в `.htaccess`, касающихся `/.well-known`.
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
# Пусть API webapplication1 для `/.well-known` обрабатывает все остальные
# запросы, передавая их контроллеру переднего плана.
return 301 /index.php$request_uri;
}
location ^~ /webapplication1 {
# укажите максимальный размер загрузки и увеличьте тайм-аут загрузки:
client_max_body_size 512M;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
# Включите gzip, но не удаляйте заголовки ETag
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml;
# Pagespeed не поддерживается webapplication1, поэтому если ваш сервер собран
# с модулем `ngx_pagespeed`, раскомментируйте эту строку, чтобы отключить его.
#pagespeed off;
# Настройки позволяют вам оптимизировать ширину канала HTTP2.
# См. https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
# для рекомендаций по настройкам
client_body_buffer_size 512k;
# HTTP-заголовки ответа заимствованы из `.htaccess` webapplication1
add_header Referrer-Policy "no-referrer" всегда;
add_header X-Content-Type-Options "nosniff" всегда;
add_header X-Download-Options "noopen" всегда;
add_header X-Frame-Options "SAMEORIGIN" всегда;
add_header X-Permitted-Cross-Domain-Policies "none" всегда;
add_header X-Robots-Tag "noindex, nofollow" всегда;
add_header X-XSS-Protection "1; mode=block" всегда;
# Удалите X-Powered-By, что является утечкой информации
fastcgi_hide_header X-Powered-By;
# Установите MIME-типы для .mjs и .wasm
# Либо добавьте его в список mime.types по умолчанию
# и явно включите этот список, либо добавьте расширение файла
# только для webapplication1, как ниже:
include mime.types;
types {
text/javascript js mjs;
application/wasm wasm;
}
# Укажите, как обрабатывать директории -- указание `/webapplication1/index.php$request_uri`
# здесь как запасного варианта означает, что Nginx всегда будет вести себя,
# как требуется, когда клиент запрашивает путь, который соответствует директории, существующей
# на сервере. В частности, если в этой директории есть файл index.php,
# этот файл будет правильно обслужен; если его нет, запрос передается
# контроллеру переднего плана. Это последовательное поведение означает, что нам не нужно
# указывать специальные правила для определенных путей (например, для изображений и других ресурсов,
# `/updater`, `/ocm-provider`, `/ocs-provider`), и поэтому
# `try_files $uri $uri/ /webapplication1/index.php$request_uri`
# всегда обеспечивает требуемое поведение.
index index.php index.html /webapplication1/index.php$request_uri;
# Правило, заимствованное из `.htaccess`, для обработки клиентов Microsoft DAV
location = /webapplication1 {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /webapplication1/remote.php/webdav/$is_args$args;
}
}
# Правила, заимствованные из `.htaccess`, чтобы скрыть определенные пути от клиентов
location ~ ^/webapplication1/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/webapplication1/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
# Убедитесь, что этот блок, который передает файлы PHP на процесс PHP, находится выше блоков
# , которые обрабатывают статические ресурсы (как видно ниже). Если этот блок не объявлен первым,
# Nginx столкнется с бесконечным циклом переписывания, когда он добавляет
# `/webapplication1/index.php` к URI, что приведет к коду ответа HTTP 500.
location ~ \.php(?:$|/) {
# Требуется для поддержки устаревших версий
rewrite ^/webapplication1/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocume);
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true; # Избегайте двойной отправки заголовков безопасности
fastcgi_param front_controller_active true; # Включите красивые URL
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_max_temp_file_size 0;
}
location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
try_files $uri /webapplication1/index.php$uri$is_args$args;
proxy_set_header Cache-Control "public, max-age=7200";
# Добавьте заголовки для предоставления заголовков безопасности
# Снова используйте 'proxy_set_header' (не 'add_header'), поскольку заголовки должны проходить через прокси.
proxy_set_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;";
proxy_set_header X-Content-Type-Options nosniff;
proxy_set_header X-Frame-Options "SAMEORIGIN";
proxy_set_header X-XSS-Protection "1; mode=block";
proxy_set_header X-Robots-Tag none;
proxy_set_header X-Download-Options noopen;
proxy_set_header X-Permitted-Cross-Domain-Policies none;
# По желанию: не регистрировать доступ к ресурсам
access_log off;
location ~ \.wasm$ {
default_type application/wasm;
}
}
location ~ \.woff2?$ {
try_files $uri /webapplication1/index.php$request_uri;
expires 7d; # Политика Cache-Control заимствована из `.htaccess`
access_log off; # По желанию: не регистрировать доступ к ресурсам
}
# Правило, заимствованное из `.htaccess`
location /webapplication1/remote {
return 301 /webapplication1/remote.php$request_uri;
}
location /webapplication1 {
try_files $uri $uri/ /webapplication1/index.php$request_uri;
}
}
}
Спасибо за вашу помощь.
Кришан
Ответ или решение
Для решения проблемы с конфигурацией HAProxy, которая будет работать в качестве обратного прокси с SSL-офладингом для двух приложений на порту 443, важно корректно настроить маршрутизацию трафика, а также убедиться, что все настройки правильно обрабатывают исходящие HTTP заголовки. Ниже представлена возможная конфигурация для исправления проблемы.
HAProxy Конфигурация
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option forwardfor header X-Forwarded-For
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:80
acl is_letsencrypt path_beg -i /.well-known/acme-challenge/
redirect scheme https code 301 if !{ ssl_fc } !is_letsencrypt
use_backend be_letsencrypt if is_letsencrypt
frontend https-in
bind *:443 ssl crt /etc/letsencrypt/domain.com/haproxy.pem
option http-server-close
acl is_application1 hdr(host) -i domain.com
acl is_application2 hdr(host) -i application2.domain.com
use_backend be_application1 if is_application1
use_backend be_application2 if is_application2
backend be_letsencrypt
mode http
server letsencrypt 127.0.0.1:81
backend be_application1
mode http
server application1 127.0.0.1:82 send-proxy
backend be_application2
mode http
server application2 127.0.0.1:4545 send-proxy
Nginx Конфигурация
Убедитесь, что конфигурация Nginx для первого приложения, слушающего на порту 82, настроена на поддержку proxy_protocol
, так как HAProxy отправляет IP-адреса клиента.
upstream php-handler {
server unix:/run/php/php8.3-fpm.sock;
}
server {
listen 127.0.0.1:82 proxy_protocol;
listen [::]:82 proxy_protocol;
server_name localhost domain.com;
# Root и другие настройки
root /var/www/;
location ~ ^/application1 {
try_files $uri /application1/index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php-handler;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
}
}
Объяснение внесенных изменений
-
Настройки ACL: Мы определили два отдельных ACL для каждого приложения, используя заголовок
Host
. Это позволяет HAProxy правильно маршрутизировать запросы на соответствующий бэкенд. -
Параметр
send-proxy
: Используется для передачи IP-адресов клиента от HAProxy к Nginx. Убедитесь, что сервер Nginx настроен для поддержкиproxy_protocol
. -
Использование
try_files
в Nginx: Это гарантирует правильную обработку запросов, перенаправляя их наindex.php
приложения, если файл не найден.
Заключение
С этими изменениями ваша установка HAProxy и Nginx должна работать корректно. Вы сможете получить доступ к обоим приложениям через HTTPS, и параметры send-proxy
и proxy_protocol
обеспечат правильную передачу исходящего IP-адреса клиента. Обязательно протестируйте доступ к каждому приложению, чтобы убедиться, что конфигурация выполнена правильно.