跳到主要内容

反向代理

XLADA 面板没有带 TLS 的反向代理就无法工作。其代码中有 middleware,如果请求到达时没有 X-Forwarded-For 和 X-Forwarded-Proto: https 请求头,就会断开连接。这不是任性: 面板必须确信对外提供它的是可信节点,否则就无法确定客户端真实 IP。

选择 Web 服务器 —— 每一节都是完整可用的配置,而不是最小的 示例:

方案何时选择
nginx经典之选,可最大限度控制每个请求头和超时
Caddy配置最少,证书自动签发
Angienginx 的分支,自带现成模块;语法熟悉
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 会断开连接。

不要覆盖 X-Forwarded-Proto

即使 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 网段信任文件如下:

/etc/nginx/conf.d/real-ip.conf
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 运行。

/etc/nginx/sites-available/xlada-panel
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 和未知域名的请求由单独的兜底块拦截:

/etc/nginx/conf.d/00-default.conf
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 块。

/etc/nginx/sites-available/xlada-panel
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 访问。

接下来​

  • nginx —— 带 TLS、gzip、缓存和 security headers 的完整配置。
  • Caddy —— “开箱即用”的证书。
  • Traefik —— 用 labels 描述路由并按 IP 限制。
  • 面板安全 —— 已完成的工作以及需要记住的事项。