Oracle VPS 代理部署的坑
在 Oracle Cloud 免费实例上部署 sing-box 代理时反复踩过的真实坑:OCI 安全列表只放 22、GFW 链路层黑洞、无公网 IPv6 导致 handshake dial 失败、公钥须用 cryptography 校验、Cloudflare SSL 必须 Full 等。
[!info] related notes
- 部署流程:在 Oracle VPS 上部署 sing-box 代理
- 节点优选:Cloudflare 优选 IP
- 背景:甲骨文免费服务器回收问题调研
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 无法连到你的源站」。排错顺序:
- OCI 安全列表是否放行 443(见坑 1)。
- 源站 sing-box 的 443 是否在监听(
ss -tlnp)。 - 域名 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 日志。
一锤定音的排查套路
- 服务器本机
127.0.x.x自测 → 配置 OK 与否。 - 服务器
journalctl -u sing-box看有没有客户端 IP 的入站记录 → 区分「包没到」vs「握手失败」。 - 客户端完整代理请求 + 客户端日志(
EOF/timeout/invalid address)→ 定位链路 or 配置。 - 永远用
cryptography校验 Reality 密钥对。