پرش به مطلب اصلی

صفحه اشتراک روی همان سرورِ پنل

کد منبع

پنل — xray-panel/backend · نود — xray-panel/node · صفحه اشتراک — xray-panel/subscription-page

صفحه اشتراک فهرست آراسته اتصال‌های کاربر را به او ارائه می‌دهد: لینک‌های کلاینت‌ها، کدهای QR، تاریخ انقضا و باقی‌مانده ترافیک. داده‌ها را با API-توکن از پنل می‌گیرد و ساختار درونی پنل را به مرورگر افشا نمی‌کند. کلاینت آن را روی دامنه sub.example.com با یک کد کوتاه در مسیر باز می‌کند.

این گزینه برای حالتی است که پنل و صفحه اشتراک روی یک هاست قرار دارند. کانتینرها از طریق شبکه داخلی Docker به هم متصل می‌شوند و فقط nginx به بیرون نگاه می‌کند. اگر پنل و اشتراک باید روی ماشین‌های جداگانه باشند، به صفحه صفحه اشتراک روی سرور جداگانه بروید.

چه زمانی به صفحه اشتراک نیاز است​

صفحه اشتراک اختصاصی زمانی لازم است که زیردامنه جداگانه sub.example.com، طراحی خودتان و به‌روزرسانی‌های مستقل از پنل را می‌خواهید. پنل می‌تواند صفحه داخلی خود را ارائه دهد، اما آن شکل دیگری دارد و روی دامنه پنل قرار دارد. تا زمانی که در SUB_PUBLIC_DOMAIN مقداری مانند panel.example.com/api/sub نوشته شده باشد، کلاینت‌ها صفحه داخلی پنل را دریافت می‌کنند، نه صفحه‌ای که شما نصب کرده‌اید.

چه چیزهایی لازم است​

موردمقدار
دامنهsub.example.com
پنلروی همان هاست، کانتینر xpanel
API-توکندر پنل ساخته می‌شود
Docker و Docker Composeنصب شده باشند
nginx و certbotنصب شده باشند
پورت محلی3010

API-توکن در پنل ساخته می‌شود: Settings → API Tokens → Create token. مقدار آن فقط یک‌بار نشان داده می‌شود — آن را ذخیره کنید.

هشدار

هم پنل و هم صفحه اشتراک اگر درخواست بدون هدرهای X-Forwarded-For و X-Forwarded-Proto: https برسد، اتصال را قطع می‌کنند. بنابراین پیش از صفحه، پروکسی معکوس با TLS الزامی است.

فایل محیط​

یک دایرکتوری و فایل محیط بسازید:

mkdir -p /opt/xlada-subpage && cd /opt/xlada-subpage

cat > .env <<'EOF'
APP_PORT=3010

# پنل در شبکه مشترک Docker — دسترسی با نام کانتینر.
REMNAWAVE_PANEL_URL=http://xpanel:3000
REMNAWAVE_API_TOKEN=paste_token_from_panel

# پیشوند مسیر، اگر صفحه در ریشه ارائه نمی‌شود. بدون اسلش در دو طرف.
CUSTOM_SUB_PREFIX=

# تعداد هاپ‌های پروکسی معکوس که هنگام تشخیص IP کلاینت مورد اعتماد است.
TRUST_PROXY=1

# X-Api-Key برای احراز هویت خارجی: Caddy با افزونه امنیتی یا Tiny Auth.
CADDY_AUTH_API_TOKEN=

# Cloudflare Zero Trust.
CLOUDFLARE_ZERO_TRUST_CLIENT_ID=""
CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET=""

# پذیرش لینک‌های قدیمی Marzban.
MARZBAN_LEGACY_LINK_ENABLED=false
MARZBAN_LEGACY_SECRET_KEY=
MARZBAN_LEGACY_SUBSCRIPTION_VALID_FROM=
EOF

chmod 600 .env

نام‌های REMNAWAVE_* برای سازگاری حفظ شده‌اند — به این ترتیب صفحه با پنل قدیمی Remnawave هم کار می‌کند. متغیرهای خالی را همان‌طور که هستند رها کنید.

Docker Compose​

docker-compose.yml
services:
xpanel-subpage:
image: ghcr.io/xray-panel/subscription-page:latest
container_name: xpanel-subpage
restart: unless-stopped
env_file: .env
ports:
- 127.0.0.1:3010:3010
networks:
- xpanel-network

networks:
xpanel-network:
name: xpanel-network
external: true

شبکه xpanel-network از قبل توسط پنل ساخته شده است، بنابراین اینجا به‌صورت external متصل می‌شود. کانتینر را راه‌اندازی کنید:

docker compose up -d
docker compose logs -f xpanel-subpage

صفحه به 127.0.0.1:3010 گوش می‌دهد و به بیرون نگاه نمی‌کند: دسترسی به آن فقط از طریق nginx ممکن است.

گواهی​

ابتدا یک بلوک موقت روی پورت 80 بسازید که بررسی certbot را ارائه می‌دهد:

sudo mkdir -p /var/www/certbot
sudo nano /etc/nginx/sites-available/xlada-subpage
# /etc/nginx/sites-available/xlada-subpage
# بلوک موقت برای صدور گواهی.
server {
listen 80;
listen [::]:80;
server_name sub.example.com;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
sudo ln -sf /etc/nginx/sites-available/xlada-subpage /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

sudo apt-get update && sudo apt-get install -y certbot
sudo certbot certonly --webroot -w /var/www/certbot -d sub.example.com

پیکربندی کامل nginx​

فایل /etc/nginx/sites-available/xlada-subpage را با پیکربندی کامل جایگزین کنید:

# /etc/nginx/sites-available/xlada-subpage
server {
listen 80;
listen [::]:80;
server_name sub.example.com;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}

server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name sub.example.com;

ssl_certificate /etc/letsencrypt/live/sub.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sub.example.com/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:XLADA_TLS:10m;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;

location / {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_read_timeout 300s;
}
}
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-run

بررسی مالکیت دامنه هنگام تمدید هم لازم است، بنابراین مسیر /var/www/certbot را از پیکربندی حذف نکنید. پروکسی‌های دیگر — در بخش پروکسی معکوس.

تنظیم در پنل​

  1. توکن را در REMNAWAVE_API_TOKEN در /opt/xlada-subpage/.env بنویسید و کانتینر را دوباره راه‌اندازی کنید:

    cd /opt/xlada-subpage && docker compose up -d
  2. آدرس عمومی اشتراک را در /opt/xlada/.env پنل مشخص کنید:

    SUB_PUBLIC_DOMAIN=sub.example.com

    مقدار — بدون https:// و بدون اسلش در انتها. اگر CUSTOM_SUB_PREFIX=sub تنظیم شده است، SUB_PUBLIC_DOMAIN=sub.example.com/sub را بنویسید.

  3. پنل را دوباره راه‌اندازی کنید تا فایل را بازخوانی کند:

    cd /opt/xlada
    docker compose up -d xpanel
  4. لینک را بررسی کنید: Users → یک کاربر را انتخاب کنید → Subscription link. آدرس باید با https://sub.example.com/ شروع شود: پنل آن را از SUB_PUBLIC_DOMAIN و شناسه کوتاه کاربر می‌سازد.

TRUST_PROXY و CUSTOM_SUB_PREFIX​

TRUST_PROXY=1 یعنی پیش از صفحه دقیقاً یک پروکسی معکوس وجود دارد. اگر هاپ‌ها بیشتر باشند، مقدار تغییر می‌کند: جدول مقادیر — در صفحه سرور جداگانه.

CUSTOM_SUB_PREFIX مسیر را تعیین می‌کند، اگر صفحه در ریشه دامنه ارائه نمی‌شود:

CUSTOM_SUB_PREFIX=sub
SUB_PUBLIC_DOMAIN=sub.example.com/sub

هر دو مقدار بدون اسلش نوشته می‌شوند و باید یکسان باشند. جزئیات بیشتر — در صفحه سرور جداگانه.

بررسی و خطاهای رایج​

# کانتینر راه‌اندازی شده و به پورت محلی گوش می‌دهد
docker compose ps
docker compose logs --tail 50 xpanel-subpage

# صفحه پاسخ می‌دهد: انتظار HTTP/2 200 داریم
curl -sI https://sub.example.com/AbCdEf1234567890 | head -n 5

پاسخ موفق شامل HTTP/2 200 و هدر profile-web-page-url است.

نشانهعلت و اقدام
پاسخ خالی، اتصال قطع می‌شودهدرهای X-Forwarded-For یا X-Forwarded-Proto: https از دست رفته‌اند؛ بلوک location در nginx را بررسی کنید
502 Bad Gatewayکانتینر راه‌اندازی نشده یا nginx درخواست را به 127.0.0.1:3010 نمی‌فرستد
401 یا 403مقدار REMNAWAVE_API_TOKEN نادرست یا باطل‌شده است؛ توکن جدید بسازید
صفحه با timeout از کار می‌افتدپنل از کانتینر در دسترس نیست: اشتباه تایپی در نام xpanel یا شبکه xpanel-network متصل نشده است
در لاگ‌ها آدرس کلاینت اشتباه دیده می‌شودمقدار TRUST_PROXY نادرست است؛ تعداد هاپ‌های پیش از صفحه را مشخص کنید
404 روی لینک اشتراکCUSTOM_SUB_PREFIX و SUB_PUBLIC_DOMAIN یکسان نیستند

لاگ‌های کانتینر و nginx:

docker compose logs -f xpanel-subpage
sudo tail -f /var/log/nginx/error.log

گام‌های بعدی​