反向代理
XLADA 面板没有带 TLS 的反向代理就无法工作。其代码中有
middleware,如果请求到达时没有 X-Forwarded-For 和
X-Forwarded-Proto: https 请求头,就会断开连接。这不是任性:
面板必须确信对外提供它的是可信节点,否则就无法确定客户端真实
IP。
选择 Web 服务器 —— 每一节都是完整可用的配置,而不是最小的 示例:
| 方案 | 何时选择 |
|---|---|
| nginx | 经典之选,可最大限度控制每个请求头和超时 |
| Caddy | 配置最少,证书自动签发 |
| Angie | nginx 的分支,自带现成模块;语法熟悉 |
| Traefik | 你已有 Docker 栈,希望用 labels 描述路由 |
| Cloudflare Tunnel | 服务器没有公网 IP,或需要隐藏 origin |
所有方案解决同一个任务:从外部接收 HTTPS,添加请求头,并把请求
转发到 127.0.0.1:3000(面板)或 127.0.0.1:3010(订阅
页面)。面板、数据库和 Valkey 只监听 localhost —— 不对外
暴露。
无论如何都要配置的内容
下面列出的内容在本节各页面的每份完整配置中都有。 这里说明的是:如果你要按自己的情况修改配置,具体该检查什么。
1. 代理请求头
面板和订阅页面从 X-Forwarded-For 读取客户端地址,从
X-Forwarded-Proto 读取 HTTPS 标志。两个请求头都是必需的:没有它们
middleware 会断开连接。
即使 TLS 在上层负载均衡器上终止,也不要在这里手动
填入 http:面板会拒绝工作。
2. WebSocket
面板会打开到节点的 SSH 终端 —— 这是 WebSocket。请求头
Connection 只有在客户端请求协议升级时才会被填入,
因此在 nginx 和 Angie 中通过 map $http_upgrade $connection_upgrade 来计算。如果不转发 Upgrade 和 Connection
请求头,终端会静默地连接不上。
3. 客户端真实 IP
在 XLADA 中,有三件事取决于正确的 IP:
- 设备限制(HWID)—— 按订阅请求计算;
- 管理员操作日志 —— 记录 IP;
- 登录防暴力破解 —— 按“用户名 + IP”组合计算封禁。
如果 Web 服务器前面还有 CDN 或第二层代理,请配置对其 请求头的信任。面向 nginx 的完整 Cloudflare 网段信任文件如下:
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
在 Caddy 中由 trusted_proxies static 负责,在 Traefik 中是
forwardedHeaders.trustedIPs,在 Cloudflare Tunnel 中则是带本地
代理的专有方案。否则所有客户端都会看起来像同一个 IP,各项限制
就不会按你预期的方式生效。
4. 超时、流式传输与缓存
- 长连接。 SSH 终端和 API 的流式响应会持续数分钟:
将
proxy_read_timeout设为不少于 300 秒,终端则设为一天。 - 关闭缓冲。 对流式端点适合使用
proxy_buffering off,否则响应会堆积在缓冲区里、断断续续地到达。 - 请求体大小。 导入配置和模板有时会很大 ——
client_max_body_size 32m可消除意外的 413。 - 静态资源缓存。 界面构建产物位于
/assets/,文件名中包含 哈希,因此以Cache-Control: public, immutable请求头和 较长的有效期提供。
nginx 完整配置
下面是面板的完整可用配置:证书、TLS、请求头、WebSocket、 超时、gzip 和静态资源缓存。单独的 nginx 页面为它补充订阅页面的主机、按 IP 限制访问以及以 Docker 运行。
upstream xlada_panel {
server 127.0.0.1:3000;
keepalive 32;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# HTTP:验证域名所有权并重定向到 HTTPS
server {
listen 80;
listen [::]:80;
server_name panel.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/panel.example.com/chain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:XLADA_TLS:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 valid=60s;
resolver_timeout 2s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
client_max_body_size 32m;
client_body_timeout 60s;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/wasm
application/xhtml+xml
application/xml
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
# 界面静态资源:文件名中的哈希允许长期缓存
location ^~ /assets/ {
proxy_pass http://xlada_panel;
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_set_header Connection "";
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# API 和流式响应:不缓冲
location /api/ {
proxy_pass http://xlada_panel;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
}
# 面板界面
location / {
proxy_pass http://xlada_panel;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;
proxy_buffering on;
}
}
按 IP 和未知域名的请求由单独的兜底块拦截:
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}
启用该主机并重新加载 nginx:
sudo ln -sf /etc/nginx/sites-available/xlada-panel /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
该配置的证书由 certbot 签发 —— 完整流程 见 nginx 页面。
精简版本
如果只需要用于测试的最小配置,同样完整但更短的 配置也适用:相同的重定向、TLS、请求头和 WebSocket,不含 gzip、静态资源缓存和 单独的 API 块。
server {
listen 80;
listen [::]:80;
server_name panel.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 panel.example.com;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.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;
client_max_body_size 32m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 86400;
}
}
检查
nginx -t && systemctl reload nginx
# 面板有响应并能看到 HTTPS
curl -sI https://panel.example.com | head -3
curl -s https://panel.example.com/api/system/health
# 日志中没有 “Reverse proxy and HTTPS are required”
tail -n 50 /var/log/nginx/error.log
检查清单
- HTTP 重定向到 HTTPS,证书自动续期。
-
X-Forwarded-For和X-Forwarded-Proto: https能到达面板。 - WebSocket 正常工作:到节点的 SSH 终端可以打开。
- 在管理员操作日志中能看到客户端真实 IP。
- 面板和订阅页面由不同域名提供(如果启用了订阅 页面)。
- 端口 3000、3001 和 3010 不对外开放 —— 只能从 localhost 访问。