opened image

Продакшн-стек 3x-ui + Caddy + Cloudflare

Цель: поднять устойчивый VLESS-сервер (Xray через 3x-ui), спрятанный за TLS и CDN Cloudflare, чтобы клиенты подключались по обычному https://-домену, а не к «голому» IP и нестандартным портам.

 

В статье разбираем полный рабочий вариант:

  • 3x-ui/Xray в Docker

  • Caddy как reverse proxy и TLS-терминатор

  • Cloudflare как фронт и маскировка трафика

  • VLESS + WebSocket + TLS

  • Автоматическая генерация корректных ссылок / QR-кодов для v2rayNG через 3x-ui (без ручного редактирования профилей)

 

В примерах используем домен:

  • основной домен: your.site

  • VPN-сабдомен: vpn.your.site

Подставьте свои домены и пути, где нужно.

 

 

1. Архитектура решения

 

Цепочка выглядит так:

Клиент (v2rayNG, etc.)
   ⇅  TLS+WS (443, wss://vpn.your.site/ws)
Cloudflare (оранжевое облако, HTTPS proxy)
   ⇅  HTTPS
Caddy (порт 443 на сервере)
   ⇅  ws (локально)
Xray (3x-ui, VLESS+WS без TLS на 10000 порту)
   ⇅  Интернет

 

Ключевые моменты:

  • Клиент всегда ходит на vpn.your.site:443 по TLS.

  • Xray внутри сервера слушает локальный порт 10000 без TLS.

  • Caddy принимает HTTPS, делает TLS-рукопожатие и проксирует WebSocket на Xray.

  • Cloudflare видит обычный https://-запрос и не отличает его от легитимного сайта.

 

2. Требования к серверу

  • Linux-сервер/VPS с публичным IP

  • Установленные Docker и Docker Compose

  • Зарегистрированный домен, подключённый к Cloudflare

 

Пусть файлы стека лежат в /opt/vpn-stack.

 

sudo mkdir -p /opt/vpn-stack/{db,cert,caddy,www,caddy/data,caddy/config}
cd /opt/vpn-stack

 

Создадим простую заглушку-сайт, который будет открываться в браузере (полезно для проверки HTTPS):

cat > www/index.html << 'EOF'
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>your VPN</title>
  </head>
  <body>
    <h1>your.site</h1>
    <p>Service node is up and running.</p>
  </body>
</html>
EOF

 

 

3. Docker Compose: 3x-ui + Caddy

 

Файл docker-compose.yml:

services:
  3x-ui:
    image: ghcr.io/mhsanaei/3x-ui:latest
    container_name: 3x-ui
    hostname: vpn.your.site
    volumes:
      - ./db:/etc/x-ui
      - ./cert:/root/cert
    environment:
      XRAY_VMESS_AEAD_FORCED: "false"
      XUI_ENABLE_FAIL2BAN: "true"
      TZ: "Europe/Amsterdam"   
    restart: unless-stopped
    networks:
      - proxy

  caddy:
    image: caddy:2.8-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./www:/var/www/html
    networks:
      - proxy

networks:
  proxy:
    driver: bridge

 

Здесь:

  • 3x-ui работает только внутри Docker-сети proxy.

  • Caddy - единственный сервис, который слушает порты 80/443 наружу.

  • vpn.your.site - замените на ваш домен

 

 

4. Конфигурация Caddy

 

Файл caddy/Caddyfile:

{
  email admin@your.site
}

vpn.your.site {
  encode gzip

  # === VLESS WebSocket → Xray (порт 10000) ===
  reverse_proxy /ws* 3x-ui:10000

  # === Всё остальное → панель 3x-ui (порт 2053) ===
  reverse_proxy 3x-ui:2053

  header {
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    X-Content-Type-Options nosniff
    X-Frame-Options SAMEORIGIN
    Referrer-Policy strict-origin-when-cross-origin
    -Server
    -X-Powered-By
  }
}

 

Что здесь важно:

  • Caddy сам получит сертификат Let’s Encrypt для vpn.your.site.

  • Любой запрос на https://vpn.your.site/ws проксируется на 3x-ui:10000 (VLESS+WS).

  • Всё остальное (/, /panel, /login, /assets/...) уходит на 3x-ui:2053 — панель и её статика.

 

 

5. Первый запуск стека

 

Из каталога /opt/vpn-stack:

docker compose up -d

 

Проверяем, что оба контейнера живы:

docker ps

Должны быть 3x-ui и caddy в статусе Up.

 

Первые минуты Caddy может быть занят получением сертификата. Логи:

docker logs --tail=50 caddy

После успешного получения сертификата в логах будет certificate obtained successfully.

 

6. Настройка Cloudflare

DNS

В панели Cloudflare для зоны yuor.site создаём запись:

  • Type: A

  • Name: vpn

  • IPv4 address: IP вашего сервера

  • Proxy status:

    • для первичного теста можно поставить DNS only (серое облако),

    • после тестов — Proxied (оранжевое облако).

SSL/TLS режим

В разделе SSL/TLS → Overview:

  • Режим: Full или Full (strict) (рекомендуется Full strict).

Убедитесь, что Universal SSL для зоны активен, и для *.your.site/your.site есть действующий edge-сертификат.

 

 

 

7. Первый заход в панель 3x-ui

 

Пока Cloudflare в режиме DNS only (серое облако):

  1. Откройте в браузере:

    https://vpn.your.site/
    

    Должна открыться страница панели 3x-ui (логин/редирект на /panel или /login).

  2. Войдите под стандартным логином/паролем (если не меняли), сразу измените их.

 

Панель отвечает — значит Caddy + 3x-ui связаны корректно.

После этого можно включить оранжевое облако (Proxied) и убедиться, что https://vpn.your.site/ продолжает открываться без ошибок SSL.

 

 

8. Создание рабочего VLESS+WS inbound в 3x-ui

 

Теперь настроим входящий VLESS-инбаунд, который будет работать за Caddy.

 

8.1. Базовые поля инбаунда

В панели 3x-ui:

  1. Перейдите в Inbounds / Инбаунды.

  2. Нажмите Add / Добавить.

  3. Заполните верхнюю часть:

  • Протокол: VLESS

  • Включить: да

  • Адрес: пусто или 0.0.0.0

  • Порт: 10000

  • Примечание: например, vless-ws-10000

Остальные лимиты (трафик, срок действия) — по вашему усмотрению.

 

8.2. Клиент (user)

В блоке клиентов:

  • Добавьте пользователя (кнопка +):

    • ID (UUID): сгенерируйте кнопкой Generate

    • Email/Комментарий: для себя, например user@your.site

Этот UUID будем использовать в клиентах (v2rayNG и т.п.).

 

8.3. Транспорт WebSocket (ws)

Ниже, в блоке транспорта:

  • Network / Транспорт: ws (WebSocket)

  • Path / Путь: /ws

  • Host: vpn.your.site

  • Безопасность: none (пусто, TLS на уровне Xray не включаем)

Остальные поля (SNI, ALPN, uTLS, сертификаты) оставляем по умолчанию.

 

8.4. Sniffing

Для начала можно оставить выключенным. При желании включите позже для авто-распознавания доменов.

Сохраните инбаунд.

 

 

9. Настройка External Proxy (чтобы QR/ссылки были сразу правильные)

 

По умолчанию 3x-ui будет генерировать vless-URL вида:

vless://UUID@vpn.your.site:10000?...&security=none

 

Это удобно только при прямом подключении к порту 10000. В нашем случае клиенты должны ходить на vpn.your.site:443 по TLS.

 

Чтобы 3x-ui сам подставлял 443 и TLS, включаем External Proxy.

  1. Откройте только что созданный инбаунд на редактирование.

  2. Пролистайте вниз до блока External Proxy.

  3. Включите переключатель External Proxy.

  4. Заполните поля:

  • Security / Тип: tls

  • Host / Address: vpn.your.site

  • Port: 443

  • (Опционально) uTLS: chrome

  • (Опционально) ALPN: h2,http/1.1

  1. Сохраните инбаунд.

 

Теперь в подробностях клиента URL будет примерно таким:

vless://UUID@vpn.your.site:443?type=ws&encryption=none&path=%2Fws&host=vpn.your.site&security=tls#Имя

 

Этот URL / QR-код уже корректен для v2rayNG, Shadowrocket и других клиентов — никаких ручных правок порта/ TLS не нужно.

 

 

10. Настройка клиента (пример для v2rayNG)

 

Теперь можно либо отсканировать QR-код из 3x-ui, либо добавить профиль вручную.

 

10.1. Ожидаемые параметры профиля

После импорта QR/URI профиль в v2rayNG должен выглядеть так:

  • Type / Protocol: VLESS

  • Server / Address: vpn.your.site

  • Port: 443

  • ID (UUID): ваш UUID из 3x-ui

  • Encryption: none

  • TLS: включен (ON)

  • SNI / Server name: vpn.your.site

  • Transport / Network: ws

  • WS path: /ws

  • WS Host: vpn.your.site

Если всё совпадает — профиль готов.

 

10.2. Тест

 

Подключитесь в v2rayNG → откройте любой сайт.

В панели 3x-ui в этом инбаунде и у конкретного пользователя появится:

  • статус «Был(а) в сети» со временем последнего подключения

  • байты в колонке «Использование» начнут расти

 

 

11. Проверка и типичные проблемы

 

11.1. TLS / SSL ошибки в браузере

 

Если https://vpn.your.site/ выдаёт ERR_SSL_VERSION_OR_CIPHER_MISMATCH или 525/526:

  • проверьте, что Caddy успешно получил сертификат (логи caddy)

  • проверьте, что в Cloudflare:

    • SSL mode = Full / Full strict

    • Universal SSL активен, сертификат для домена выпущен

 

11.2. net/http: TLS handshake timeout в клиенте

 

Чаще всего связано с тем, что:

  • Xray не слушает тот порт, на который проксирует Caddy (проверьте, что инбаунд на 10000, а не на 443);

  • или XRAY падает из-за лишнего инбаунда с TLS и пустыми сертификатами (ищите ошибки в логах 3x-ui).

 

11.3. io: read/write on closed pipe / connect: connection refused

 

Появляется, когда Caddy не может подключиться к 3x-ui:10000:

  • XRAY не запущен или падает на старте;

  • инбаунд выключен или порт изменён;

  • ошибка в Caddyfile (не тот порт, опечатка в 3x-ui:10000).

 

 

12. Добавление второго инбаунда (опционально)

 

Если нужно несколько независимых профилей (например, разные лимиты/страны), можно добавить второй инбаунд.

 

12.1. В 3x-ui

  • Создайте ещё один инбаунд:

    • Протокол: VLESS

    • Адрес: 0.0.0.0

    • Порт: 20000

    • Транспорт: ws

    • Path: /ws2

    • Host: vpn.your.site

    • Безопасность: none

  • Включите External Proxy:

    • Security: tls

    • Host: vpn.your.site

    • Port: 443

 

 

12.2. В Caddyfile

 

Добавьте строку:

reverse_proxy /ws2* 3x-ui:20000

 

Теперь:

  • /ws идёт на порт 10000

  • /ws2 идёт на порт 20000

Клиенты будут отличаться только path (/ws vs /ws2).

 

 

13. Безопасность панели и эксплуатация

 

Несколько рекомендаций для продакшен-использования:

  1. Смените стандартный логин/пароль 3x-ui на сложные.

  2. Закройте панель по IP (через firewall), Cloudflare Access или вынесите её на отдельный поддомен (например, panel.your.site).

  3. Регулярно обновляйте образ ghcr.io/mhsanaei/3x-ui и Caddy.

  4. Делайте резервные копии каталога ./db (там конфигурация 3x-ui и пользователи).

  5. Следите за нагрузкой CPU и сетевым трафиком (htop, iftop) при росте числа клиентов.

 

 

14. Заключение

 

Мы собрали полный рабочий стек:

  • Xray/3x-ui в Docker как ядро VLESS-сервера

  • Caddy как удобный и безопасный TLS-терминатор и reverse proxy

  • Cloudflare как фронтовый слой и маскировка под обычный HTTPS-сайт

  • VLESS + WebSocket + TLS, настроенный так, что клиенты получают корректные профили по ссылке или QR-коду без ручных доработок

 

Такой подход хорошо масштабируется:

  • можно добавлять новые инбаунды (другие пути и порты)

  • выносить панель на отдельный домен

  • переключаться на более современные транспорты (XHTTP/H2/H3), когда они окончательно заменят WebSocket

 

Главное — соблюдать разделение ролей:

  • Xray слушает локальные порты без TLS;

  • Caddy занимается TLS и маршрутизацией;

  • Cloudflare скрывает реальный origin и отдаёт только «чистый» HTTPS.

 

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