2929 字
15 分钟
Xray的配置
2023-07-16
2026-05-23

本文章主要记录一些VPS的设置,使得购买的VPS及域名能够支持科学上网,服务器为Ubuntu 20.04。在执行任何操作之前,切换到/root目录,并使用apt update && apt upgrade来更新系统。

事前准备#

服务器准备#

部分软件:

Terminal window
apt install vim git curl wget -y

启用 BBR TCP 拥塞控制算法:

Terminal window
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
sysctl -p

设置时区:

Terminal window
timedatectl set-timezone Asia/Shanghai

Cloudflare准备#

将Cloudflare中SSL/TLS加密模式设置为完全(严格)

Cloudflare创建一个新的令牌(参考cf插件主页配置),权限为Zone.Zone.Read; Zone.DNS.Edit,并将令牌保存到本地。

Caddy官方下载地址,选择dns.provider.cloudflare,下载到本地准备。

设置密钥登陆#

生成密钥#

连接到服务器后,使用ssh-keygen来生成密钥,并把私钥保存到本地。

使用mv /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys更改公钥名称。使用如下命令来更改权限:

Terminal window
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

修改登录方式#

使用vim /etc/ssh/sshd_config来修改SSH配置文件,将PasswordAuthentication改为no,将PubkeyAuthentication改为yes,并systemctl restart ssh重启SSH服务。

如有必要,可以更改ssh的端口号,修改Port即可。

安装Caddy#

Terminal window
apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy

把之前下载的Caddy传到root下,

Terminal window
mv ./caddy_linux_amd64_custom ./caddy
chmod +x ./caddy
./caddy list-modules | grep dns

如果正常,会显示出dns.providers.cloudflare模块。

在替换官方的Caddy:

Terminal window
mv ./caddy /usr/bin/caddy

寻找合适的伪装站#

示例关键字:intext:登录 Cloudreve

配置Caddy#

启动Caddy服务:

Terminal window
systemctl enable --now caddy

通过如下命令查看Caddy的日志:

Terminal window
journalctl -u caddy --no-pager | less +G
Terminal window
vim /etc/caddy/Caddyfile

改为如下:

Terminal window
example.com {
encode gzip
tls {
dns cloudflare 你的API token
protocols tls1.2 tls1.3
}
reverse_proxy /xui* 127.0.0.1:port {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
# ws
# reverse_proxy /ray* 127.0.0.1:port {
# header_up Host {host}
# header_up X-Real-IP {remote_host}
# }
# xhttp
reverse_proxy /ray* h2c://127.0.0.1:port {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
# 伪装网址
reverse_proxy https://demo.cloudreve.org {
header_up Host {upstream_hostport}
}
}

奇怪的是xui只能在Chrome匿名模式下进入

caddy run查看tls是否生效:

Terminal window
tls.obtain certificate obtained successfully
Terminal window
sudo systemctl reload caddy

3X-UI#

安装3X-UI#

参考官方仓库

Terminal window
curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh | sudo bash

配置XUI#

进入面板,把监听IP改为127.0.0.1,端口改为上面定义的xui端口。添加端口为ray端口的节点。

配置节点 vmess+ws#

监听设置为127.0.0.1,端口设置为ray的端口,传输方式为WebSocket,路径填/ray

配置节点 vless+xhttp#

监听设置为127.0.0.1,端口设置为ray的端口,传输方式为xhttp,路径填/ray

优选ip#

这里使用假节点vless://[email protected]:443?encryption=none&security=tls&sni=aaa.bbb.ccc&type=websocket&path=%2F123&#temp%20-%2054,然后使用如下脚本拿到ip:

import base64
import json
import urllib.parse
import os
INPUT_FILE = "cf_ips.txt"
OUTPUT_FILE = "new_ips.txt"
def extract_ips():
ips = []
if not os.path.exists(INPUT_FILE):
print(f"[!] 找不到 {INPUT_FILE},请确保该文件存在。")
return
with open(INPUT_FILE, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
if line.startswith("vless://"):
try:
parsed = urllib.parse.urlparse(line)
netloc = parsed.netloc
# 解析如 uuid@ip:port 的格式
if "@" in netloc:
host_port = netloc.split("@")[-1]
ip = host_port.split(":")[0]
ips.append(ip)
except Exception as e:
print(f"[!] VLESS 解析失败: {line[:30]}... 错误信息: {e}")
elif line.startswith("vmess://"):
try:
payloadstr = line[8:]
padding = len(payloadstr) % 4
if padding:
payloadstr += '=' * (4 - padding)
config = json.loads(base64.b64decode(payloadstr).decode('utf-8'))
ip = config.get("add")
if ip:
ips.append(ip)
except Exception as e:
print(f"[!] VMESS 解析失败: {line[:30]}... 错误信息: {e}")
# 去重处理(如有需要可取消注释下面这行进行去重过滤,但为了保持与源文件等量这里原样输出)
# ips = list(dict.fromkeys(ips))
if ips:
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
for ip in ips:
f.write(ip + "\n")
print(f"[+] 成功提取 {len(ips)} 个 IP 地址,已保存至 {OUTPUT_FILE}")
else:
print("[!] 未能从文件中提取到任何 IP。")
if __name__ == "__main__":
extract_ips()

v2rayn#

拿到new_ips.txt后,新建一个ips.txt,把内容复制进去,然后使用如下脚本生成对应的链接,然后进行测速:

import urllib.parse
import os
import base64
import json
# ==========================================
# ⚙️ 配置区
# ==========================================
# 你的 3x-ui 生成的“本地母体链接”
BASE_URI = "vless://..."
# 域名
# TODO: 替换为你的真实域名
DOMAIN = "aaa.bbb.ccc"
# 节点名称
# TODO: 替换为你喜欢的节点名称前缀
NODE_NAME_PREFIX = "MyNode"
TXT_FILE = "ips.txt"
OUTPUT_FILE = "v2rayn.txt"
# ==========================================
def build_links():
if not os.path.exists(TXT_FILE):
print(f"[!] 找不到 {TXT_FILE},请先创建。")
return
is_vmess = BASE_URI.startswith("vmess://")
is_vless = BASE_URI.startswith("vless://")
if not is_vmess and not is_vless:
print("[!] 初始化失败,母体链接必须以 vless:// 或 vmess:// 开头")
return
# 1. 拆解母体链接
if is_vless:
parsed_base = urllib.parse.urlparse(BASE_URI)
qs = urllib.parse.parse_qs(parsed_base.query)
uuid = parsed_base.username
# 提取核心固定参数(如果母体里有,就继承;没有就用默认值)
network_type = qs.get('type', ['xhttp'])[0]
path = qs.get('path', ['/'])[0]
mode = qs.get('mode', ['auto'])[0]
encryption = qs.get('encryption', ['none'])[0]
else:
# 解析 vmess
payloadstr = BASE_URI[8:]
padding = len(payloadstr) % 4
if padding:
payloadstr += '=' * (4 - padding)
try:
base_config = json.loads(base64.b64decode(payloadstr).decode('utf-8'))
except Exception as e:
print(f"[!] VMESS 解析失败: {e}")
return
links_list = []
index = 1
# 2. 遍历优选 IP 列表
with open(TXT_FILE, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split()
ip = parts[0]
domain = DOMAIN # 跟 build_nodes.py 保持一致
node_name_base = f"{NODE_NAME_PREFIX} - {index}"
if is_vless:
# 3. 组装全新的查询参数 (Query String)
new_qs = {
'encryption': encryption,
'security': 'tls', # 强制开启 TLS
'sni': domain, # 注入真实域名
'type': network_type,
'path': path,
'mode': mode,
# 'alpn': 'h2,http/1.1', # 注入 CF 兼容的 ALPN
# 'fp': 'chrome', # 注入浏览器指纹
# 'host': domain, # 注入 Host 头
}
# 如果母体带有 extra 的 padding 参数,原样继承
if 'extra' in qs:
new_qs['extra'] = qs['extra'][0]
if 'x_padding_bytes' in qs:
new_qs['x_padding_bytes'] = qs['x_padding_bytes'][0]
# 4. URL 编码参数
encoded_qs = urllib.parse.urlencode(new_qs, doseq=True)
# 5. 组装节点名称(备注名),与 build_nodes.py 保持一致
node_name = urllib.parse.quote(node_name_base)
# 6. 拼接最终的 URI
final_uri = f"vless://{uuid}@{ip}:443?{encoded_qs}#{node_name}"
links_list.append(final_uri)
else:
# VMESS 处理逻辑
config = base_config.copy()
config['add'] = ip
config['port'] = 443 # 使用 CF 的 TLS 端口
config['tls'] = 'tls' # 强制 TLS
config['sni'] = domain # 注入真实域名
config['ps'] = node_name_base # 节点名称
b64str = base64.b64encode(json.dumps(config).encode('utf-8')).decode('utf-8')
links_list.append(f"vmess://{b64str}")
index += 1
# 追加最后一个直连域名的节点
node_name_base = f"{NODE_NAME_PREFIX} - {index}"
if is_vless:
new_qs['sni'] = DOMAIN
new_qs['host'] = DOMAIN
encoded_qs = urllib.parse.urlencode(new_qs, doseq=True)
node_name = urllib.parse.quote(node_name_base)
final_uri = f"vless://{uuid}@{DOMAIN}:443?{encoded_qs}#{node_name}"
links_list.append(final_uri)
else:
config = base_config.copy()
config['add'] = DOMAIN
config['port'] = 443
config['tls'] = 'tls'
config['sni'] = DOMAIN
config['host'] = DOMAIN
config['ps'] = node_name_base
b64str = base64.b64encode(json.dumps(config).encode('utf-8')).decode('utf-8')
links_list.append(f"vmess://{b64str}")
# 7. 导出到文件
if links_list:
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
for link in links_list:
f.write(link + '\n')
schema_name = "VMESS" if is_vmess else "VLESS"
print(f"[+] 成功生成 {len(links_list)} 个标准 {schema_name} 链接,已保存至 {OUTPUT_FILE}")
if __name__ == '__main__':
build_links()

clash#

vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIlJFUExBQ0VNRSIsDQogICJhZGQiOiAiMTA0LjE4LjExOS4yMCIsDQogICJwb3J0IjogIjQ0MyIsDQogICJpZCI6ICIwY2MzMjY3ZS1kNmIwLTRiMjctYTM0Zi0zOGY3MGI5ZjhjZGMiLA0KICAiYWlkIjogIjAiLA0KICAic2N5IjogImF1dG8iLA0KICAibmV0IjogIndzIiwNCiAgInR5cGUiOiAibm9uZSIsDQogICJob3N0IjogImFhYS5iYmIiLA0KICAicGF0aCI6ICIvYWJjZGVmZyIsDQogICJ0bHMiOiAidGxzIiwNCiAgInNuaSI6ICIiLA0KICAiYWxwbiI6ICIiDQp9

使用上面的假链接到这里生成订阅链接,获取配置文件,保存到configs/mini_adplus.yaml。然后使用如下的脚本生成:

import urllib.parse
import urllib.request
import json
import yaml
import os
import base64
import ssl
# ==========================================
# ⚙️ 配置区
# ==========================================
# 你的母体链接,支持 vless:// 或 vmess://
BASE_URI = "vless://..."
# 域名
DOMAIN = "aaa.bbb.ccc"
# 节点名称
NODE_NAME_PREFIX = "MyNode"
TXT_FILE = "ips.txt"
OUTPUT_YAML = "optimized_nodes.yaml"
# mini_adplus 配置模板
MINI_ADPLUS_YAML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "configs", "mini_adplus.yaml")
MINI_ADPLUS_URL = (
"https://api.wcc.best/sub?target=clash&url=vmess%3A%2F%2Few0KICAidiI6ICIyIiwNCiAgInBzIjogIlJFUExBQ0VNRSIsDQogICJhZGQiOiAiMTA0LjE4LjExOS4yMCIsDQogICJwb3J0IjogIjQ0MyIsDQogICJpZCI6ICIwY2MzMjY3ZS1kNmIwLTRiMjctYTM0Zi0zOGY3MGI5ZjhjZGMiLA0KICAiYWlkIjogIjAiLA0KICAic2N5IjogImF1dG8iLA0KICAibmV0IjogIndzIiwNCiAgInR5cGUiOiAibm9uZSIsDQogICJob3N0IjogImFhYS5iYmIiLA0KICAicGF0aCI6ICIvYWJjZGVmZyIsDQogICJ0bHMiOiAidGxzIiwNCiAgInNuaSI6ICIiLA0KICAiYWxwbiI6ICIiDQp9%7C&insert=false&config=https%3A%2F%2Fraw.githubusercontent.com%2FACL4SSR%2FACL4SSR%2Fmaster%2FClash%2Fconfig%2FACL4SSR_Online_Mini_AdblockPlus.ini"
)
# ==========================================
def build_node_yaml(protocol, ip, uuid, network, path, mode, domain, index, config_dict):
# 构造 Mihomo 标准 YAML 字典
proxy = {
'name': f"{NODE_NAME_PREFIX} - {index}",
'type': protocol,
'server': ip,
'port': 443, # 强制使用 HTTPS 端口
'uuid': uuid, # 继承母体的 UUID
'network': network,
'udp': True,
'tls': True, # 强制开启 TLS
# 'alpn': ['h2', 'http/1.1'], # 强制指定 ALPN,部分 CF 节点非常挑剔
# 'sni': domain,
'servername': domain, # 许多受限内核 VLESS 配置只认 servername
'skip-cert-verify': False,
'client-fingerprint': 'chrome', # 极其重要:伪装 TLS 指纹,防 CF 阻断
}
if protocol == 'vmess':
proxy['alterId'] = 0
proxy['cipher'] = config_dict.get('scy', 'auto')
if network == 'xhttp':
proxy['xhttp-opts'] = {
'mode': mode,
'path': path,
# 'headers': {
# 'Host': [domain]
# }
}
if protocol == 'vless' and 'extra' in config_dict:
try:
extra_json = json.loads(urllib.parse.unquote(config_dict['extra'][0]))
proxy['xhttp-opts']['extra'] = extra_json
except:
pass
elif network == 'ws':
proxy['ws-opts'] = {
'path': path,
# 'headers': {
# 'Host': domain # ws-opts 的 Host 在 Mihomo 中通常是字符串
# }
}
return proxy
def download_mini_adplus():
"""检测 mini_adplus.yaml 是否存在,不存在则从远程下载"""
if os.path.exists(MINI_ADPLUS_YAML):
print(f"[✓] 配置模板已存在: {MINI_ADPLUS_YAML}")
return True
print(f"[*] 配置模板不存在,正在从远程下载...")
os.makedirs(os.path.dirname(MINI_ADPLUS_YAML), exist_ok=True)
try:
# 跳过 SSL 验证(subconverter API 可能证书不完整)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
req = urllib.request.Request(MINI_ADPLUS_URL, headers={
'User-Agent': 'ClashForAndroid/2.5.12'
})
with urllib.request.urlopen(req, context=ctx, timeout=30) as resp:
data = resp.read()
with open(MINI_ADPLUS_YAML, 'wb') as f:
f.write(data)
print(f"[✓] 下载成功: {MINI_ADPLUS_YAML} ({len(data)} bytes)")
return True
except Exception as e:
print(f"[✗] 下载失败: {e}")
return False
def build_clash():
# 0. 确保 mini_adplus.yaml 模板存在
if not download_mini_adplus():
print("[!] 无法获取配置模板,退出。")
return
if not os.path.exists(TXT_FILE):
print(f"[!] 找不到 {TXT_FILE},请先创建。")
return
# 1. 拆解母体链接
if BASE_URI.startswith('vmess://'):
protocol = 'vmess'
b64_str = BASE_URI[8:]
b64_str += "=" * ((4 - len(b64_str) % 4) % 4)
config_dict = json.loads(base64.b64decode(b64_str).decode('utf-8'))
uuid = config_dict.get('id')
network = config_dict.get('net', 'ws')
path = config_dict.get('path', '/')
mode = 'auto'
elif BASE_URI.startswith('vless://'):
protocol = 'vless'
parsed_base = urllib.parse.urlparse(BASE_URI)
config_dict = urllib.parse.parse_qs(parsed_base.query)
uuid = parsed_base.username
network = config_dict.get('type', ['tcp'])[0]
path = urllib.parse.unquote(config_dict.get('path', ['/'])[0])
mode = config_dict.get('mode', ['auto'])[0]
else:
print("[!] 不支持的协议类型,只能处理 vmess:// 或 vless://")
return
proxies_list = []
index = 1
# 2. 读取优选列表并进行批量克隆
with open(TXT_FILE, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split()
ip = parts[0]
domain = DOMAIN
proxy = build_node_yaml(protocol, ip, uuid, network, path, mode, domain, index, config_dict)
index += 1
proxies_list.append(proxy)
# 追加最后一个兜底真实域名的节点
proxy = build_node_yaml(protocol, DOMAIN, uuid, network, path, mode, DOMAIN, index, config_dict)
proxies_list.append(proxy)
# 3. 基于 mini_adplus.yaml 模板生成最终配置
FINAL_CONFIG = "final_config.yaml"
if proxies_list:
proxy_names = [p['name'] for p in proxies_list]
try:
from ruamel.yaml import YAML
yaml_parser = YAML()
yaml_parser.preserve_quotes = True
with open(MINI_ADPLUS_YAML, 'r', encoding='utf-8') as f:
config_data = yaml_parser.load(f)
except ImportError:
with open(MINI_ADPLUS_YAML, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)
yaml_parser = None
# 替换 proxies 为新生成的节点
if 'proxy-providers' in config_data:
del config_data['proxy-providers']
config_data['proxies'] = proxies_list
# mode 值需要小写
if 'mode' in config_data:
config_data['mode'] = config_data['mode'].lower()
# 期望的策略组结构(用于变更检测)
EXPECTED_GROUPS = [
'🚀 节点选择',
'♻️ 自动选择',
'🎯 全球直连',
'🛑 全球拦截',
'🐟 漏网之鱼',
]
# 需要注入新节点的策略组
INJECT_GROUPS = {'🚀 节点选择', '♻️ 自动选择'}
# 需要清理旧节点但不注入新节点的策略组(只保留分组引用)
CLEAN_GROUPS = {'🐟 漏网之鱼'}
if 'proxy-groups' in config_data:
# ⚠️ 变更检测:检查模板的策略组是否与预期一致
actual_groups = [g['name'] for g in config_data['proxy-groups']]
if actual_groups != EXPECTED_GROUPS:
print("=" * 60)
print("⚠️ 警告: 模板策略组结构已发生变化!")
print(f" 期望: {EXPECTED_GROUPS}")
print(f" 实际: {actual_groups}")
added = set(actual_groups) - set(EXPECTED_GROUPS)
removed = set(EXPECTED_GROUPS) - set(actual_groups)
if added:
print(f" 新增: {added}")
if removed:
print(f" 缺失: {removed}")
if actual_groups != EXPECTED_GROUPS and not (added or removed):
print(f" 顺序不同")
print(" 请检查是否需要更新脚本逻辑!")
print("=" * 60)
# 收集内置策略和分组名,用于区分"具体节点"和"引用"
builtin_names = {'DIRECT', 'REJECT'}
group_names = {g['name'] for g in config_data['proxy-groups']}
skip_names = builtin_names | group_names
for group in config_data['proxy-groups']:
gname = group['name']
# 处理 use 字段(proxy-provider 引用)
if 'use' in group:
del group['use']
if gname in INJECT_GROUPS:
# 节点选择 / 自动选择:替换为新节点
if 'proxies' in group:
kept = [p for p in group['proxies'] if p in skip_names]
group['proxies'] = proxy_names + kept
else:
group['proxies'] = proxy_names[:]
elif gname in CLEAN_GROUPS:
# 漏网之鱼:只删掉具体节点名(REPLACEME等),保留分组引用
if 'proxies' in group:
group['proxies'] = [p for p in group['proxies'] if p in skip_names]
# 修补 rules 尾部,防止 DNS 泄露
if 'rules' in config_data:
rules = config_data['rules']
new_rules = []
for r in rules:
rule_str = str(r)
# GEOIP,CN 加上 no-resolve
if rule_str.startswith('GEOIP,CN,') and 'no-resolve' not in rule_str:
new_rules.append(rule_str + ',no-resolve')
else:
new_rules.append(r)
# 在 MATCH 之前插入 GEOSITE,CN(如果不存在)
has_geosite_cn = any('GEOSITE,CN,' in str(r) for r in new_rules)
if not has_geosite_cn:
# 找到 MATCH 规则的位置,插入到它前面
match_idx = next((i for i, r in enumerate(new_rules) if str(r).startswith('MATCH,')), len(new_rules))
new_rules.insert(match_idx, 'GEOSITE,CN,🎯 全球直连')
config_data['rules'] = new_rules
# ⚠️ 检查所有 IP 类规则是否都有 no-resolve
IP_RULE_PREFIXES = ('IP-CIDR,', 'IP-CIDR6,', 'GEOIP,')
missing = []
for i, r in enumerate(new_rules):
rule_str = str(r)
if any(rule_str.startswith(p) for p in IP_RULE_PREFIXES) and 'no-resolve' not in rule_str:
missing.append((i + 1, rule_str))
if missing:
print("=" * 60)
print(f"⚠️ 警告: 发现 {len(missing)} 条 IP 类规则缺少 no-resolve,可能导致 DNS 泄露!")
for idx, rule in missing[:10]: # 最多显示10条
print(f" 第 {idx} 条: {rule}")
if len(missing) > 10:
print(f" ... 还有 {len(missing) - 10} 条")
print("=" * 60)
# 写入最终配置
try:
if yaml_parser is not None:
with open(FINAL_CONFIG, 'w', encoding='utf-8') as f:
yaml_parser.dump(config_data, f)
else:
with open(FINAL_CONFIG, 'w', encoding='utf-8') as f:
yaml.dump(config_data, f, allow_unicode=True, sort_keys=False, indent=2)
print(f"[+] 成功生成配置: {FINAL_CONFIG} (基于 mini_adplus.yaml, 类型: {protocol}, 包含 {len(proxies_list)} 个节点)")
except Exception as e:
print(f"[✗] 写入配置失败: {e}")
if __name__ == '__main__':
build_clash()

注意事项#

如果clash遇到wsl无网络的问题,在最前面加入mtu设置:

mixed-port: 7890
allow-lan: true
mode: rule
log-level: info
external-controller: :9090
tun:
mtu: 1280
Xray的配置
https://blog.xiaobaizhang.top/posts/xray/
作者
张小白
发布于
2023-07-16
许可协议
CC BY-NC-SA 4.0