跳到主要内容

nginx

概览​

nginx 安装在 XLADA 面板之前,解决三个任务:在 80 和 443 端口接收流量、终止 TLS,并把面板请求转发到 127.0.0.1:3000, 同时填入请求头,面板据此判断连接是受保护的、以及谁是 真正的客户端。

代理是必需的,而不是可选的:面板和订阅页面的代码中 有 middleware,如果请求到达时没有 X-Forwarded-For 和 X-Forwarded-Proto: https 请求头,它会断开连接。因此在 安装 nginx 之前,面板应当已经搭建好 —— 参见 安装面板,第二个主机参见 订阅页面。

注意

你需要一个已注册的域名,并能访问它的 DNS 记录。没有 域名就无法签发证书,而没有 HTTPS 面板就无法工作。

DNS:把域名指向服务器​

查明服务器的外部地址。最好是固定的 IPv4 地址。然后 在注册商或 DNS 服务商处创建记录:

类型名称值用途
Apanel服务器的 IPv4 地址面板和 API
AAAApanel服务器的 IPv6 地址有 IPv6 时同上,通过 IPv6
Asub服务器的 IPv4 地址订阅页面

如果面板要位于域名本身,请在名称字段填 @。只有在服务器 确实配置了 IPv6 时才创建 AAAA 记录: 否则客户端会连到不存在的地方并得到连接错误。

记录更新需要几分钟到几小时。可以用下面的命令检查 结果:

dig +short panel.example.com

灰云与橙云​

在 Cloudflare 管理面板中,记录有一个代理开关:

  • 灰云 —— 记录直接把你的服务器地址返回给客户端; 证书在服务器上签发和续期,IP 限制按通常方式 工作;
  • 橙云 —— 流量经过 Cloudflare,客户端看到的是 Cloudflare 地址而不是你的地址。这会隐藏服务器并削平流量峰值,但需要 单独配置客户端真实 IP(见下文),并且在没有附加条件时 无法通过 80 端口验证来签发证书。

对于面板,通常选择橙云,并且必须配置对 CF-Connecting-IP 请求头的信任。详情见 Cloudflare 一节。

证书​

certbot —— 主要方式​

certbot 签发 Let's Encrypt 证书,并通过自己的定时器续期。 首先创建一个完整的 HTTP 主机:它提供域名所有权验证, 并把其余请求重定向到 HTTPS。

sudo mkdir -p /var/www/certbot
sudo mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
sudo nano /etc/nginx/sites-available/xlada-bootstrap
/etc/nginx/sites-available/xlada-bootstrap
# 用于为面板和订阅页面签发证书的临时主机。
server {
listen 80;
listen [::]:80;
server_name panel.example.com sub.example.com;

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

location / {
return 301 https://$host$request_uri;
}
}
sudo ln -sf /etc/nginx/sites-available/xlada-bootstrap /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d panel.example.com
sudo certbot --nginx -d sub.example.com

Certbot 会为每个域名签发单独的证书:它们会放在 /etc/letsencrypt/live/panel.example.com/ 和 /etc/letsencrypt/live/sub.example.com/。签发后请删除临时主机并 启用下文各节中的完整配置:其中证书路径 已经写好。

sudo rm -f /etc/nginx/sites-enabled/xlada-bootstrap

certbot 会自行续期。这样检查:

sudo certbot renew --dry-run
systemctl list-timers | grep certbot

acme.sh —— 替代方案​

如果 certbot 不可用,或者你更喜欢用一个工具管理多台 服务器,请使用通过 ALPN 验证域名所有权的 acme.sh。这种方式 不依赖 Web 服务器,但需要一个空闲的 8443 端口。

sudo apt-get install -y cron socat
curl https://get.acme.sh | sh -s email=admin@example.com
source ~/.bashrc

sudo mkdir -p /opt/xlada/nginx && cd /opt/xlada/nginx

# 一次为两个域名签发一个证书:签发并固定路径
acme.sh --issue --standalone -d 'panel.example.com' -d 'sub.example.com' \
--key-file /opt/xlada/nginx/privkey.key \
--fullchain-file /opt/xlada/nginx/fullchain.pem \
--alpn --tlsport 8443 \
--reloadcmd "systemctl reload nginx"

# 为自动续期固定路径和重载命令
acme.sh --install-cert -d 'panel.example.com' \
--key-file /opt/xlada/nginx/privkey.key \
--fullchain-file /opt/xlada/nginx/fullchain.pem \
--reloadcmd "systemctl reload nginx"

# 把文件名调整为下文完整配置中指定的名称
sudo mkdir -p /etc/letsencrypt/live/panel.example.com
sudo mkdir -p /etc/letsencrypt/live/sub.example.com
sudo ln -sf /opt/xlada/nginx/fullchain.pem /etc/letsencrypt/live/panel.example.com/fullchain.pem
sudo ln -sf /opt/xlada/nginx/privkey.key /etc/letsencrypt/live/panel.example.com/privkey.pem
sudo ln -sf /opt/xlada/nginx/fullchain.pem /etc/letsencrypt/live/panel.example.com/chain.pem
sudo ln -sf /opt/xlada/nginx/fullchain.pem /etc/letsencrypt/live/sub.example.com/fullchain.pem
sudo ln -sf /opt/xlada/nginx/privkey.key /etc/letsencrypt/live/sub.example.com/privkey.pem
sudo ln -sf /opt/xlada/nginx/fullchain.pem /etc/letsencrypt/live/sub.example.com/chain.pem

8443 端口必须开放,且不能被其他进程占用。acme.sh 大约每 60 天 通过 cron 自行续期证书 —— 请确认 cron 服务 已启动。

说明

如果 nginx 在容器中运行(见下文),请在 --reloadcmd 中不要重载 服务,而改为在容器内重载: docker exec xlada-nginx nginx -s reload。

注意

请注意证书颁发机构的限制:通过 acme.sh 默认使用的 ZeroSSL 签发时,不支持 .ru、.su 和 .рф 域名区域。对于这类 域名,请选择 certbot 或其他证书颁发机构。

面板的 nginx 配置​

创建虚拟主机文件并启用它:

sudo nano /etc/nginx/sites-available/xlada-panel
sudo ln -sf /etc/nginx/sites-available/xlada-panel /etc/nginx/sites-enabled/
/etc/nginx/sites-available/xlada-panel
# XLADA 面板虚拟主机。

upstream xlada_panel {
server 127.0.0.1:3000;
keepalive 32;
}

# WebSocket 需要 upgrade 值,普通请求则需要 close。
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;

# Let's Encrypt 证书
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;

# 协议与加密套件:Mozilla Intermediate 建议
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;

# 安全请求头。只有在确认 HTTPS 正常工作后才启用 HSTS:
# 浏览器会长期记住这个要求。
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 25m;
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;

# ─── 界面静态资源:Vite 构建产物位于 /assets/ ─────────────────────
# 文件名包含哈希,因此可以长期缓存。
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;

# WebSocket:到节点的 SSH 终端和界面的实时更新
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 nginx -t && sudo systemctl reload nginx

按 IP 限制访问​

基础配置中没有按 IP 的访问限制:面板由自身的 认证保护,而订阅页面应对所有客户端开放。如果 面板只对你开放,请使用同一主机的完整配置并加上地址 过滤器:它如下所示,写入同一个文件 /etc/nginx/sites-available/xlada-panel。

/etc/nginx/sites-available/xlada-panel
# 带按 IP 访问限制的 XLADA 面板虚拟主机。

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;

# 只放行可信地址,其余返回 403
allow 203.0.113.0/24;
allow 198.51.100.7;
deny all;

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 25m;
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;
}

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;
}
}

这样的过滤器只应加在面板主机上。在订阅页面主机上它会 破坏向用户发放链接,而在 API 上会破坏 webhook 和外部集成。 把 basic auth 作为第二层并不方便:它会破坏 API 令牌和内部 请求的工作,因此请改用管理员密码和 TOTP。

容器中的 nginx​

如果你的 nginx 也在 Docker 中,请为它启用 host 网络模式:这样 容器看到的 127.0.0.1:3000 和 127.0.0.1:3010 与宿主机相同,本页的完整 配置无需任何修改即可使用。

/opt/xlada/nginx/docker-compose.yml
services:
xlada-nginx:
image: nginx:1.30
container_name: xlada-nginx
restart: unless-stopped
# host 模式:上游 127.0.0.1:3000 和 127.0.0.1:3010 与宿主机上一样可访问
network_mode: host
volumes:
- /etc/nginx/sites-available/xlada-panel:/etc/nginx/conf.d/xlada-panel.conf:ro
- /etc/nginx/sites-available/xlada-subpage:/etc/nginx/conf.d/xlada-subpage.conf:ro
- /etc/nginx/conf.d/00-default.conf:/etc/nginx/conf.d/00-default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- /var/www/certbot:/var/www/certbot:ro
cd /opt/xlada/nginx
docker compose up -d
docker exec xlada-nginx nginx -t
docker exec xlada-nginx nginx -s reload

更新配置只需修改 /etc/nginx/sites-available/ 中的文件 并在容器内重载:

docker exec xlada-nginx nginx -t && docker exec xlada-nginx nginx -s reload

订阅页面​

订阅页面在 xpanel-subpage 容器中的 127.0.0.1:3010 上运行, 并位于单独的二级域名。要求相同:HTTPS 以及 X-Forwarded-For 和 X-Forwarded-Proto: https 请求头,否则容器会断开 连接。这里不需要 WebSocket,超时也可以设得更短。

/etc/nginx/sites-available/xlada-subpage
# XLADA 订阅页面虚拟主机。

upstream xlada_subpage {
server 127.0.0.1:3010;
keepalive 16;
}

# 订阅页面不需要 WebSocket,但保留了请求头:
# 这样即使应用以后开始使用它们,配置也仍然可用。
map $http_upgrade $subpage_connection_upgrade {
default upgrade;
'' close;
}

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_trusted_certificate /etc/letsencrypt/live/sub.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 8.8.8.8 valid=60s;
resolver_timeout 2s;

add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

client_max_body_size 10m;

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
application/javascript
application/json
application/manifest+json
application/xml
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;

# 订阅页面的构建产物静态资源
location ^~ /assets/ {
proxy_pass http://xlada_subpage;
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;
}

location / {
proxy_pass http://xlada_subpage;
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;

# WebSocket:目前未使用,但配置已为它准备好
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $subpage_connection_upgrade;

proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;

proxy_buffering on;
}
}

启用该主机并重载 nginx:

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

如果面板中设置了 CUSTOM_SUB_PREFIX,就需要把同一路径加入 SUB_PUBLIC_DOMAIN —— 详情见 订阅页面。而在该页面自身 的环境文件中请保留 TRUST_PROXY=1,让它信任一层代理跳数 并从请求头获取客户端 IP。

客户端真实 IP​

面板必须看到客户端的真实地址,因为以下功能都依赖它:

  • 按 HWID 统计设备 —— 设备绑定和访问历史会保存 请求地址(设备限制);
  • 管理员操作日志:通过地址可以看出是谁、从哪里登录的 (面板安全);
  • 登录防暴力破解:登录尝试按地址计数,如果所有 请求都来自同一个代理地址,限制要么对所有人 同时触发,要么完全不触发。

如果 nginx 前面有 Cloudflare,普通的 X-Forwarded-For 就没有用了: Cloudflare 在 CF-Connecting-IP 请求头中传递客户端地址。只能信任来自 Cloudflare 子网的该请求头 —— 否则任何想这么做的人都可以在请求中 填入他人的地址。

/etc/nginx/conf.d/real-ip.conf
# 从 Cloudflare 请求头获取客户端地址。
# 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;

如果 nginx 前面是你自己的负载均衡器或第二层代理, 请信任它的子网和普通的请求头:

/etc/nginx/conf.d/real-ip.conf
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
危险

绝不要指定 set_real_ip_from 0.0.0.0/0,也不要信任来自整个 互联网的 X-Forwarded-For。并且不要手动把 X-Forwarded-Proto 替换为 http:面板会拒绝工作。

检查​

# 1. 语法与重载
sudo nginx -t && sudo systemctl reload nginx

# 2. 从 HTTP 到 HTTPS 的重定向 —— 预期 301
curl -sI http://panel.example.com | head -1

# 3. HTTPS:响应码和 HSTS 请求头
curl -sI https://panel.example.com | grep -Ei 'HTTP/|strict-transport'

# 4. 证书有效期
echo | openssl s_client -connect panel.example.com:443 \
-servername panel.example.com 2>/dev/null | openssl x509 -noout -dates

# 5. 订阅页面 —— 预期 200 和 profile-web-page-url 请求头
curl -sI https://sub.example.com/AbCdEf1234567890

如果某些功能不工作,该看什么:

sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/nginx/access.log
sudo journalctl -u nginx -n 50 --no-pager
docker compose logs --tail 50 xpanel

典型迹象:502 Bad Gateway —— 面板容器未启动或监听了 错误的端口;TLS 握手错误 —— 证书路径错误或已过期; 请求后连接立即中断 —— X-Forwarded-For 或 X-Forwarded-Proto 请求头丢失。在访问日志中,客户端地址应 与其真实地址一致,而不是 Cloudflare 的地址。

接下来​