Перейти к содержанию

Раздельная установка PasarGuard: панель + нода, VLESS Reality и восемь скрытых граблей

Опубликовано
  • Админы

Классический сценарий для PasarGuard — форка Marzban — это не «одна коробка со всем сразу», а связка из двух машин: отдельная панель и отдельная нода, отдающая трафик. Именно так и стоит запускать прод: панель хранит пользователей и раздаёт подписки, а нода крутит Xray-ядро и принимает клиентов. Ниже — полный разбор такой раздельной установки, где вместо игрушечного Shadowsocks сразу поднимается VLESS + Reality с маскировкой под чужой сайт, а панель закрывается боевым TLS от Let's Encrypt.

Отдельный акцент — на восьми местах, где инсталляция ломается молча: интерфейс при этом не говорит ничего внятного, и почти любую поломку приходится ловить в docker logs. Всё, что здесь описано, снято с реального стенда: панель v5.2.1, нода v0.5.3, ядро Xray 26.3.27. Настраивать будем преимущественно через веб-панель — со скриншотами конкретных экранов; в терминал уходим только за тем, что иначе не сделать.

Договоримся об обозначениях. Домен в примерах — pg-test.gig.ovh. На скриншотах IP заменены документационными (203.0.113.10 — нода, 198.51.100.10 — панель), приватный ключ Reality, api_key ноды и UUID клиента — плейсхолдеры. Везде подставляйте своё.

Как всё устроено

Панель и нода живут на разных серверах намеренно. Панель — это «мозг»: пользователи, ядра, хосты, подписки, статистика. Нода — «мышцы»: на ней работает Xray и держит клиентские соединения. Связывает их управляющий gRPC-канал под самоподписанным TLS.

  • Сервер A (панель): PasarGuard panel + TimescaleDB + Caddy. Наружу открыты только 80 и 443.
  • Сервер B (нода): PasarGuard node + Xray. Слушает управляющий 62050 от панели и клиентов VLESS Reality на 443.

Сколько ресурсов нужно

Обе машины стенда — почти одинаковые VPS. Цифры ниже сняты вживую; это рабочий минимум под одну ноду и несколько пользователей, без свопа.

ПараметрСервер A (панель)Сервер B (нода)
ОСUbuntu 26.04 LTSUbuntu 26.04 LTS
Ядро7.0.0-14-generic7.0.0-14-generic
Архитектураx86_64 (KVM)x86_64 (KVM)
vCPU22
ОЗУ3.3 ГБ3.3 ГБ
Диск99 ГБ (занято ~5 ГБ)79 ГБ (занято ~4 ГБ)

Больше всех на панели ест не сама панель, а TimescaleDB: образ ~2 ГБ и несколько сотен мегабайт в покое. С SQLite панели хватило бы и 1 ГБ, но тогда пропадает статистика по нодам (об этом ниже). Под Timescale держите в уме 2 ГБ.

Порты

ПортГдеНаружу?Зачем
22A, Bда (лучше сузить)SSH
80AдаHTTP → редирект, ACME
443AдаHTTPS: панель и подписки
8000Aнет (localhost)uvicorn за Caddy
5432Aнет (127.0.0.1)TimescaleDB
62050Bтолько с IP панелиgRPC панель↔нода
443Bдаинбаунд VLESS Reality

443 фигурирует дважды, но на разных хостах: на A его держит Caddy, на B — Xray. Пересечения нет.

База: почему Timescale, а не SQLite

SQLite в PasarGuard есть, и для одной локальной ноды его достаточно. Но выносите ноды на отдельные серверы и захотите графики по каждой — упрётесь в ограничение прямо из .env.example:

## Usage recording. Node stats recording is only available for PostgreSQL/TimescaleDB.
# ENABLE_RECORDING_NODES_STATS = False

Запись статистики по нодам живёт только на PostgreSQL/TimescaleDB; на SQLite флаг мёртвый, и раздел статистики по узлам просто исчезает. Поэтому база — TimescaleDB (это PostgreSQL с расширением под временные ряды, что удобно для поминутного трафика).

Важно. Базу выбирают до первого старта — переезд SQLite→PostgreSQL на живой панели болезненный. Есть шанс на вторую ноду и графики — сразу Timescale.

Что приготовить заранее

Чтобы не спотыкаться посреди установки, соберите это до первой команды: два VPS с root и их публичные IP (PANEL_IP, NODE_IP); домен для панели, делегированный на NS Cloudflare (DNS-01 ходит через её API); аккаунт Cloudflare и токен с правами Zone:DNS на зону (My Profile → API Tokens → Create Token, шаблон Edit zone DNS, в Zone Resources — ваша зона; значение показывается один раз); и проверьте, что нужные порты не закрыты у провайдера. A-запись на домен заведите сразу — пока ставится Docker, она успеет разойтись по DNS.

Готовим обе машины

Docker ставим официальным скриптом — он сам подцепит репозиторий и compose-плагин. Одинаково для A и B:

# от root
curl -fsSL https://get.docker.com | sh
docker --version          # Docker version 29.7.2
docker compose version    # Docker Compose version v5.5.0

Проверьте, что доступен именно docker compose (v2-плагин через пробел) — на нём построен весь гайд.

Сервер A: база и панель

Заводим каталог и один раз генерируем секреты — пароль БД и api_key (общий секрет панели и ноды):

mkdir -p /opt/pasarguard && cd /opt/pasarguard
DB_PASS=$(openssl rand -hex 24)
API_KEY=$(cat /proc/sys/kernel/random/uuid)   # обязан быть валидным UUID
printf 'DB_PASS=%s\nAPI_KEY=%s\n' "$DB_PASS" "$API_KEY" > .secrets && chmod 600 .secrets

Посмотреть значения в любой момент — cat /opt/pasarguard/.secrets. Ниже по тексту они идут плейсхолдерами и подставляются руками: DB_PASS — в .env и в compose базы, API_KEY — позже на сервере B и в карточке ноды (забрать: grep API_KEY /opt/pasarguard/.secrets).

ЗначениеГде берётсяКуда подставляется
PANEL_IPпубличный IP сервера AA-запись домена, правило ufw на ноде
NODE_IPпубличный IP сервера Bкарточка ноды, адрес хоста, SAN сертификата
DB_PASSopenssl rand -hex 24 на A.env панели и compose базы
API_KEYUUID на A (.secrets)compose ноды и поле API Key в карточке
доменваш, на NS CloudflareCaddyfile, VITE_BASE_API, ALLOWED_ORIGINS
CF_API_TOKENCloudflare → API Tokenscompose Caddy
PRIVATE_KEY / PUBLIC_KEYdocker exec pg-node xray x25519приватный — в инбаунд; публичный панель подставит в ссылку
shortIdopenssl rand -hex 8инбаунд Core Config (shortIds)

Грабля №1: формат .env и падение --env-file

Оригинальный .env.example идёт «с пробелами вокруг равно»: UVICORN_HOST = "0.0.0.0". Python-овский dotenv это переваривает, а докеровский --env-file — нет:

docker: invalid env file (/opt/pasarguard/.env): variable 'UVICORN_HOST ' contains whitespaces

Пробел утёк в имя переменной ('UVICORN_HOST '). Лечится тем, что .env пишется без пробелов, строго KEY=value. Полный файл с комментариями:

# ==== PasarGuard panel .env (чистый KEY=value, без пробелов) ====

# Слушать только localhost: наружу панель выставляет Caddy.
UVICORN_HOST=127.0.0.1
UVICORN_PORT=8000

# Подключение к базе. Драйвер asyncpg идёт в образе панели.
SQLALCHEMY_DATABASE_URL=postgresql+asyncpg://pasarguard:DB_PASS@127.0.0.1:5432/pasarguard

# Статистика по нодам — только на PostgreSQL/TimescaleDB.
ENABLE_RECORDING_NODES_STATS=True
DISABLE_RECORDING_NODE_USAGE=False

SUBSCRIPTION_PATH=sub
DASHBOARD_PATH=/dashboard/
LOG_LEVEL=INFO

# ==== работа за reverse-proxy Caddy (домен поднимем следующим шагом) ====
UVICORN_PROXY_HEADERS=True
UVICORN_FORWARDED_ALLOW_IPS=127.0.0.1
VITE_BASE_API=https://pg-test.gig.ovh/
ALLOWED_ORIGINS=https://pg-test.gig.ovh

Грабля №2: образы из Docker Hub и 429

В штатном compose образ — pasarguard/panel:latest, то есть Hub, который на пуллах любит отдавать 429 Too Many Requests. Берём зеркало ghcr: ghcr.io/pasarguard/panel:latest и ghcr.io/pasarguard/node:latest. Compose сервера A:

services:
  timescaledb:
    image: timescale/timescaledb:latest-pg17
    container_name: pasarguard-db
    restart: always
    environment:
      POSTGRES_DB: pasarguard
      POSTGRES_USER: pasarguard
      POSTGRES_PASSWORD: DB_PASS
    volumes:
      - /var/lib/pasarguard-db/pgdata:/var/lib/postgresql/data   # отдельно от состояния панели
    ports:
      - "127.0.0.1:5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pasarguard -d pasarguard"]
      interval: 5s
      timeout: 5s
      retries: 20

  pasarguard:
    image: ghcr.io/pasarguard/panel:v5.2.1     # версия со стенда
    container_name: pasarguard
    restart: always
    env_file: .env
    network_mode: host
    volumes:
      - /var/lib/pasarguard:/var/lib/pasarguard
    depends_on:
      timescaledb:
        condition: service_healthy

Поднимаем по очереди — сначала БД до healthy, затем панель:

cd /opt/pasarguard
docker compose up -d          # очерёдность обеспечит depends_on: compose дождётся healthy у базы
docker logs -f pasarguard

Грабля №3: без TLS панель слушает только localhost

После миграций в логе появится предупреждение — не пугайтесь, оно ожидаемо:

IMPORTANT!
You're running PasarGuard without specifying UVICORN_SSL_CERTFILE and UVICORN_SSL_KEYFILE.
The application will only be accessible through localhost...
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Это и есть задумка: панель на 127.0.0.1:8000, а TLS и внешний доступ обеспечит Caddy. Поэтому в .env и стоит UVICORN_HOST=127.0.0.1. Ниже будет и Attention: You have no node — ноду добавим позже.

Открываем панель наружу: Caddy и TLS через Cloudflare DNS-01 (грабля №4)

Панель доступна только на localhost — ставим перед ней Caddy с сертификатом Let's Encrypt по DNS-01 (открытый 80 для ACME не нужен). И тут грабля: обычный caddy:2-alpine не умеет Cloudflare — нужен билд с DNS-модулем, ghcr.io/caddybuilds/caddy-cloudflare:latest. В нём модуль dns.providers.cloudflare уже вкомпилирован, и этот же образ идёт в compose ниже — отдельной команды под проверку не нужно: будь модуль не на месте, Caddy упал бы на directive dns cloudflare с понятной ошибкой.

В зоне Cloudflare заводим A-запись pg-test.gig.ovh → PANEL_IP в режиме DNS only (серое облако). Caddyfile — это reverse-proxy на панель плюс TLS через DNS-01; Caddy сам возьмёт настоящий, доверенный браузерами сертификат Let's Encrypt:

{
    email admin@gig.ovh
}

pg-test.gig.ovh {
    reverse_proxy 127.0.0.1:8000
    tls {
        dns cloudflare {env.CF_API_TOKEN}
        resolvers 1.1.1.1
    }
}
services:
  caddy:
    image: ghcr.io/caddybuilds/caddy-cloudflare:latest
    container_name: caddy
    restart: always
    network_mode: host
    environment:
      CF_API_TOKEN: "CLOUDFLARE_TOKEN"
    volumes:
      - /opt/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - /var/lib/caddy/data:/data
      - /var/lib/caddy/config:/config
mkdir -p /opt/caddy && cd /opt/caddy   # сюда кладём Caddyfile и docker-compose.yml
docker compose up -d
docker logs -f caddy
# http.acme_client  trying to solve challenge  challenge_type=dns-01
# http.acme_client  authorization finalized  authz_status=valid
# tls.obtain  certificate obtained successfully

Во время челленджа в зоне на секунды появляется TXT _acme-challenge.pg-test.gig.ovh и затем убирается — так работает DNS-01. Проверяем сертификат снаружи:

echo | openssl s_client -connect pg-test.gig.ovh:443 -servername pg-test.gig.ovh 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates
# issuer=C=US, O=Let's Encrypt, CN=YE2
# subject=CN=pg-test.gig.ovh
# notBefore=... notAfter=... (90 дней)

Ключи для работы за прокси мы задали в .env ещё на старте, поэтому перезапускать панель не нужно — домен сразу отдаёт дашборд:

curl -s -o /dev/null -w "%{http_code}\n" https://pg-test.gig.ovh/dashboard/   # 200

Готово: панель открывается по https://pg-test.gig.ovh с валидным сертификатом, дальше работаем в браузере. Опционально: при отладке, чтобы не жечь лимиты боевого Let's Encrypt, можно временно добавить в глобальный блок Caddyfile строку acme_ca https://acme-staging-v02.api.letsencrypt.org/directory (сертификат будет недоверенный — это норма для проверки), затем убрать её, удалить /var/lib/caddy/data/caddy/certificates и перезапустить Caddy.

Грабля №5: владелец и мёртвый SUDO_USERNAME

Попытка войти статическим админом из .env в 5.2.1 упирается в отлуп:

env admin not allowed in production

В проде env-админ запрещён специально. Владелец создаётся одноразовым ключом через CLI, и тут вторая ловушка: команды pasarguard в PATH нет, cli_wrapper.sh не исполняемый. Работает только прямой вызов Python внутри контейнера (рабочий каталог /code):

docker exec pasarguard python /code/pasarguard-cli.py generate-temp-key
# Temp key: 3e2a706a-....
# This key is valid for 5 minutes and can only be used once.

Ключ действует 5 минут и однократно. Панель уже открывается по домену — идём на https://pg-test.gig.ovh/dashboard/, на логине жмём Owner access, режим Create и вводим ключ, логин и пароль владельца.

Экран логина PasarGuard, кнопка Owner access

После входа попадаем на дашборд — дальше почти всё мышкой.

Дашборд PasarGuard

Карта интерфейса: за что отвечает каждая вкладка

Меню слева — это весь PasarGuard целиком. Коротко по каждому пункту.

  • Dashboard — сводка: онлайн, активные, суммарный трафик, состояние нод. Только мониторинг.
  • Users — сердце панели: создание клиентов, их лимиты, сроки, статусы, привязка к группам, а по иконкам в строке — ссылка-подписка и QR. Тут проходит основная ежедневная работа.
  • Statistics — графики трафика по пользователям и по нодам; работают только на PostgreSQL/TimescaleDB.
  • Hosts — «витрина» подключений: инбаунд превращается в клиентскую ссылку только через хост (адрес, порт, SNI, отпечаток). Один инбаунд может иметь несколько хостов.
  • Groups — набор инбаундов, который выдаётся пользователю; удобно делать «тарифы».
  • Admins и Admin Roles — дополнительные администраторы и ограничение их прав (например, реселлеры).
  • API Keys — ключи для автоматизации и биллинга.
  • Nodes — узлы и ядро. Вкладки: Nodes (список и статусы), Core Configs (конфиги Xray: инбаунды, исходящие, маршрутизация), WireGuard (подсети/пиры WG) и Logs (живые логи ядра с ноды).
  • Templates — шаблоны страниц подписки и настроек пользователей по умолчанию.
  • Bulk — массовые операции над пользователями.
  • Settings — общие настройки панели: подписки, уведомления, оформление.

Раздел Statistics: CPU, RAM, диск, трафик

Дальше мы и пройдём по этой карте: подключим ноду (Nodes), настроим на ней протокол (Core Configs), выставим наружу (Hosts), соберём в тариф (Groups) и выдадим клиенту (Users).

Сервер B: нода и её сертификат

Грабля №6: без сертификата нода не стартует, а без SAN — не подключается

Канал панель↔нода шифруется TLS, и сертификат обязан существовать до первого запуска, иначе контейнер циклится:

open /var/lib/pg-node/certs/ssl_cert.pem: no such file or directory

Сделали сертификат «обычной» командой без SAN — панель отвергает ноду на подключении:

[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
IP address mismatch, certificate is not valid for 'NODE_IP'

Панель сверяет именно SAN с IP, а не CN. Правильная генерация добавляет subjectAltName=IP:...:

mkdir -p /var/lib/pg-node/certs && cd /var/lib/pg-node/certs
openssl req -x509 -newkey rsa:2048 -keyout ssl_key.pem -out ssl_cert.pem -days 3650 -nodes \
  -subj "/CN=NODE_IP" -addext "subjectAltName=IP:NODE_IP"

openssl x509 -in ssl_cert.pem -noout -text | grep -A1 "Subject Alternative Name"
# X509v3 Subject Alternative Name:
#     IP Address:NODE_IP

Compose ноды (образ из ghcr; API_KEY — тот самый UUID с панели; нужен NET_ADMIN и сеть хоста):

services:
  node:
    image: ghcr.io/pasarguard/node:v0.5.3      # версия со стенда
    container_name: pg-node
    restart: always
    network_mode: host
    cap_add:
      - NET_ADMIN
    environment:
      SERVICE_PORT: 62050
      SERVICE_PROTOCOL: "grpc"
      SSL_CERT_FILE: "/var/lib/pg-node/certs/ssl_cert.pem"
      SSL_KEY_FILE: "/var/lib/pg-node/certs/ssl_key.pem"
      API_KEY: "API_KEY"
      GENERATED_CONFIG_PATH: "/var/lib/pg-node/generated"
    volumes:
      - /var/lib/pg-node:/var/lib/pg-node
mkdir -p /opt/pg-node && cd /opt/pg-node   # сюда кладём docker-compose.yml
docker compose up -d
docker logs pg-node
# Starting Node: v0.5.3
# gRPC Server listening on 0.0.0.0:62050

Строку Failed to load env file ... open .env игнорируем — переменные пришли через environment:.

Файрвол ноды

Управляющий 62050 открываем только для IP панели, SSH разрешаем до включения ufw:

ufw allow 22/tcp
ufw allow from PANEL_IP to any port 62050 proto tcp   # управляющий канал — только с панели
ufw allow 443/tcp                                     # клиентский инбаунд VLESS Reality
ufw --force enable
ufw status numbered

Политика ufw по умолчанию — deny incoming, поэтому отдельное запрещающее правило для 62050 не нужно: всё, что не разрешено явно, отбрасывается ещё на SYN.

Безопасность. С посторонней машины nc -zv NODE_IP 62050 обязан таймаутиться, с панели — отвечать succeeded. Управляющий канал даёт полный доступ к ядру ноды, в открытый интернет его выпускать нельзя.

Подключаем ноду в панели (Nodes)

В Nodes → Nodes жмём Create Node. Форма короткая, но одно поле коварно — и при ошибке причина видна только в docker logs pasarguard, тогда как в UI просто краснеет точка.

Форма создания/редактирования ноды

ПолеЗначениеКомментарий
Node Nametest-node-1любое имя
Node AddressNODE_IPпубличный IP сервера B
Node Port62050gRPC-порт
Core ConfigurationDefault Core Configкакое ядро применить
API KeyAPI_KEYUUID из переменных ноды
Certificatessl_cert.pem с нодыцеликом, с BEGIN/END

Грабля №7: не тот сертификат в карточке

В поле Certificate (в API — server_ca) кладут сертификат с ноды целиком. Подсунете чужой (скажем, свежесгенерированный на панели) — отвал, и причина снова только в логе:

certificate verify failed: self-signed certificate

Сертификат берём так: cat /var/lib/pg-node/certs/ssl_cert.pem. Успех в логе панели:

Node-operation - New node "test-node-1" with id "1" added by admin "admin"
Node-operation - Connecting to "test-node-1" node
Node-operation - Connected to "test-node-1" node v0.5.3, core run on v26.3.27

А в списке нод точка зеленеет, появляются версии ноды (0.5.3) и ядра (26.3.27):

Список нод: зелёный статус и версии

Красный статус? Не гадайте по UI — открывайте docker logs pasarguard.

VLESS + Reality: узел, который выглядит как чужой сайт

Дефолтный Shadowsocks хорош лишь для проверки «а оно вообще заводится». Боевой протокол на Xray сегодня — VLESS + Reality. Суть: нода не поднимает собственный TLS (который сам по себе выдаёт прокси), а на рукопожатии подделывается под чужой сайт, заимствуя TLS у реального внешнего хоста (например, www.microsoft.com). Снаружи 443 на ноде неотличим от настоящего Microsoft: тот же сертификат, та же цепочка. Своего домена и сертификата для клиента не требуется.

Шаг 1. Ключи Reality

Reality опирается на пару x25519 и короткий shortId. Ключи выдаёт само ядро прямо в контейнере ноды:

docker exec pg-node xray x25519
# PrivateKey: <PRIVATE_KEY>
# Password (PublicKey): <PUBLIC_KEY>
# Hash32: ...

openssl rand -hex 8    # shortId, например 86d173896823210b

PrivateKey — в конфиг инбаунда на ноде, PublicKey (pbk) панель сама подставит в клиентскую ссылку. Сохраните обе половины.

Шаг 2. Инбаунд в Core Config

В Nodes → Core Configs открываем Default Core Config, на вкладке Inbounds жмём Add inbound: тип vless, порт 443, security reality. Проще всего вставить готовый JSON (через {}-редактор), заменив плейсхолдеры своими ключами:

{
  "tag": "VLESS Reality",
  "listen": "0.0.0.0",
  "port": 443,
  "protocol": "vless",
  "settings": { "clients": [], "decryption": "none" },
  "streamSettings": {
    "network": "tcp",
    "security": "reality",
    "realitySettings": {
      "show": false,
      "dest": "www.microsoft.com:443",
      "xver": 0,
      "serverNames": ["www.microsoft.com"],
      "privateKey": "<PRIVATE_KEY>",
      "shortIds": ["86d173896823210b"]
    }
  },
  "sniffing": { "enabled": true, "destOverride": ["http", "tls", "quic"] }
}

Оставляем галку Restart Nodes и сохраняем — панель раскатает конфиг на ноду и перезапустит ядро. В списке появляется VLESS Reality / vless / 443:

Core Config: инбаунд VLESS Reality на порту 443

Про «жертву». dest/serverNames должны указывать на реально живой чужой сайт на 443 с TLS 1.3, не связанный с вами и не заблокированный у ваших клиентов (крупные CDN, www.microsoft.com, www.cloudflare.com). И откройте порт инбаунда: ufw allow 443/tcp.

Шаг 3. Хост (Hosts)

Инбаунд есть, но клиент о нём не знает — нужен хост. В Hosts → Add Host: инбаунд VLESS Reality, адрес — IP ноды, порт 443, в Security Settings — SNI www.microsoft.com и fingerprint chrome.

Форма хоста: инбаунд VLESS Reality

Важно. Поле Security в хосте принимает лишь Inbound Default, none или tls — значения reality там нет. Для Reality берите Inbound Default: хост унаследует reality из инбаунда, а pbk и sid панель подставит сама. Ручной security: reality даст 422.

Шаг 4. Группа (Groups)

Чтобы инбаунд дошёл до пользователя, он должен быть в группе. В Groups создаём test-group и включаем в неё VLESS Reality.

Группа test-group с инбаундом VLESS Reality

Клиент и его подключение (Users)

Users → Create User: имя, при желании лимит и срок, и обязательно — группа test-group.

Создание пользователя с выбором группы

Пользователь появляется со статусом Active; в строке — иконки копирования подписки, ссылки и QR.

Список пользователей

Клик по QR открывает страницу подписки: QR-код и список конфигураций (VLESS-Reality-node1 с адресом ноды). Именно это вы отдаёте клиенту — он сканирует QR или вставляет ссылку в v2rayNG / v2rayN / Hiddify.

Страница подписки: QR и конфигурация VLESS Reality

Подписка отдаёт готовый vless:// со всеми параметрами Reality:

vless://<UUID>@NODE_IP:443?encryption=none&security=reality&type=tcp&headerType=none
        &sni=www.microsoft.com&fp=chrome&pbk=<PUBLIC_KEY>&sid=86d173896823210b#VLESS-Reality-node1

Проверка, что маскировка живая

Ссылки мало — убедимся, что Reality реально притворяется. TLS-проба на порт ноды с именем «жертвы» должна вернуть настоящий сертификат Microsoft: для наблюдателя 443 на вашей ноде выглядит как сервер Microsoft.

echo | openssl s_client -connect NODE_IP:443 -servername www.microsoft.com 2>/dev/null \
  | openssl x509 -noout -issuer -subject
# issuer=C=US, O=Microsoft Corporation, CN=Microsoft TLS G2 RSA CA OCSP 04
# subject=C=US, ST=WA, L=Redmond, O=Microsoft Corporation, CN=www.microsoft.com

Цепочка Microsoft — значит Reality поднят и корректно одалживает чужой TLS. Импортируйте vless:// в клиент и проверьте реальный выход.

Устойчивость к перезапуску

Проверим, что нода сама возвращается после рестарта всего стека:

cd /opt/pg-node && docker compose restart
cd /opt/pasarguard && docker compose restart
cd /opt/caddy && docker compose restart

Через полминуты-минуту статус снова connected, а в логе панели — свежая пара Connecting to ... / Connected to. Руками ничего делать не нужно.

Если не завелось: симптом → где смотреть

СимптомКуда смотретьОбычная причина
домен не открывается, сертификат не выпустилсяdocker logs caddyневерный токен CF, A-запись не доехала, домен не на NS Cloudflare
домен открылся, но пусто или 502docker logs pasarguardпанель не поднялась (чаще — пароль базы), либо неверный VITE_BASE_API
панель не стартует, ошибки про базуdocker logs pasarguard-dbне подставлен DB_PASS или он разъехался с compose
нода в вечном рестартеdocker logs pg-nodeнет файлов сертификата в /var/lib/pg-node/certs
у ноды красная точкаdocker logs pasarguardсертификат без SAN, не тот сертификат в карточке, не совпал api_key, закрыт 62050
ссылка импортируется, трафика нетdocker logs pg-node + Statisticsзакрыт 443, разъехались pbk/sid/sni

Бэкап и обновление

Состояние панели — база и /var/lib/pasarguard:

docker exec pasarguard-db pg_dump -U pasarguard pasarguard | gzip > pg_$(date +%F).sql.gz
tar czf pasarguard_state_$(date +%F).tar.gz /opt/pasarguard/.env /opt/pasarguard/docker-compose.yml /var/lib/pasarguard

На ноде бэкапить нечего, кроме ssl_cert.pem/ssl_key.pem. Помните: сертификат ноды лежит в двух местах — файл и копия в поле Certificate карточки; при замене (например, новый IP → новый SAN) правьте оба, иначе будет self-signed certificate.

Обновление — это перетягивание образов. Перед панелью снимите дамп: alembic-миграции накатываются автоматически и назад не откатываются.

cd /opt/pasarguard && docker compose pull pasarguard && docker compose up -d pasarguard
cd /opt/pg-node && docker compose pull node && docker compose up -d

Грабля №8 (и совет напоследок). Тег latest удобен, но лишает контроля над мажорными обновлениями с ломающими изменениями — на проде фиксируйте конкретный тег (ghcr.io/pasarguard/panel:v5.2.1). А базовый образ Caddy без Cloudflare-модуля — восьмая грабля: не забудьте именно сборку caddy-cloudflare. И держите открытым docker logs — в PasarGuard почти всё диагностируется там, а не в интерфейсе.

Источники

Вопрос к залу. Какой сайт-«жертву» для Reality вы держите на проде — крупный CDN, нейтральный зарубежный ресурс или собственный домен? И чем закрываете управляющий 62050: голым ufw по IP панели, WireGuard/приватной сетью между панелью и нодами или отдельным туннелем? Любопытно собрать разные схемы.

Featured Replies

No posts to show

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Последние посетители 0

  • Ни одного зарегистрированного пользователя не просматривает данную страницу
Яндекс.Метрика

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.