Oracle VPS 代理部署的坑

在 Oracle Cloud 免费实例上部署 sing-box 代理时反复踩过的真实坑:OCI 安全列表只放 22、GFW 链路层黑洞、无公网 IPv6 导致 handshake dial 失败、公钥须用 cryptography 校验、Cloudflare SSL 必须 Full 等。

#type / debug #status / evergreen #tech / network #tech / security #tech / ops / cloud

[!info] related notes

Oracle VPS 代理部署的坑

[!abstract] 核心结论 从中国大陆连 Oracle 上的 sing-box,直连暴露的代理端口(尤其 VLESS-Reality)会被 GFW 在链路层黑洞;能连上的只有「直连 SS」和「Cloudflare 前置的 WS/TLS」两条路。下面按踩坑顺序记录诊断与修复。

坑 1:OCI 安全列表默认只放行 22

现象:Cloudflare 回源报 522(连不上源站),或客户端直连超时;但 ss 显示端口在监听、systemctl 显示服务正常。

诊断:去 OCI 控制台看 VCN 的 Security Lists / NSG,默认只有 22/TCP + ICMP,根本没有 80/443。很多人「以为自己放行了」其实没生效。

修复:加 ingress 规则:

  • 80:TCP + UDP(Shadowsocks 走 UDP,只开 TCP 不通)→ 或直接选「All Protocols」。
  • 443:TCP。
  • 8443:TCP(仅验证用)。

[!warning] SS 是 UDP 用户最初只给 80 加了 TCP,SS 一直不通——Shadowsocks 流量是 UDP,防火墙必须同时放 UDP。

坑 2:GFW 对暴露的 Reality/代理端口做链路层黑洞

现象:客户端连 :8443 Reality,日志 outbound connection ... EOF;服务器 journalctl该 IP 在 80/443 上哗哗连,8443 一次入站都没有

关键诊断(别被骗):用 Python 裸 socket 探 8443 会显示 OPEN (0ms)——这是 GFW/中间盒伪造的 SYN-ACK,不是真连通。真正 TLS 握手包在半路被丢弃,服务器永远收不到,客户端只能等超时 EOF。

结论:从中国大陆直连暴露的 sing-box 端口基本不可用,尤其 Reality(会被主动探测后封锁)。必须走 Cloudflare 前置(客户端只连 CDN,GFW 看不到真实 VPS)。

验证方法:在服务器本机用正确公钥走 127.0.x.x:8443 自测(HTTP=200 且出现 inbound/vless[VLESS-Reality] 记录)→ 证明服务器配置 OK,问题在链路。

坑 3:Oracle 免费实例没有公网 IPv6

现象:sing-box 报 failed to dial dest: invalid address,Reality 入站起不来或握手失败。

根因:Reality 的 handshake 目标(如 updates.cdn-apple.com)解析优先返回 IPv6,而 Oracle 免费实例无公网 IPv6,dial 失败。

修复:把 handshake 写成硬编码 IPv4

"reality": {
  "enabled": true,
  "handshake": { "server": "23.48.x.x", "server_port": 443 },
  "private_key": "<REALITY_PRIVATE_KEY>",
  "short_id": ["<SHORT_ID>"]
}

并用 dns.final: local + 本地解析规则,避免出站 DNS 又解析到 IPv6。

坑 4:Reality 公钥必须用 cryptography 校验,别手写 X25519

现象/教训:曾一度从服务器私钥手算 public_key 填入客户端,结果连不上,误以为是「公钥写错 / GFW 拦」。

真相:手写 X25519 实现有 bug(没过 RFC 7748 测试向量),算出的公钥是错的——是误判,不是真问题。最终用官方 cryptography 库反推,证明配置里的公钥本来就是对的

正确校验

import base64
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey

priv_raw = base64.urlsafe_b64decode("<REALITY_PRIVATE_KEY>" + "==")
priv = X25519PrivateKey.from_private_bytes(priv_raw)
pub = priv.public_key().public_bytes_raw()
print(base64.urlsafe_b64encode(pub).rstrip(b"=").decode())
# 与客户端配置里的 public-key 比对,必须完全一致

[!warning] 手写密码学不可信 任何自己实现的 X25519 / ECDH 都先用 RFC 7748 测试向量验证过再上,否则宁可装 cryptography 库。

坑 5:Cloudflare SSL 模式必须 Full,不是 Flexible

现象:CF 前置的 WS/TLS 节点连不上,或源站日志出现 CF 以明文连 80(SS 端口)的失败。

根因Flexible 模式下 CF 回源用明文 HTTP 连源站 80(那里是 Shadowsocks,不是 Web),握手直接失败。Full 模式才会用 TLS 连源站 443(自签证书),且非 strict 所以自签可用。

修复:CF 控制台 SSL/TLS → 设为 Full(不是 Full(strict),除非你有 CA 签名证书)。

此设置对整个域名生效,会影响该域名下所有绑定——改之前确认没有其他服务依赖 Flexible。

坑 6:Cloudflare 522 = 连不上源站

522 是 CF 侧错误,意思是「CF 无法连到你的源站」。排错顺序:

  1. OCI 安全列表是否放行 443(见坑 1)。
  2. 源站 sing-box 的 443 是否在监听(ss -tlnp)。
  3. 域名 A 记录是否指向正确 VPS IP、且开启了橙色云朵(代理)。

坑 7:裸 WebSocket 升级返回 403 是正常假象

用裸 websocket-client 或 curl 直接对 :443/vlessws 发 WS 升级,源站会回 403——因为请求里没有合法的 VLESS 帧。这不是配置错误,别据此判断「WS 不通」。真正验证请用完整 VLESS 客户端。

坑 8:别把国内 TCP “open” 当连通证据

国内网络的端口探测常常拿到 GFW 伪造的 SYN-ACK(TcpTestSucceeded: True / socket OPEN),但真实应用层握手过不去。判定代理是否可用,只能用完整客户端做应用层请求(如 curl -x 代理访问 http 站点看 HTTP=200)或看服务器 inbound connection 日志。

一锤定音的排查套路

  1. 服务器本机 127.0.x.x 自测 → 配置 OK 与否。
  2. 服务器 journalctl -u sing-box有没有客户端 IP 的入站记录 → 区分「包没到」vs「握手失败」。
  3. 客户端完整代理请求 + 客户端日志(EOF / timeout / invalid address)→ 定位链路 or 配置。
  4. 永远用 cryptography 校验 Reality 密钥对。
创建于 2026/7/20 更新于 2026/7/20