صفحه اشتراک روی سرور جداگانه
پنل — xray-panel/backend · نود — xray-panel/node · صفحه اشتراک — xray-panel/subscription-page
صفحه اشتراک فهرست آراسته اتصالهای کاربر را به او ارائه میدهد: لینکهای
کلاینتها، کدهای QR، تاریخ انقضا و باقیمانده ترافیک. دادهها را با API-توکن از پنل
میگیرد. کلاینت آن را روی دامنه sub.example.com با یک کد کوتاه
در مسیر باز میکند.
چه زمانی به سرور جداگانه نیاز است
سرور جداگانه را زمانی برمیگزینند که صفحه اشتراک باید از
پنل جدا باشد: بار میان ماشینها تقسیم میشود و روی هاست پنل سرویس
اضافی ظاهر نمیشود. صفحه از طریق دامنه
https://panel.example.com به API پنل متصل میشود، بنابراین پنل باید از اینترنت و
از طریق HTTPS در دسترس باشد. اگر پنل و اشتراک روی یک هاست قرار دارند، از صفحه
صفحه اشتراک روی همان سرورِ پنل استفاده کنید.
چه چیزهایی لازم است
| مورد | مقدار |
|---|---|
| دامنه | sub.example.com |
| پنل | از بیرون از طریق https://panel.example.com در دسترس باشد |
| API-توکن | در پنل ساخته میشود |
| Docker و Docker Compose | روی سرور جدید |
| nginx و certbot | روی سرور جدید |
| پورت محلی | 3010 |
توکن در پنل ساخته میشود: Settings → API Tokens → Create token. مقدار آن فقط یکبار نشان داده میشود — آن را ذخیره کنید. این همان توکنی است که در نصب روی یک سرور با پنل استفاده میشود.
آمادهسازی سرور
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-v2 nginx certbot
sudo systemctl enable --now docker
docker compose version
اگر بسته docker-compose-v2 موجود نیست، Docker Engine را بر اساس راهنمای
رسمی نصب کنید و docker compose version را بررسی کنید.
فایل محیط
mkdir -p /opt/xlada-subpage && cd /opt/xlada-subpage
cat > .env <<'EOF'
APP_PORT=3010
# پنل از بیرون و از طریق HTTPS در دسترس است.
REMNAWAVE_PANEL_URL=https://panel.example.com
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
Docker Compose
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
driver: bridge
روی این سرور پنل وجود ندارد، بنابراین شبکه xpanel-network بهصورت محلی ساخته
میشود. صفحه به 127.0.0.1:3010 گوش میدهد و به بیرون نگاه نمیکند: دسترسی به آن فقط
از طریق nginx ممکن است. کانتینر را راهاندازی کنید:
docker compose up -d
docker compose logs -f xpanel-subpage
گواهی
پورت 80 روی سرور جدید آزاد است، بنابراین گواهی پیش از تنظیم nginx صادر میشود:
sudo certbot certonly --standalone -d sub.example.com
پیکربندی کامل nginx
# /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 ln -sf /etc/nginx/sites-available/xlada-subpage /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-run
TRUST_PROXY
TRUST_PROXY تنظیم trust proxy در Express است. آدرس واقعی کلاینت
در هدر X-Forwarded-For میآید که جعل آن آسان است، بنابراین برنامه باید
بداند به کدام هاپها اعتماد کند.
| مقدار | چه زمانی تنظیم شود |
|---|---|
1 | پیش از صفحه دقیقاً یک پروکسی است — حالت معمول |
2 | دو هاپ، مثلاً Cloudflare و سپس nginx |
true | اعتماد به همه؛ فقط برای اشکالزدایی در شبکه ایزوله |
false | اعتماد به هیچکس، IP واقعی تشخیص داده نمیشود |
127.0.0.1, 172.16.0.0/12 | اعتماد به آدرسها و زیرشبکههای فهرستشده |
هاپهای اضافی را فقط در صورتی مشخص کنید که واقعاً وجود دارند: اعتماد اضافی به کلاینت اجازه میدهد آدرس دیگری را در هدر جعل کند.
CUSTOM_SUB_PREFIX
بهطور پیشفرض صفحه در ریشه دامنه ارائه میشود. اگر مسیر لازم است،
CUSTOM_SUB_PREFIX را بدون اسلش در دو طرف تنظیم کنید و همان مسیر را در پنل هم
تکرار کنید:
CUSTOM_SUB_PREFIX=sub
SUB_PUBLIC_DOMAIN=sub.example.com/sub
هر دو مقدار باید یکسان باشند، وگرنه کلاینت لینکی به مسیر ناموجود دریافت میکند.
پروکسی کردن صفحه به یک location دلخواه مجاز نیست: مسیر دقیقاً با همین متغیر
تعیین میشود و nginx همچنان ریشه را به 127.0.0.1:3010 میفرستد.
تنظیم در پنل و بررسی
-
توکن را در
REMNAWAVE_API_TOKENدر/opt/xlada-subpage/.envبنویسید و کانتینر را دوباره راهاندازی کنید:cd /opt/xlada-subpage && docker compose up -d -
روی سرور پنل، آدرس عمومی اشتراک را در
/opt/xlada/.envمشخص کنید:SUB_PUBLIC_DOMAIN=sub.example.comمقدار — بدون
https://و بدون اسلش در انتها. اگرCUSTOM_SUB_PREFIX=subتنظیم شده است،SUB_PUBLIC_DOMAIN=sub.example.com/subرا بنویسید. -
پنل را دوباره راهاندازی کنید تا فایل را بازخوانی کند:
cd /opt/xladadocker compose up -d xpanel
# کانتینر راهاندازی شده و به پورت محلی گوش میدهد
docker compose ps
docker compose logs --tail 50 xpanel-subpage
# صفحه پاسخ میدهد: انتظار HTTP/2 200 داریم
curl -sI https://sub.example.com/AbCdEf1234567890 | head -n 5
# پنل باید از بیرون پاسخ دهد
curl -sI https://panel.example.com | head -n 1
پاسخ موفق شامل HTTP/2 200 و هدر profile-web-page-url است. در پنل
لینک را اینگونه بررسی میکنند: Users → یک کاربر را انتخاب کنید → Subscription link —
آدرس باید با https://sub.example.com/ شروع شود.
تا زمانی که در SUB_PUBLIC_DOMAIN مقداری مانند panel.example.com/api/sub
نوشته شده باشد، کلاینتها صفحه داخلی پنل را دریافت میکنند، نه صفحهای که شما
نصب کردهاید.