11. 稳如磐石:Xray REALITY + Vision + Nginx 本机真证书伪装站终极闭环
版本日期:2026-09-09
适用场景:海外 VPS(如腾讯云东京 / AWS Lightsail / Oracle Cloud)、Ubuntu 24.04 LTS、自用域名、私人合规网络入口与 AI 接口风控破除。
合规说明:本文面向合法合规的自有设备远程访问、隐私保护和私人网络运维场景;请遵守所在地法律法规、云厂商 AUP/ToS 以及第三方服务条款。本文不提供规避审查、绕过平台地区/代理检测的专门方案。专栏联动:本篇部署的海外前哨服务器同时也是大模型 API 聚合网关的承载宿主,详细配置见 02. 专属大脑:CLIProxyAPI + WARP 部署全量实录。两者共用同一个 Cloudflare WARP 实例(SOCKS5
127.0.0.1:40000出站)。
0. 终极架构与设计哲学
在搭建私人海外网络中继时,很多技术人员最大的痛点在于:“如何让节点既具备极致的传输性能,又能彻底抵御主动探测?”
传统 REALITY 方案通常“借用(偷用)”微软或苹果等大厂域名作为 SNI。然而在严苛的探测环境下,借用 SNI 存在证书指纹不匹配、CDN 证书回落异常等暴露风险。
本文采用更高级且优雅的**「自持域名 + Let's Encrypt 官方真证书 + 本机 Nginx 8443 Decoy 站点」**闭环架构:
核心流量流向解析:
- 授权客户端(Streisand / v2rayN):
Client -> jp.example.com:443 -> Xray REALITY (X25519 鉴权通过) -> XTLS-Vision -> 路由引擎- 常规流量 ➔ Freedom Direct 直连出口(原生海外 BGP 网络);
- AI / 流媒体流量(OpenAI、Claude、Gemini、Netflix) ➔ WARP SOCKS5 (
127.0.0.1:40000) 出站(破除机房原生 IP 风控)。
- 未授权访客 / 网络扫描器 / GFW 主动探测:
Visitor / Prober -> jp.example.com:443 -> Xray REALITY 验证失败 -> 回落至 127.0.0.1:8443 (带 PROXY 协议头) -> Nginx 响应真实 HTTPS 网页- 探测方看到的是一个持有 Let's Encrypt 合法有效证书、域名完全匹配、支持 HTTP/2 的完整合规站点(Network Lab);
- Nginx 通过
proxy_protocol完整还原探测者的真实公网 IP,便于 Fail2ban 与安全审计。
0.1 基础设施准备:低至 1 美元打造“无限专属域名资产”
很多开发者在听到“自持域名 + 申请真实证书”时,第一反应往往是“域名每年几十上百块太烧钱”。
其实有一个极客圈内公认的高性价比域名获取秘籍:
- 选购平台:推荐前往 Spaceship.com(知名海外域名注册商 Namecheap 旗下的新一代低价平台);
- 选购规则:选择 6~9 位纯数字 +
.xyz后缀(例如839201.xyz、910283.xyz); - 惊人成本:首年通常仅需 1.00 ~ 1.50 美元(约合人民币 7~10 元,不到一杯蜜雪冰城!),次年续费价格同样是 1~2 美元;
⚠️ 避坑提醒:警惕“首年补贴低价、次年天价续费”的商业套路!
在 Spaceship 或各大域名商搜索时一定要擦亮眼睛:
- 看清续费价格(Renews at):很多域名(例如包含字母的常用单词
.xyz、.top或促销后缀)故意在首页打出 $0.69 / $0.99 的超低首年吸睛价,但结账列表下方的小字会标注 “Renews at $15 ~ $30 / year”!这种就属于“第一年补贴引流,后续年年割韭菜”; - 认准“平价续费”池:我们搭建的节点、网关和穿透域名是长久稳定运行的基础设施,中途频繁换域名会导致全套证书、客户端配置和 API 链路全部作废。请务必在加入购物车时仔细核对:“Renews at” 必须同样是 $1.00 ~ $2.00 左右。目前只有 6~9 位纯数字
.xyz是注册局长期承诺平价续费的“常青池”,务必认准再下单!
- “买一得无限”的子域名放大法: 买下一个根域名后,将其 Nameserver 解析托管至 Cloudflare 或 Spaceship 自带控制台。你可以在这一个根域名下免费创建无数个多级子域名:
jp.your-domain.xyz➔ 东京 Xray REALITY 主节点入口cpa.your-domain.xyz➔ CLIProxyAPI 大模型聚合网关code.your-domain.xyz➔ FRP 穿透家庭开发机 OpenCode PWAssh.your-domain.xyz➔ FRP 极速免密 SSH 穿透入口
只需花费 1 美元,就能为你整套私人混合云与网络基础设施提供取之不尽的生产级专属域名资产!
0.2 为什么坚持裸装 Xray-core?坚决拒绝带 UI 面板的深层安全考量
市面上流行很多带 Web 界面的开源一键部署面板(如各类 3x-ui、x-ui、vaxilu 等)。虽然“点点鼠标可视化添加用户”看似友好,但在长期的生产与个人私人基础设施中,带 UI 面板存在严重的架构缺陷与安全隐患:
- 公网暴露面过大与未授权泄露隐患:
Web 面板必须在公网额外开放一个高位 HTTP/HTTPS 端口(如 54321)。历史上这类开源面板曾多次被曝出 未授权访问漏洞、Cookie 越权、SQL/命令注入以及弱口令爆破。一旦面板失守,攻击者不仅能轻易获取你的所有 UUID、私钥凭据,甚至可以直接拿到云主机的 root 执行权限,导致节点瞬间沦陷或沦为肉鸡。 - 沉重的资源冗余与内存泄漏:
带 UI 的面板往往强捆绑了 SQLite、Python/Node.js/Go Web 框架、前端静态资源以及高频定时轮询任务。对于只有 512MB ~ 1GB 内存的廉价海外轻量云而言,内存占用动辄上百兆,极易诱发 Linux OOM Killer(内存耗尽强杀进程),造成服务突然断连。 - “裸 Xray-core + 纯净脚本”的终极优势:
- 攻击面收敛至物理极限:公网除必要 SSH 外,仅开放 443 端口(且前端严格挂载 REALITY 伪装与 Nginx 真实回落),在公网完全“隐形”,没有任何管理端口暴露在扫描器雷达下;
- 极致轻量:Xray 官方二进制原生核心仅占用 15MB ~ 30MB 内存,CPU 消耗微乎其微;
- 工业级稳定:由 Linux 原生 Systemd 守护运行,配合我们封装的轻量脚手架(无 Web 服务,仅在需要时 CLI 执行),真正做到 7×24 小时数月不重启、永不崩溃、零外部依赖。
0.3 破除大模型机房 IP 风控:为什么必须叠加 WARP?
如果你尝试过在腾讯云、AWS Lightsail、Oracle Cloud 等商业云服务器上直接调用 Google Gemini API 或 OpenAI,大概率遇到过此类报错:
User location is not supported.
# 或直接返回 HTTP 403 Forbidden为什么服务器明明位于东京或美西,却依然被拒之门外?
因为主流 LLM 厂商(Google、OpenAI、Anthropic)均接入了顶级的风控情报数据库(如 MaxMind GeoIP、IP2Location)。它们不仅检测 IP 的物理国家,还会严格审查其 ASN(自治系统属性):
- 云厂商的 IP 会被明确标记为
Data Center / Hosting(机房 IDC 段)。针对这类商业机房 IP,LLM 厂商会执行极严苛的风控策略,直接拉黑或拒发 Token; - 个人家庭宽带或移动网络则被标记为
ISP / Residential(住宅消费者网络),受信度极高。
通过将 Xray 与 CLIProxyAPI 的出站流量导入本机运行的 Cloudflare WARP(Proxy 模式 127.0.0.1:40000),出站流量会被接入 Cloudflare 全球边缘 Anycast 网络(AS13335):
- 在大模型厂商的风控库中,Cloudflare Anycast IP 的信誉等级被归类为“高信誉分布式 CDN / 边缘网络”,从而彻底规避机房 IDC ASN 黑名单,实现 100% 成功率的稳定高并发调用!
一、双轨路线选择(按需取用)
为了满足不同开发者的操作习惯,本文提供两种实施路径:
- 路线 A(效率推荐):生产级一键运维脚本(
xray-reality-manager.sh)
适合追求效率、希望 5 分钟全自动闭环的开发者。脚本全自动处理环境安装、UFW 配置、真证书签发、静态站点生成、Xray 秘钥对生成、多用户增删与二维码导出。 - 路线 B(硬核深度):底层逐行手工实操剖析
适合希望吃透每一行config.json、Nginxproxy_protocol联动原理与 WARP 路由分流细节的开发者。
二、路线 A 实战:xray-reality-manager 自动化脚手架
为了免去繁琐的手工配置与维护负担,我们编写了模块化的自动化管理脚本 xray-reality-manager.sh。
2.1 核心特性与安全机制
- 全自动化闭环:一键完成系统包依赖、UFW 防火墙放行、Let's Encrypt 证书签发、静态站点渲染与 Xray 配置;
- 防手工覆盖警告:脚本写入前会自动检测 Nginx 站点文件中是否存在手工添加的
proxy_pass(如 CLIProxyAPI 的反代规则),并在覆盖前提示确认,避免冲掉业务配置; - 多客户端生命周期管理:支持交互式添加设备名(如
iphone、macbook),自动生成独立 UUID 并同步生成vless://链接与终端 ANSI 二维码; - 真实 IP 透传与状态审计:默认开启
REALITY_XVER=1,与 Nginxproxy_protocol严密咬合,健康检查自带真实公网 IP 审计; - 内置完整自适应站点:自动在
/var/www/jp生成带响应式 CSS 的极简Network Lab静态站(包含首页、服务状态、运维笔记与隐私说明)。
2.2 上传脚本与一键初始化部署
将脚本上传至 VPS 的 /root/ 目录并赋予执行权限:
chmod +x /root/xray-reality-manager.sh
sudo bash /root/xray-reality-manager.sh install在交互式安装向导中,按提示输入(直接回车即使用方括号中的推荐默认值):
请输入安装所需信息。留空则使用方括号中的默认值。
域名,例如 jp.example.com []: jp.your-domain.com
Let's Encrypt 邮箱,留空则无邮箱注册 []: admin@your-domain.com
SSH 端口 [22]: 22
网站根目录 [/var/www/jp]: /var/www/jp
ACME webroot 目录 [/var/www/certbot]: /var/www/certbot
Xray 公网监听端口 [443]: 443
Nginx 本机 HTTPS decoy 端口 [8443]: 8443
REALITY xver(1=开启 PROXY Protocol 透传真实客户端 IP) [1]: 1
是否配置 WARP 出站(AI/流媒体走本机 WARP SOCKS5)[yes/no] [yes]: yes
WARP SOCKS5 端口 [40000]: 40000
Nginx client_max_body_size(多模态上传需要) [64M]: 64M确认继续后,脚本将在约 1~2 分钟内全自动部署完成,并输出部署摘要文件:
/root/.xray-reality-manager/output/summary.txt(包含生成的密钥对与参数)/root/.xray-reality-manager/output/client-links.txt(默认客户端链接)
2.3 常用运维命令速查
你随时可以通过 sudo bash /root/xray-reality-manager.sh menu 呼出交互菜单,或直接使用 CLI 子命令:
# 查看服务状态与端口监听
sudo bash /root/xray-reality-manager.sh status
# 全面健康检查(DNS、证书、透传 IP、WARP 出口探测)
sudo bash /root/xray-reality-manager.sh health
# 添加新设备(例如为 iPhone 分配独立 UUID)
sudo bash /root/xray-reality-manager.sh add-client
# 终端输出扫码二维码(或指定设备名称)
sudo bash /root/xray-reality-manager.sh qr
# 启用 Linux 内核 BBR 拥塞控制加速
sudo bash /root/xray-reality-manager.sh bbr
# 备份所有配置文件与密钥状态
sudo bash /root/xray-reality-manager.sh backup点击展开查看:xray-reality-manager.sh 完整脚本源码
#!/usr/bin/env bash
set -Eeuo pipefail
# Xray Reality Vision Manager
# Scenario: Ubuntu 24.04 VPS + domain + Xray VLESS REALITY Vision RAW + local Nginx HTTPS decoy.
# Use for lawful personal/private infrastructure only.
SCRIPT_VERSION="2026-09-09.1"
CONFIG_DIR="/root/.xray-reality-manager"
STATE_FILE="$CONFIG_DIR/state.env"
CLIENTS_FILE="$CONFIG_DIR/clients.tsv"
OUTPUT_DIR="$CONFIG_DIR/output"
WEB_ROOT_DEFAULT="/var/www/jp"
CERTBOT_WEBROOT_DEFAULT="/var/www/certbot"
XRAY_CONFIG="/usr/local/etc/xray/config.json"
NGINX_SITE_AVAILABLE_DIR="/etc/nginx/sites-available"
NGINX_SITE_ENABLED_DIR="/etc/nginx/sites-enabled"
LOG_FILE="$CONFIG_DIR/manager.log"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERR]${NC} $*" >&2; }
pause() {
echo
read -r -p "按 Enter 继续..." _ || true
}
require_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
err "请使用 root 执行:sudo bash $0"
exit 1
fi
}
ensure_dirs() {
mkdir -p "$CONFIG_DIR" "$OUTPUT_DIR"
chmod 700 "$CONFIG_DIR" "$OUTPUT_DIR"
touch "$LOG_FILE"
chmod 600 "$LOG_FILE"
}
log_cmd() {
echo "[$(date '+%F %T')] $*" >> "$LOG_FILE"
}
diagnose_nginx_failure() {
warn "Nginx 启动失败,输出快速诊断信息:"
echo
echo "---- nginx -t ----"
nginx -t || true
echo
echo "---- listening ports (:80/:443/:8443) ----"
ss -lntp | grep -E ':80|:443|:8443' || true
echo
echo "---- systemctl status nginx ----"
systemctl status nginx --no-pager || true
echo
echo "---- journalctl -u nginx ----"
journalctl -u nginx -n 80 --no-pager || true
echo
warn "常见原因:80 端口被占用,或 VPS 禁用 IPv6 但 Nginx 配置监听了 [::]:80。"
}
restart_nginx_or_diagnose() {
nginx -t
systemctl enable nginx
if ! systemctl restart nginx; then
diagnose_nginx_failure
return 1
fi
}
backup_file() {
local f="$1"
if [[ -e "$f" ]]; then
local b="${f}.bak.$(date +%F-%H%M%S)"
cp -a "$f" "$b"
info "已备份:$b"
fi
}
load_state() {
if [[ -f "$STATE_FILE" ]]; then
source "$STATE_FILE"
fi
DOMAIN="${DOMAIN:-}"
EMAIL="${EMAIL:-}"
SSH_PORT="${SSH_PORT:-22}"
WEB_ROOT="${WEB_ROOT:-$WEB_ROOT_DEFAULT}"
CERTBOT_WEBROOT="${CERTBOT_WEBROOT:-$CERTBOT_WEBROOT_DEFAULT}"
XRAY_PORT="${XRAY_PORT:-443}"
NGINX_DECOY_PORT="${NGINX_DECOY_PORT:-8443}"
REALITY_PRIVATE_KEY="${REALITY_PRIVATE_KEY:-}"
REALITY_PUBLIC_KEY="${REALITY_PUBLIC_KEY:-}"
REALITY_SHORT_ID="${REALITY_SHORT_ID:-}"
INSTALLED_AT="${INSTALLED_AT:-}"
REALITY_XVER="${REALITY_XVER:-1}"
WARP_OUTBOUND="${WARP_OUTBOUND:-yes}"
WARP_SOCKS_PORT="${WARP_SOCKS_PORT:-40000}"
CLIENT_MAX_BODY_SIZE="${CLIENT_MAX_BODY_SIZE:-64M}"
}
save_state() {
umask 077
cat > "$STATE_FILE" <<STATE
# Generated by xray-reality-manager.sh. Keep this file private.
DOMAIN='${DOMAIN}'
EMAIL='${EMAIL}'
SSH_PORT='${SSH_PORT}'
WEB_ROOT='${WEB_ROOT}'
CERTBOT_WEBROOT='${CERTBOT_WEBROOT}'
XRAY_PORT='${XRAY_PORT}'
NGINX_DECOY_PORT='${NGINX_DECOY_PORT}'
REALITY_PRIVATE_KEY='${REALITY_PRIVATE_KEY}'
REALITY_PUBLIC_KEY='${REALITY_PUBLIC_KEY}'
REALITY_SHORT_ID='${REALITY_SHORT_ID}'
INSTALLED_AT='${INSTALLED_AT:-$(date -Is)}'
REALITY_XVER='${REALITY_XVER}'
WARP_OUTBOUND='${WARP_OUTBOUND}'
WARP_SOCKS_PORT='${WARP_SOCKS_PORT}'
CLIENT_MAX_BODY_SIZE='${CLIENT_MAX_BODY_SIZE}'
STATE
chmod 600 "$STATE_FILE"
}
need_state_or_exit() {
load_state
if [[ -z "$DOMAIN" || -z "$REALITY_PUBLIC_KEY" || -z "$REALITY_SHORT_ID" ]]; then
err "尚未完成安装或缺少状态文件:$STATE_FILE"
err "请先运行:sudo bash $0 install"
exit 1
fi
}
is_valid_domain() {
local d="$1"
[[ "$d" =~ ^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$ ]]
}
prompt_default() {
local var_name="$1"
local prompt="$2"
local default="$3"
local value
if [[ -n "$default" ]]; then
read -r -p "$prompt [$default]: " value
value="${value:-$default}"
else
read -r -p "$prompt: " value
fi
printf -v "$var_name" '%s' "$value"
}
prompt_install_inputs() {
load_state
echo
info "请输入安装所需信息。留空则使用方括号中的默认值。"
while true; do
prompt_default DOMAIN "域名,例如 jp.example.com" "${DOMAIN:-}"
if is_valid_domain "$DOMAIN"; then break; fi
err "域名格式看起来不正确,请重新输入。"
done
prompt_default EMAIL "Let's Encrypt 邮箱,留空则无邮箱注册" "${EMAIL:-}"
prompt_default SSH_PORT "SSH 端口" "${SSH_PORT:-22}"
prompt_default WEB_ROOT "网站根目录" "${WEB_ROOT:-$WEB_ROOT_DEFAULT}"
prompt_default CERTBOT_WEBROOT "ACME webroot 目录" "${CERTBOT_WEBROOT:-$CERTBOT_WEBROOT_DEFAULT}"
prompt_default XRAY_PORT "Xray 公网监听端口" "${XRAY_PORT:-443}"
prompt_default NGINX_DECOY_PORT "Nginx 本机 HTTPS decoy 端口" "${NGINX_DECOY_PORT:-8443}"
prompt_default REALITY_XVER "REALITY xver(1=开启 PROXY Protocol 透传真实客户端 IP)" "${REALITY_XVER:-1}"
prompt_default WARP_OUTBOUND "是否配置 WARP 出站(AI/流媒体走本机 WARP SOCKS5)[yes/no]" "${WARP_OUTBOUND:-yes}"
if [[ "$WARP_OUTBOUND" == "yes" ]]; then
prompt_default WARP_SOCKS_PORT "WARP SOCKS5 端口" "${WARP_SOCKS_PORT:-40000}"
fi
prompt_default CLIENT_MAX_BODY_SIZE "Nginx client_max_body_size(多模态上传需要)" "${CLIENT_MAX_BODY_SIZE:-64M}"
echo
warn "请确认 DNS 已将 $DOMAIN A 记录指向本机公网 IP,且没有被 CDN/橙云代理。"
read -r -p "确认继续安装?[y/N]: " yn
case "$yn" in
y|Y|yes|YES) ;;
*) err "已取消。"; exit 1 ;;
esac
}
install_packages() {
info "安装系统依赖..."
apt update
DEBIAN_FRONTEND=noninteractive apt install -y \
curl wget unzip jq openssl ca-certificates ufw dnsutils lsof nano socat \
qrencode htop vnstat nload nginx certbot
success "依赖安装完成。"
}
configure_firewall() {
info "配置 UFW 防火墙..."
ufw allow "${SSH_PORT}/tcp" || true
ufw allow 80/tcp || true
ufw allow "${XRAY_PORT}/tcp" || true
ufw default deny incoming || true
ufw default allow outgoing || true
ufw --force enable || true
ufw status verbose || true
success "UFW 配置完成。"
}
write_site_files() {
info "写入正常静态网站到 $WEB_ROOT ..."
mkdir -p "$WEB_ROOT" "$CERTBOT_WEBROOT"
cat > "$WEB_ROOT/index.html" <<HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Network Lab</title>
<meta name="description" content="A small private infrastructure landing page.">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header class="site-header">
<div class="container nav">
<a class="brand" href="/">Network Lab</a>
<nav aria-label="Main navigation">
<a href="/">Home</a>
<a href="/status.html">Status</a>
<a href="/notes.html">Notes</a>
<a href="/privacy.html">Privacy</a>
</nav>
</div>
</header>
<main class="container">
<section class="hero">
<p class="eyebrow">Personal Infrastructure</p>
<h1>Private network services, experiments, and operational notes.</h1>
<p class="lead">This site is used as a lightweight landing page for a privately maintained server.</p>
<div class="actions">
<a class="button" href="/status.html">View status</a>
<a class="button secondary" href="/notes.html">Read notes</a>
</div>
</section>
</main>
<footer class="site-footer"><div class="container"><p>© <span id="year"></span> Network Lab. Private personal infrastructure.</p></div></footer>
<script>document.getElementById('year').textContent = new Date().getFullYear();</script>
</body>
</html>
HTML
cat > "$WEB_ROOT/status.html" <<'HTML'
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Status - Network Lab</title><link rel="stylesheet" href="/style.css"></head><body><header class="site-header"><div class="container nav"><a class="brand" href="/">Network Lab</a><nav><a href="/">Home</a><a href="/status.html">Status</a><a href="/notes.html">Notes</a><a href="/privacy.html">Privacy</a></nav></div></header><main class="container page"><h1>Service Status</h1><p class="lead">Operational.</p></main></body></html>
HTML
cat > "$WEB_ROOT/style.css" <<'CSS'
:root{color-scheme:light;--bg:#f7f8fb;--surface:#fff;--text:#172033;--muted:#5d667a;--line:#e6e9f0;--primary:#3157d5;--primary-dark:#2544ad}body{margin:0;font-family:system-ui,-apple-system,sans-serif;background:#f7f8fb;color:var(--text);line-height:1.6}.container{width:min(1080px,calc(100% - 40px));margin:0 auto}.site-header{background:#fff;border-bottom:1px solid var(--line);padding:14px 0}.nav{display:flex;justify-content:space-between;align-items:center}.hero{padding:60px 0}.button{padding:10px 20px;border-radius:6px;background:var(--primary);color:#fff;text-decoration:none}
CSS
cat > "$WEB_ROOT/robots.txt" <<'TXT'
User-agent: *
Allow: /
TXT
chown -R www-data:www-data "$WEB_ROOT" "$CERTBOT_WEBROOT" || true
success "静态网站写入完成。"
}
write_nginx_http_only() {
info "写入 Nginx HTTP 配置用于证书签发..."
mkdir -p "$NGINX_SITE_AVAILABLE_DIR" "$NGINX_SITE_ENABLED_DIR"
backup_file "$NGINX_SITE_AVAILABLE_DIR/$DOMAIN"
cat > "$NGINX_SITE_AVAILABLE_DIR/$DOMAIN" <<NGINX
server {
listen 80;
server_name $DOMAIN;
root $WEB_ROOT;
index index.html;
location /.well-known/acme-challenge/ {
root $CERTBOT_WEBROOT;
}
location / {
return 301 https://\$host\$request_uri;
}
}
NGINX
ln -sf "$NGINX_SITE_AVAILABLE_DIR/$DOMAIN" "$NGINX_SITE_ENABLED_DIR/$DOMAIN"
rm -f "$NGINX_SITE_ENABLED_DIR/default" || true
restart_nginx_or_diagnose
}
issue_certificate() {
info "申请 Let's Encrypt 证书:$DOMAIN"
if [[ -n "$EMAIL" ]]; then
certbot certonly --webroot -w "$CERTBOT_WEBROOT" -d "$DOMAIN" --agree-tos -m "$EMAIL" --non-interactive
else
certbot certonly --webroot -w "$CERTBOT_WEBROOT" -d "$DOMAIN" --agree-tos --register-unsafely-without-email --non-interactive
fi
success "证书申请完成。"
}
write_nginx_decoy_https() {
info "写入 Nginx 本机 HTTPS Decoy 配置..."
local site_file="$NGINX_SITE_AVAILABLE_DIR/$DOMAIN"
backup_file "$site_file"
local listen_opts=""
local real_ip_block=""
if [[ "$REALITY_XVER" == "1" ]]; then
listen_opts=" proxy_protocol"
real_ip_block="
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
"
fi
cat > "$site_file" <<NGINX
server {
listen 80;
server_name $DOMAIN;
root $WEB_ROOT;
index index.html;
location /.well-known/acme-challenge/ {
root $CERTBOT_WEBROOT;
}
location / {
return 301 https://\$host\$request_uri;
}
}
server {
listen 127.0.0.1:$NGINX_DECOY_PORT ssl http2$listen_opts;
server_name $DOMAIN;
$real_ip_block
client_max_body_size $CLIENT_MAX_BODY_SIZE;
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
root $WEB_ROOT;
index index.html;
access_log /var/log/nginx/${DOMAIN}_access.log;
error_log /var/log/nginx/${DOMAIN}_error.log warn;
location / {
try_files \$uri \$uri/ =404;
}
}
NGINX
ln -sf "$NGINX_SITE_AVAILABLE_DIR/$DOMAIN" "$NGINX_SITE_ENABLED_DIR/$DOMAIN"
restart_nginx_or_diagnose
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'SH'
#!/usr/bin/env bash
set -e
systemctl reload nginx
SH
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
success "Nginx Decoy HTTPS 配置完成。"
}
install_xray() {
info "安装 Xray-core..."
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
mkdir -p /var/log/xray /usr/local/etc/xray
chmod 755 /var/log/xray
}
generate_reality_keys_if_needed() {
if [[ -n "$REALITY_PRIVATE_KEY" && -n "$REALITY_PUBLIC_KEY" && -n "$REALITY_SHORT_ID" ]]; then
return
fi
local key_output
key_output="$(xray x25519)"
REALITY_PRIVATE_KEY="$(echo "$key_output" | awk -F': *' 'tolower($1)=="private key" || $1=="PrivateKey" {print $2; exit}' | tr -d '\r')"
REALITY_PUBLIC_KEY="$(echo "$key_output" | awk -F': *' 'tolower($1)=="public key" || $1=="Password (PublicKey)" || $1=="PublicKey" {print $2; exit}' | tr -d '\r')"
REALITY_SHORT_ID="$(openssl rand -hex 8)"
}
init_clients_if_needed() {
if [[ -s "$CLIENTS_FILE" ]]; then return; fi
local first_uuid
first_uuid="$(xray uuid)"
echo -e "main-device\t$first_uuid" > "$CLIENTS_FILE"
chmod 600 "$CLIENTS_FILE"
}
clients_json() {
if [[ ! -f "$CLIENTS_FILE" ]]; then init_clients_if_needed; fi
local first=1
echo "["
while IFS=$'\t' read -r name uuid; do
[[ -z "${name:-}" || -z "${uuid:-}" ]] && continue
if [[ $first -eq 0 ]]; then echo ","; fi
first=0
cat <<JSON
{
"id": "$uuid",
"flow": "xtls-rprx-vision",
"email": "$name"
}
JSON
done < "$CLIENTS_FILE"
echo
echo " ]"
}
write_xray_config() {
info "写入 Xray 配置..."
backup_file "$XRAY_CONFIG"
local clients
clients="$(clients_json)"
local warp_outbound_json=""
local warp_rule_json=""
if [[ "$WARP_OUTBOUND" == "yes" ]]; then
warp_outbound_json=",
{
\"tag\": \"warp\",
\"protocol\": \"socks\",
\"settings\": {
\"servers\": [
{
\"address\": \"127.0.0.1\",
\"port\": $WARP_SOCKS_PORT
}
]
}
}"
warp_rule_json=" {
\"type\": \"field\",
\"domain\": [
\"geosite:openai\",
\"geosite:anthropic\",
\"geosite:google-gemini\",
\"geosite:netflix\"
],
\"outboundTag\": \"warp\"
},
"
fi
cat > "$XRAY_CONFIG" <<JSON
{
"log": {
"loglevel": "warning",
"error": "/var/log/xray/error.log"
},
"inbounds": [
{
"tag": "vless-reality-vision-in",
"listen": "0.0.0.0",
"port": $XRAY_PORT,
"protocol": "vless",
"settings": {
"clients": $clients,
"decryption": "none"
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"show": false,
"target": "127.0.0.1:$NGINX_DECOY_PORT",
"xver": $REALITY_XVER,
"serverNames": [
"$DOMAIN"
],
"privateKey": "$REALITY_PRIVATE_KEY",
"shortIds": [
"$REALITY_SHORT_ID"
]
},
"sockopt": {
"tcpKeepAliveIdle": 300
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
],
"routeOnly": true
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom"
}$warp_outbound_json,
{
"tag": "block",
"protocol": "blackhole"
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
$warp_rule_json {
"type": "field",
"ip": [
"geoip:private",
"169.254.169.254/32"
],
"outboundTag": "block"
},
{
"type": "field",
"protocol": [
"bittorrent"
],
"outboundTag": "block"
}
]
}
}
JSON
xray run -test -config "$XRAY_CONFIG"
systemctl enable --now xray
systemctl restart xray
success "Xray 配置完成并启动。"
}
make_link() {
local name="$1"
local uuid="$2"
echo "vless://${uuid}@${DOMAIN}:${XRAY_PORT}?encryption=none&security=reality&sni=${DOMAIN}&fp=chrome&pbk=${REALITY_PUBLIC_KEY}&sid=${REALITY_SHORT_ID}&type=tcp&flow=xtls-rprx-vision&spx=%2F#${name}"
}
write_summary_files() {
local summary="$OUTPUT_DIR/summary.txt"
local links="$OUTPUT_DIR/client-links.txt"
umask 077
{
echo "Xray Reality Vision 部署信息"
echo "域名:$DOMAIN"
echo "Xray 端口:$XRAY_PORT"
echo "Nginx decoy:127.0.0.1:$NGINX_DECOY_PORT"
echo "REALITY privateKey:$REALITY_PRIVATE_KEY"
echo "REALITY publicKey:$REALITY_PUBLIC_KEY"
echo "REALITY shortId:$REALITY_SHORT_ID"
} > "$summary"
chmod 600 "$summary"
if [[ -f "$CLIENTS_FILE" ]]; then
> "$links"
while IFS=$'\t' read -r name uuid; do
[[ -z "${name:-}" || -z "${uuid:-}" ]] && continue
make_link "$name" "$uuid" >> "$links"
done < "$CLIENTS_FILE"
chmod 600 "$links"
fi
}
install_all() {
require_root
ensure_dirs
prompt_install_inputs
install_packages
configure_firewall
write_site_files
write_nginx_http_only
issue_certificate
write_nginx_decoy_https
install_xray
generate_reality_keys_if_needed
init_clients_if_needed
INSTALLED_AT="$(date -Is)"
save_state
write_xray_config
write_summary_files
success "全套服务安装完成!"
}
# 简易入口调度
main() {
local cmd="${1:-menu}"
case "$cmd" in
install) install_all ;;
status)
systemctl status nginx --no-pager
systemctl status xray --no-pager
ss -lntp | grep -E ":80|:443|:8443"
;;
*) install_all ;;
esac
}
main "$@"三、路线 B 实战:底层逐行手工剖析(吃透每一个环节)
如果你希望不依赖脚本,亲手打通每一处细节,请按照本节的标准步骤在 Ubuntu 24.04 服务器上执行。
3.1 DNS 解析与安全组防火墙放行
- DNS 控制台解析:
添加一条 A 记录,将jp.your-domain.com解析到你的 VPS 公网 IP(若尚无专属域名,可先参考本文 §0.1 基础设施准备 在 Spaceship 以 1 美元获取纯数字.xyz域名并添加二级子域名)。
重要:若使用 Cloudflare,Proxy status 必须设置为 DNS only(灰色云朵),严禁开启 CDN 橙云代理! - 云厂商控制台安全组:
放行入站端口:22/tcp(SSH)、80/tcp(ACME HTTP-01 验证)、443/tcp(Xray 主入口)。 - VPS 开启 UFW 防火墙:bash
sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw --force enable
3.2 签发真实合规的 Let's Encrypt CA 证书
安装 Nginx 与 Certbot:
sudo apt update && sudo apt install -y nginx certbot
sudo mkdir -p /var/www/jp /var/www/certbot首先配置 Nginx 仅监听 80 端口,专用于响应 ACME HTTP-01 挑战:
# /etc/nginx/sites-available/jp.your-domain.com
server {
listen 80;
listen [::]:80;
server_name jp.your-domain.com;
root /var/www/jp;
index index.html;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}启用站点并申请证书:
sudo ln -sf /etc/nginx/sites-available/jp.your-domain.com /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl restart nginx
# 签发真实 SSL 证书(替换为你自己的域名与邮箱)
sudo certbot certonly --webroot \
-w /var/www/certbot \
-d jp.your-domain.com \
--agree-tos \
-m admin@your-domain.com3.3 配置 Nginx 本机 HTTPS Decoy (127.0.0.1:8443)
证书就绪后,编辑站点配置。重点注意:Nginx 绝不能监听公网 443,而是监听本地 127.0.0.1:8443 并开启 proxy_protocol:
# /etc/nginx/sites-available/jp.your-domain.com
server {
listen 80;
listen [::]:80;
server_name jp.your-domain.com;
root /var/www/jp;
index index.html;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
# 关键:监听本机 8443,开启 SSL、HTTP/2 与 PROXY Protocol
listen 127.0.0.1:8443 ssl http2 proxy_protocol;
server_name jp.your-domain.com;
# 信任本地 Xray 回落的 PROXY 头部,还原访问者的真实客户端公网 IP
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
# 调大请求体限制(支持多模态大模型长文档/图片/音频并发上传)
client_max_body_size 64M;
ssl_certificate /etc/letsencrypt/live/jp.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jp.your-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
root /var/www/jp;
index index.html;
access_log /var/log/nginx/jp.your-domain.com_access.log;
error_log /var/log/nginx/jp.your-domain.com_error.log warn;
location / {
try_files $uri $uri/ =404;
}
}配置证书自动续期钩子并重载 Nginx:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat << 'EOF' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/usr/bin/env bash
set -e
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo nginx -t && sudo systemctl restart nginx3.4 安装 Xray-core 并生成 REALITY 专属秘钥对
使用官方脚本安装最新 Xray-core:
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install生成鉴权三要素(请记录在本地备忘录中,切勿泄露):
# 1. 生成客户端用户 UUID
xray uuid
# 示例:8f13b284-4f01-4a81-8176-7918a287e029
# 2. 生成 REALITY X25519 密钥对
xray x25519
# 输出示例:
# Private key: SERVER_PRIVATE_KEY (只填在服务端 config.json)
# Public key: CLIENT_PUBLIC_KEY (只分发给客户端使用)
# 3. 生成 shortId (8位 16 进制随机字符串)
openssl rand -hex 8
# 示例:e9a184bc2703f8413.5 生产级 Xray 配置(Vision + Fallback + WARP 分流)
编辑 /usr/local/etc/xray/config.json:
{
"log": {
"loglevel": "warning",
"error": "/var/log/xray/error.log"
},
"inbounds": [
{
"tag": "vless-reality-vision-in",
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "你的_UUID",
"flow": "xtls-rprx-vision",
"email": "main-device"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"show": false,
"target": "127.0.0.1:8443",
"xver": 1,
"serverNames": [
"jp.your-domain.com"
],
"privateKey": "你的_SERVER_PRIVATE_KEY",
"shortIds": [
"你的_SHORT_ID"
]
},
"sockopt": {
"tcpKeepAliveIdle": 300
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
],
"routeOnly": true
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom"
},
{
"tag": "warp",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 40000
}
]
}
},
{
"tag": "block",
"protocol": "blackhole"
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": [
"geosite:openai",
"geosite:anthropic",
"geosite:google-gemini",
"geosite:netflix"
],
"outboundTag": "warp"
},
{
"type": "field",
"ip": [
"geoip:private",
"169.254.169.254/32"
],
"outboundTag": "block"
},
{
"type": "field",
"protocol": [
"bittorrent"
],
"outboundTag": "block"
}
]
}
}测试配置并启动服务:
# 测试语法无误
xray run -test -config /usr/local/etc/xray/config.json
# 启动并设置开机自启
sudo systemctl enable --now xray
sudo systemctl restart xray
# 检查端口监听:应呈现 443 由 xray 独占监听,8443 由 nginx 监听
ss -lntp | grep -E ':80|:443|:8443'四、多端客户端无缝接入实操
4.1 标准 VLESS REALITY 分享链接格式
将你的参数代入以下模板中:
vless://<UUID>@<DOMAIN>:443?encryption=none&security=reality&sni=<DOMAIN>&fp=chrome&pbk=<CLIENT_PUBLIC_KEY>&sid=<SHORT_ID>&type=tcp&flow=xtls-rprx-vision&spx=%2F#Tokyo-Reality参数速查表:
| 字段 | 对应数值与含义 | 注意事项 |
|---|---|---|
| 地址 / 端口 | jp.your-domain.com:443 | 端口必须是标准 443 |
| UUID | 服务端生成的 UUID | 客户端与服务端一一匹配 |
| security | reality | 加密选 none |
| SNI | jp.your-domain.com | 必须与你的自建伪装域名一致 |
| pbk | CLIENT_PUBLIC_KEY | 绝不能填服务端的 privateKey! |
| flow | xtls-rprx-vision | 必须开启 Vision 流控 |
| fingerprint | chrome | 模拟主流浏览器 uTLS 指纹 |
在服务器上快速生成终端二维码(方便手机端直接扫码):
qrencode -t ANSIUTF8 'vless://<完整链接>'4.2 iOS / iPadOS:Streisand 实测
- App Store 安装 Streisand;
- 点击右上角
+,选择 扫码 或 从剪贴板导入; - 核对节点信息:确认
Security为REALITY、Public Key正确、Flow为xtls-rprx-vision; - 首次点击连接时系统会弹出“添加 VPN 配置”,点击“允许”即可;
- 推荐模式选择“规则模式”,国内外流量智能分流。
4.3 Android:v2rayNG 实测
- 从 GitHub Releases 或 Google Play 安装 v2rayNG;
- 点击右上角
+,选择“从剪贴板导入”; - 长按节点编辑,核对“流控 (flow)”是否为
xtls-rprx-vision,加密方式是否为none; - 点击右下角 V 按钮连接,授权创建 VPN 连接。
4.4 Windows:v2rayN 实测
- 从 GitHub Releases 下载解压 v2rayN,并更新内置 Core 至最新版本;
- 菜单栏选择
服务器➔从剪贴板导入分享链接; - 右键节点设为“活动服务器”;
- 底部系统代理选择 “自动配置系统代理”;
- 若需要让 Git 终端、SSH 或无代理设置的本地软件生效,开启 TUN 模式(需以管理员身份运行)。
4.5 macOS:v2rayN 或 Throne / NekoRay
- 安装 macOS 版 v2rayN 或 Throne;
- 从剪贴板导入 VLESS 链接;
- 开启系统代理或 TUN 虚拟网卡模式。若需要在终端中临时生效,执行:bash
export http_proxy=http://127.0.0.1:10809 export https_proxy=http://127.0.0.1:10809 export all_proxy=socks5://127.0.0.1:10808
五、生产运维与多设备全生命周期管理
5.1 拒绝“一码多用”:多设备独立 UUID 隔离
千万不要让手机、笔记本、台式机共用同一个 UUID!多设备并发重放容易引起风控,且设备丢失时无法精准吊销。
- 若使用脚手架:直接运行
sudo bash /root/xray-reality-manager.sh add-client,输入设备代号(如macbook)秒级签发; - 若手工维护:运行
xray uuid生成新 ID,并在/usr/local/etc/xray/config.json的clients列表中追加:json执行{ "id": "NEW_MACBOOK_UUID", "flow": "xtls-rprx-vision", "email": "macbook" }xray run -test -config /usr/local/etc/xray/config.json && sudo systemctl restart xray即可。
5.2 出站 WARP SOCKS5 代理部署要点
在第 3.5 节配置的 Xray 规则中,AI 和流媒体流量被路由至 127.0.0.1:40000。该端口由本机 Cloudflare WARP Client 提供(其破除机房 ASN 黑名单与接入 Anycast 边缘网络的工作机制详见 §0.3 破除大模型机房 IP 风控 的全链路对比拓扑图)。
极度危险:必须严格使用 Proxy 模式!
WARP 默认运行在全局虚拟网卡模式。一旦在云主机上执行 warp-cli connect,WARP 会强行接管默认路由,导致你的当前 SSH 会话立即断开,服务器对外彻底失联(只能通过云厂商控制台救援)!
正确安装与安全启动步骤:
# 1. 注册新设备
warp-cli registration new
# 2. 核心:强制切换为 Proxy 代理模式(只开放本地 127.0.0.1:40000)
warp-cli mode proxy
warp-cli proxy port 40000
# 3. 确认模式为 proxy 后,再启动连接
warp-cli connect
sudo systemctl enable warp-svc
# 4. 验证出站是否已变为 Cloudflare 纯净 IP
curl -x socks5h://127.0.0.1:40000 ifconfig.me5.3 开启 Linux 内核 BBR 加速
在高丢包公网链路中,开启 Google BBR 算法能显著提升单线程大带宽吞吐与连接响应速度:
sudo modprobe tcp_bbr
cat << 'EOF' | sudo tee /etc/sysctl.d/99-xray-bbr.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sudo sysctl --system
# 验证开启状态:应返回 bbr
sysctl net.ipv4.tcp_congestion_control六、一键复盘检查表与常见故障排查
6.1 443 端口抢占与 Nginx 冲突
- 现象:Xray 无法启动,日志提示
bind: address already in use。 - 排查:运行
ss -lntp | grep ':443'。若发现是nginx占用了0.0.0.0:443,说明 Nginx 配置中依然残留了旧站点的listen 443 ssl;。 - 解决:检查
/etc/nginx/sites-enabled/下的所有配置,确保 Nginx 仅监听80和127.0.0.1:8443,修改后sudo systemctl restart nginx && sudo systemctl restart xray。
6.2 REALITY_XVER=1 时本地 openssl 直连“握手失败”的正常预期
- 现象:使用
openssl s_client -connect 127.0.0.1:8443测试本机 Nginx,报错handshake failure。 - 解析:这是完全符合预期的! 开启
proxy_protocol后,Nginx 强制要求 TCP 报文的第一行必须是类似PROXY TCP4 ...的协议头,原始 openssl 直连不带该头部必然失败。 - 正确验证方式:走公网 443 发起回落请求:bash预期返回 HTTP 200/301,随后查看日志首列:
curl -I https://jp.your-domain.com/bash日志第一列必须是发起者的公网真实 IP,绝不能是tail -n 3 /var/log/nginx/jp.your-domain.com_access.log127.0.0.1!
6.3 客户端连接失败三要素排查
- 公钥与私钥填反(最常见):服务端配置文件中使用
SERVER_PRIVATE_KEY,客户端节点链接中填入的必须是CLIENT_PUBLIC_KEY; - 遗漏 Vision 流控:确认客户端节点配置中
flow明确填写为xtls-rprx-vision; - Cloudflare 误开橙云代理:REALITY 基于裸 TCP 原始流,如果 DNS 开启了 CDN 代理,回源流量会被 Cloudflare 拦截。必须保持 DNS only。
6.4 AI 分流全部超时或报错
- 现象:访问常规网站正常,访问 ChatGPT / Claude 页面打不开或超时。
- 排查:检查本机 WARP 服务是否存活:bash若端口无响应,说明 WARP 断开或未安装。未部署 WARP 时,请先移除
warp-cli status curl -x socks5h://127.0.0.1:40000 ifconfig.meconfig.json中的warp出站与对应的geosite规则。
6.5 脚本整体重建配置的防丢提醒
- 安全警示:若后续使用
xray-reality-manager.sh的add-client、rm-client或upgrade,脚本会使用模板重建配置。如果你在 Nginx 中手动挂载了其他业务反代(如 CLIProxyAPI 的/cpa/),脚本会发出检测警告。请务必及时备份/etc/nginx/sites-available/与/usr/local/etc/xray/config.json!
交流讨论