跳到主要内容

订阅页面与面板部署在同一台服务器

源代码

面板 — xray-panel/backend · 节点 — xray-panel/node · 订阅页面 — xray-panel/subscription-page

订阅页面向用户展示其连接的美化列表:客户端链接、二维码、有效期和剩余流量。 它通过 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=在此粘贴面板令牌

# 页面不在根路径时使用的路径前缀。两端不要带斜杠。
CUSTOM_SUB_PREFIX=

# 确定客户端 IP 时信任多少层反向代理。
TRUST_PROXY=1

# 用于外部鉴权的 X-Api-Key:带 security 插件的 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 已由面板创建,因此这里以外部网络方式接入。启动容器:

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

页面监听 127.0.0.1:3010,不对外暴露:只有 nginx 能访问它。

证书​

先创建一个临时的 80 端口 server 块,用于响应 certbot 校验:

sudo mkdir -p /var/www/certbot
sudo nano /etc/nginx/sites-available/xlada-subpage
# /etc/nginx/sites-available/xlada-subpage
# 用于签发证书的临时 server 块。
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. 将令牌填入 /opt/xlada-subpage/.env 的 REMNAWAVE_API_TOKEN,然后 重启容器:

    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;检查 nginx 中的 location 块
502 Bad Gateway容器未启动,或 nginx 没有把请求转发到 127.0.0.1:3010
401 或 403REMNAWAVE_API_TOKEN 错误或已被撤销;请创建新令牌
页面超时失败容器内无法访问面板:xpanel 名称拼写错误,或未接入 xpanel-network 网络
日志中客户端地址不正确TRUST_PROXY 设置错误;请填写页面前的代理层数
订阅链接返回 404CUSTOM_SUB_PREFIX 与 SUB_PUBLIC_DOMAIN 不一致

容器和 nginx 日志:

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

接下来​