Использование 3X-UI в связке с Traefik

Привет!

Использую 3X-UI на VPS, место WG, OpenVPN и Outline. Но столкнулся с проблемой. VPS хочется использовать не только для обхода ограничений, но еще и для других целей, вроде своего сайта, бота в телеграмме и тп.

Понял, что для решения проблемы стоит использовать реверс-прокси, остановил свой выбор на Traefik, но не получается настроить его для 3X-UI (с условным приложением на React все получилось).

Пробовал искать конфиги, но нашел только пару для X-UI, но с ними не завелось :slightly_frowning_face:.

Первый вариант

В x-ui для inbound с Reality включен только этот Reality. По поводу путей панели и подписок у меня договорённость между x-ui и Traefik. Для HOSTNAME перед запуском был выполнен экспорт из bash: export HOSTNAME. Энтрипоинт на :54321 был добавлен для удобства с панелью. Сейчас у меня x-ui, а у 3x-ui другой дефолтный порт. Да и вообще обратите внимание на имена энтрипоинтов, вдруг не совпадают.

version: '3.9'
services:
  xui:  
    image: alireza7/x-ui:1.5.5
    container_name: x-ui
    hostname: ${HOSTNAME}
    volumes:
      - "./db/:/etc/x-ui/"
      - "./cert/:/cert/"
    environment:
      - XRAY_VMESS_AEAD_FORCED=false
      - XUI_LOG_LEVEL=warn
    restart: unless-stopped
#    network_mode: host
    labels:
      - "traefik.enable=true"
      # Web panel
      - "traefik.http.routers.xui-panel-r.entrypoints=websecure"
      - "traefik.http.routers.xui-panel-r.rule=HostRegexp(`{host:.+}`) && PathPrefix(`/xuipanel`)"
      - "traefik.http.routers.xui-panel-r.tls=true"
      - "traefik.http.routers.xui-panel-r.service=xui-panel-s"
      - "traefik.http.services.xui-panel-s.loadbalancer.server.port=54321"
      # Web panel fix port
      - "traefik.http.routers.xui-panel-rr.entrypoints=p54321"
      - "traefik.http.routers.xui-panel-rr.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.xui-panel-rr.tls=true"
      - "traefik.http.routers.xui-panel-rr.middlewares=xui-redirect"
      - "traefik.http.middlewares.xui-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.xui-redirect.redirectscheme.port=443"
      - "traefik.http.routers.xui-panel-rr.service=noop@internal"
      # Subscriptions
      - "traefik.http.routers.xui-sub-r.entrypoints=websecure"
      - "traefik.http.routers.xui-sub-r.rule=HostRegexp(`{host:.+}`) && PathPrefix(`/xuisub`)"
      - "traefik.http.routers.xui-sub-r.tls=true"
      - "traefik.http.routers.xui-sub-r.service=xui-sub-s"
      - "traefik.http.services.xui-sub-s.loadbalancer.server.port=4430"
      # Shadowsocks
      - "traefik.tcp.routers.xui-shadowsocks-r.rule=ClientIP(`0.0.0.0/0`)"
      - "traefik.tcp.routers.xui-shadowsocks-r.service=xui-shadowsocks-s"
      - "traefik.tcp.routers.xui-shadowsocks-r.entrypoints=p8080"
      - "traefik.tcp.services.xui-shadowsocks-s.loadbalancer.server.port=8080"
      # Vless/Reality
      - "traefik.tcp.routers.xui-reality-r.rule=HostSNI(`microsoft.com`)"
      - "traefik.tcp.routers.xui-reality-r.tls.passthrough=true"
      - "traefik.tcp.routers.xui-reality-r.service=xui-reality-s"
      - "traefik.tcp.routers.xui-reality-r.entrypoints=websecure"
      - "traefik.tcp.services.xui-reality-s.loadbalancer.server.port=443"
    networks:
      traefik:
#      xui-bridge:
networks:
  traefik:
    external: true
#  xui-bridge:
#    name: xui

Второй вариант

Идея в том, что нужно создать роутер, который будет отправлять запросы на поддельный адрес (www.microsoft.com в моем случае) на 443 порт контейнера. Для этого используется директива HostSNI.
443 порт в контейнере должен быть свободен, веб-морду и сервис подписок вешайте на другие порты (я на них делаю роутинг через отдельные поддомены, traefik запрашивает сертификат сразу на все поддомены, wildcard certificate).

version: "3.9"

services:
  xui:
    image: alireza7/x-ui
    container_name: x-ui
    volumes:
      - ПУТЬ_ДЛЯ_БЕКАПА_НАСТРОЕК:/etc/x-ui/
    environment:
      PUID: 1000
      PGID: 1000
      TZ: America/Los_Angeles #ПОМЕНЯЙТЕ НА СВОЙ ЧАСОВОЙ ПОЯС
      XRAY_VMESS_AEAD_FORCED: "false"
    networks:
      - intranet
    restart: unless-stopped
    labels:
      - traefik.http.routers.xui.rule=Host(`xui.ДОМЕН_TRAEFIK.com`)
      - traefik.http.routers.xui.service=xui-web-config
      - traefik.http.services.xui-web-config.loadbalancer.server.port=54321 #ПОРТ ДЛЯ Web-морды

      - traefik.http.routers.xui-sub.rule=Host(`sssub.ДОМЕН_TRAEFIK.com`)
      - traefik.http.routers.xui-sub.service=xui-subscription
      - traefik.http.services.xui-subscription.loadbalancer.server.port=4433 #ПОРТ для подписок

      - traefik.tcp.routers.vless.rule=HostSNI(`www.microsoft.com`) 
      - traefik.tcp.routers.vless.tls.passthrough=true
      - traefik.tcp.routers.vless.service=xui-reality
      - traefik.tcp.services.xui-reality.loadbalancer.server.port=443
      
networks:
  intranet: #СЕТЬ В КОТОРОЙ РАБОТАЕТ TRAEFIK
    external: true

Единственная проблема в том, что в веб-морде неправильно отображается ссылка на подписки, приходится менять http на https в начале и удалять порт.

Думаю тема достаточно актуальна и может быть полезна для настройки нескольких протоколов на сервере не только для меня.

Если кто-то уже использует связку Traefik и 3X-UI, не могли бы вы поделиться своим конфигом compose файла?

это для того, чтобы все сервисы были на 1 порту?

Ну можно и так сказать. Просто сайт который стоит на VPS желательно тоже на 443/80 ставить, чтоб посетители не указывали порт при подключении, и Reality очень желательно на 443 ставить.

По сути на 443/80 стоит Traefik, а он уже в зависимости от запроса направляет его куда нужно.

Можно место traefik использовать nginx, но мне traefik показался более удобным ещё и certbot отдельно ставить не нужно.

В китае возможно да, но если вы в рф, то reality будет только замедлять соединение, в данный момент не вижу в нем смысла

Настраивал на основе этих конфигов и рекомендаций.

У меня конфиг самого прокси аналогичен роутеру xui-reality-r из первого варианта; но использую другой домен, не www.microsoft.com.
В итоге имею 3 сервиса: 1) поддомен веб-морды 3X-UI; 2) поддомен для подписок (публично доступный); 3) поддомен для самого прокси reality.

Пример конфига

docker-compose.yml

---
version: '3'

networks:
  traefik_network:
    external:
      name: xxx

services:
  3x-ui:
    image: ghcr.io/mhsanaei/3x-ui:latest
    container_name: 3x-ui
    networks:
      - xxx
    volumes:
      - $PWD/db/:/etc/x-ui/
      - $PWD/cert/:/root/cert/
    environment:
      XRAY_VMESS_AEAD_FORCED: "false"
    tty: true
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=xxx"

      - "traefik.tcp.routers.xui-proxy.service=xui-proxy"
      - "traefik.tcp.routers.xui-proxy.entrypoints=https"
      - "traefik.tcp.routers.xui-proxy.rule=HostSNI(`foo.bar`,`foo.{{ mydomain }}`)"
      - "traefik.tcp.routers.xui-proxy.tls=true"
      - "traefik.tcp.routers.xui-proxy.tls.passthrough=true"
      - "traefik.tcp.services.xui-proxy.loadbalancer.server.port=443"

      - "traefik.http.routers.xui-admin.service=xui-admin"
      - "traefik.http.services.xui-admin.loadbalancer.server.scheme=http"
      - "traefik.http.services.xui-admin.loadbalancer.server.port=2053"
      - "traefik.http.routers.xui-admin-http.service=xui-admin"
      - "traefik.http.routers.xui-admin-http.rule=Host(`xui-admin.{{ mydomain }}`)"
      - "traefik.http.routers.xui-admin-http.entrypoints=http"
      - "traefik.http.routers.xui-admin.rule=Host(`xui-admin.{{ mydomain }}`)"
      - "traefik.http.routers.xui-admin.entrypoints=https"
      - "traefik.http.routers.xui-admin.tls=true"
      - "traefik.http.routers.xui-admin.tls.certresolver=http"

      - "traefik.http.routers.xui-sub.service=xui-sub"
      - "traefik.http.services.xui-sub.loadbalancer.server.scheme=http"
      - "traefik.http.services.xui-sub.loadbalancer.server.port=2096"
      - "traefik.http.routers.xui-sub-http.service=xui-sub"
      - "traefik.http.routers.xui-sub-http.rule=Host(`xui-sub.{{ mydomain }}`)"
      - "traefik.http.routers.xui-sub-http.entrypoints=http"
      - "traefik.http.routers.xui-sub-http.middlewares=redirect@file"
      - "traefik.http.routers.xui-sub.rule=Host(`xui-sub.{{ mydomain }}`)"
      - "traefik.http.routers.xui-sub.entrypoints=https"
      - "traefik.http.routers.xui-sub.tls=true"
      - "traefik.http.routers.xui-sub.tls.certresolver=http"
      # Fix host for subscription response body
      - "traefik.http.middlewares.xui-sub-fix-host.headers.customrequestheaders.host=foo.{{ mydomain }}"
      - "traefik.http.routers.xui-sub.middlewares=xui-sub-fix-host@docker"

traefik.yaml

entryPoints:
  http:
    address: :80
  https:
    address: :443
    http:
      tls:
        options: default
        certResolver: 'http'

Я попробовал настроить еще раз, раньше вообще ничего не работало, теперь веб-морда запустилась, но xrey и подписки не заводится.

Может вы заметите чего-нибудь?

traefik.yml
entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

http:
  routers:
    http-catchall:
      rule: hostregexp(`{host:.+}`)
      entrypoints:
      - http
      middlewares:
      - redirect-to-https
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: false

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik
  file:
    directory: /traefik/config
    watch: true

certificatesResolvers:
  letsEncrypt:
    acme:
      email: myemail@email.com
      storage: /ssl/acme.json
      httpChallenge:
        entryPoint: http
traefik docker-compose.yml
version: '3.9'

services:
  traefik:
    image: traefik:v2.2
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 80:80
      - 443:443 
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./logs/:/var/log/traefik/
      - ./traefik.yml:/traefik.yml:ro
      - ./config/:/traefik/config/:ro
      - ./ssl/acme.json:/ssl/acme.json
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.entrypoints=https"
      - "traefik.http.routers.traefik.rule=Host(`traefik.mydomain.io`)"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=login:password"
      - "traefik.http.routers.traefik.middlewares=traefik-auth"

networks:
  default:
    name: traefik
3x-ui docker-compose.yml
version: "3.9"

services:
  3x-ui:
    image: ghcr.io/mhsanaei/3x-ui:latest
    container_name: 3x-ui
    volumes:
      - $PWD/db/:/etc/x-ui/
      - $PWD/cert/:/root/cert/
    environment:
      XRAY_VMESS_AEAD_FORCED: "false"
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - "traefik.enable=true"

      # Web panel
      - "traefik.http.routers.xui-admin-http.service=xui-admin"
      - "traefik.http.routers.xui-admin-http.rule=Host(`vless.mydomain.io`)"
      - "traefik.http.routers.xui-admin-http.entrypoints=http"

      - "traefik.http.routers.xui-admin.service=xui-admin"
      - "traefik.http.routers.xui-admin.rule=Host(`vless.mydomain.io`)"
      - "traefik.http.routers.xui-admin.entrypoints=https"
      - "traefik.http.routers.xui-admin.tls=true"
      - "traefik.http.routers.xui-admin.tls.certresolver=http"

      - "traefik.http.services.xui-admin.loadbalancer.server.scheme=http"
      - "traefik.http.services.xui-admin.loadbalancer.server.port=web-panel-port"

      # Subscriptions
      - "traefik.http.routers.xui-sub-http.service=xui-sub"
      - "traefik.http.routers.xui-sub-http.rule=Host(`vless-sub.mydomain.io`)"
      - "traefik.http.routers.xui-sub-http.entrypoints=http"

      - "traefik.http.routers.xui-sub.service=xui-sub"
      - "traefik.http.routers.xui-sub.rule=Host(`vless-sub.moocluck.ru`)"
      - "traefik.http.routers.xui-sub.entrypoints=https"
      - "traefik.http.routers.xui-sub.tls=true"
      - "traefik.http.routers.xui-sub.tls.certresolver=http"

      - "traefik.http.services.xui-sub.loadbalancer.server.scheme=http"
      - "traefik.http.services.xui-sub.loadbalancer.server.port=subscriptions-port"

      # Vless/Reality
      - "traefik.tcp.routers.xui-proxy.service=xui-proxy"
      - "traefik.tcp.routers.xui-proxy.entrypoints=https"
      - "traefik.tcp.routers.xui-proxy.rule=HostSNI(`preinformer.com`)"
      - "traefik.tcp.routers.xui-proxy.tls=true"
      - "traefik.tcp.routers.xui-proxy.tls.passthrough=true"

      - "traefik.tcp.services.xui-proxy.loadbalancer.server.port=443"

networks:
  traefik: 
    external: true

Ошибка xrey:

XRAY: Failed to start: app/proxyman/inbound: failed to listen TCP on 443 > transport/internet:failed to listen on address: 188.212.125.206:443 > transport/internet/tcp: failed to listen TCP on 188.212.125.206:443 > listen tcp 188.212.125.206:443: bind: cannot assign requested address

Похоже на проблемы в конфигурации самого docker / traefik, без учета специфики 3x-ui. Поищи документацию или начни с рабочего примера traefik.

Для network traefik у меня определено так:

networks:
  traefik:
    name: foo
    driver: bridge

Проверю, но думаю дело не в настройке сети, потому что веб морда заводится, а значит traefik видит контейнер с 3x-ui и направляет трафик на морду, значит контейнеры работают в одной сети