前言
本文介绍如何在 macOS 下使用 sing-box 替代 wireguard-tools + Xray + Proxifier 的组合方案,实现全局代理、多 VPN 网络共存与开机自启。
旧方案存在几个痛点:
- 工具分散:需要 Xray(代理)+ Proxifier(全局代理)+ wireguard-tools(多 VPN)三个工具配合
- 手动启动:每次开机需要分别启动 Proxifier,并手动执行
sudo wg-quick up加入各个 VPN 网络 - 虚拟机不兼容:Proxifier 基于 NetworkExtension 实现,无法将代理共享给基于 NAT 的 macOS 虚拟机(比如基于 Tart 的虚拟机)
- 官方 WireGuard 限制:Mac App Store 的 WireGuard 客户端不支持同时连接多个 VPN 网络,所以 userspace wireguard 暂时是唯一解
sing-box 通过系统路由将流量送入 TUN,再按自身规则选择代理、直连或 WireGuard 出口:
- 一个工具即可同时实现全局代理并实现多 WireGuard VPN 兼容
- 基于 NAT 的虚拟机可自动继承宿主机的代理和 VPN 配置
- 可通过
brew services实现开机自启,无需手动介入
环境说明
本文的 DNS 和路由策略以使用者位于亚洲地区为前提,其他地区请酌情调整 DNS 服务器和分流规则。
网段规划
我需要同时接入两个 VPN 网络:
- 家庭网络:192.168.188.0/24
- 公司网络:172.31.0.0/16
DNS 解析策略
- 家庭域名:
.jinmiaoluo.com由家庭 DNS192.168.188.140解析;没有内网记录时,由该 DNS 查询公网记录 - 公司域名:示例假定
.company.com可通过公共 DNS 解析到172.31.0.0/16中的地址,访问服务仍需 VPN; 若记录仅在公司 DNS 中存在,应配置对应的 DNS 服务器并通过company出口访问 - 其他域名:国内域名返回真实地址,其余 A/AAAA 查询使用 FakeIP
- 默认 DNS:
dns.final显式设为proxy-dns,处理未命中规则的查询,例如其他域名的 TXT、MX 记录
内网域名规则必须放在 FakeIP 规则之前,让后续 VPN 网段规则能够匹配真实目标 IP。
SSH 安全策略
所有公网服务器的 SSH 登录请求,默认走代理服务器,从而提供唯一的出口 IP。 所有公网暴露 SSH 服务的服务器,通过防火墙限制 SSH 端口只有代理服务器的出口 IP 才能访问。 好处如下:
- 隐藏 SSH 服务:SSH 端口对互联网不可见,端口扫描无法发现
- 消除暴力破解风险:攻击者无法尝试密码或密钥爆破
- 提高安全水平:当 OpenSSH 出现安全风险时,攻击会被网络层阻断
下面的 sing-box 配置基于以上策略。
操作
- 通过 brew 安装 sing-box
brew install sing-box
- 移除了敏感性的 sing-box 配置如下。参考下面的配置添加你的配置即可。
代理服务器域名由可直连的 china-dns 解析,避免解析请求依赖代理自身。
{
"log": {
"level": "error",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "local-dns",
"type": "udp",
"server": "192.168.188.140",
"server_port": 53,
"detour": "home"
},
{
"tag": "china-dns",
"type": "https",
"server": "223.5.5.5",
"server_port": 443,
"tls": {
"server_name": "dns.alidns.com"
}
},
{
"tag": "proxy-dns",
"type": "https",
"server": "1.1.1.1",
"detour": "proxy"
},
{
"tag": "fakeip-dns",
"type": "fakeip",
"inet4_range": "198.18.0.0/15"
}
],
"rules": [
{
"domain_suffix": [".jinmiaoluo.com"],
"action": "route",
"server": "local-dns"
},
{
"domain_suffix": [".company.com"],
"action": "route",
"server": "china-dns"
},
{
"domain_suffix": [".bitwarden.com"],
"action": "route",
"server": "proxy-dns"
},
{
"rule_set": "geosite-cn",
"action": "route",
"server": "china-dns"
},
{
"query_type": ["A", "AAAA"],
"action": "route",
"server": "fakeip-dns"
}
],
"final": "proxy-dns",
"strategy": "ipv4_only"
},
"endpoints": [
{
"tag": "home",
"type": "wireguard",
"private_key": "xxx",
"address": [
"10.66.153.175/16"
],
"mtu": 1280,
"peers": [
{
"public_key": "xxx",
"pre_shared_key": "xxx",
"address": "xxx",
"port": 51820,
"allowed_ips": [
"10.66.0.0/16",
"192.168.188.0/24"
],
"persistent_keepalive_interval": 15
}
]
},
{
"tag": "company",
"type": "wireguard",
"private_key": "xxx",
"address": ["10.99.128.68/16"],
"mtu": 1280,
"peers": [
{
"public_key": "xxx",
"pre_shared_key": "xxx",
"address": "xxx",
"port": 51820,
"allowed_ips": [
"10.99.0.0/16",
"172.31.0.0/16"
],
"persistent_keepalive_interval": 15
}
]
}
],
"inbounds": [
{
"tag": "tun-in",
"type": "tun",
"address": [
"172.19.0.1/30"
],
"mtu": 9000,
"auto_route": true,
"strict_route": true
}
],
"outbounds": [
{
"tag": "proxy",
"type": "vless",
"server": "xxx",
"server_port": 443,
"domain_resolver": "china-dns",
"uuid": "xxx",
"flow": "xtls-rprx-vision",
"tls": {
"enabled": true,
"server_name": "proxy.example.com",
"alpn": ["h2", "http/1.1"],
"utls": {
"enabled": true,
"fingerprint": "chrome"
}
}
},
{
"tag": "direct",
"type": "direct"
}
],
"route": {
"auto_detect_interface": true,
"default_domain_resolver": "proxy-dns",
"rules": [
{
"inbound": "tun-in",
"action": "sniff",
"timeout": "1s"
},
{
"type": "logical",
"mode": "or",
"rules": [
{ "protocol": "dns" },
{ "port": 53 }
],
"action": "hijack-dns"
},
{
"rule_set": "geosite-category-ads-all",
"action": "reject"
},
{
"ip_cidr": [
"10.66.0.0/16",
"192.168.188.0/24"
],
"outbound": "home"
},
{
"ip_cidr": [
"10.99.0.0/16",
"172.31.0.0/16"
],
"outbound": "company"
},
{
"port": 123,
"network": ["udp"],
"outbound": "direct"
},
{
"type": "logical",
"mode": "or",
"rules": [
{ "port": 853 },
{ "protocol": "stun" }
],
"action": "reject"
},
{
"rule_set": [
"geosite-google"
],
"outbound": "proxy"
},
{
"type": "logical",
"mode": "and",
"rules": [
{ "port": 22 },
{ "ip_is_private": true, "invert": true }
],
"action": "route",
"outbound": "proxy"
},
{
"rule_set": ["geosite-cn", "geosite-private"],
"outbound": "direct"
},
{
"rule_set": "geoip-cn",
"outbound": "direct"
},
{
"domain_suffix": [
".jinmiaoluo.com",
".company.com"
],
"outbound": "direct"
},
{
"ip_is_private": true,
"outbound": "direct"
}
],
"rule_set": [
{
"tag": "geosite-cn",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
"download_detour": "proxy"
},
{
"tag": "geosite-private",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-private.srs",
"download_detour": "proxy"
},
{
"tag": "geosite-google",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-google.srs",
"download_detour": "proxy"
},
{
"tag": "geosite-category-ads-all",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ads-all.srs",
"download_detour": "proxy"
},
{
"tag": "geoip-cn",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
"download_detour": "proxy"
}
],
"final": "proxy"
},
"experimental": {
"cache_file": {
"enabled": true,
"path": "cache.db",
"store_fakeip": true
}
}
}
- 创建目录并将替换后的 JSON 保存为配置文件
mkdir -p /opt/homebrew/var/lib/sing-box
tee /opt/homebrew/etc/sing-box/config.json > /dev/null << 'EOF'
# 将上面的 JSON 配置粘贴到这里
EOF
- (可选)调试配置
在启动服务前,可以通过手动运行 sing-box 来验证配置是否正确:
sudo /opt/homebrew/opt/sing-box/bin/sing-box run \
--config /opt/homebrew/etc/sing-box/config.json \
--directory /opt/homebrew/var/lib/sing-box
手动运行时日志会直接输出到终端,便于排查问题。如需更详细的日志,可以将配置中的 log.level 从 error 改为 debug。
- 启动服务
sudo brew services start sing-box
启动后即可完成全局代理 + 多 VPN 网络的配置。后续开机会自动启动。
补充说明
公网 SSH 的反向匹配
ip_is_private: false 会关闭该条件,并不表示匹配公网。使用 logical + and 组合端口 22 与私网条件,
仅在私网子规则中设置 invert: true,
表达 port == 22 && !ip_is_private。
关于 sniff
配置中通过 route 规则 "action": "sniff" 对 tun-in 入站流量启用协议嗅探,从 TLS SNI 或 HTTP Host 中提取真实域名,用于后续路由规则匹配。
"timeout": "1s" 指定嗅探超时时间,超时后放弃嗅探、继续路由。
sniff 相关配置在 sing-box 1.11.0 版本发生了重大变更(旧字段在 1.13.0 移除,详见 Migration):
inbound.sniff已废弃,迁移到 route 规则的"action": "sniff"中inbound.sniff_override_destination已废弃且被移除,没有等效替代。 开发者认为"用嗅探到的域名替换目标 IP"是从 V2Ray 继承的错误设计(参考)。 新版 sniff 只提取域名用于路由匹配,不会替换目标 IP。这正好符合我们的需求——内网域名通过 local-dns 解析到内网 IP 后,目标地址始终保持为该内网 IP
系统路由与 VPN 分流
系统路由决定流量是否进入 TUN;进入后,route.rules 按顺序选择出口。ip_cidr 本身不会向系统安装对应的网段路由。
家庭网段使用 192.168.188.0/24,与 WireGuard allowed_ips 一致。回家后是否直连取决于系统路由;
local-dns 指定了 detour: home,其上游查询仍走家庭 WireGuard。
Vaultwarden 推送与 FakeIP
若 Vaultwarden 服务端的 DNS 查询经过该 sing-box,FakeIP 可能触发其
非公网地址检查,导致推送请求失败。
配置在 FakeIP 规则之前将 .bitwarden.com 交给 proxy-dns,返回真实地址,同时保留 Vaultwarden 的安全检查。
关于移动热点场景下的 DNS 配置
当 Mac 通过 iPhone 热点上网时,系统会使用 iPhone 分配的 DNS。如果查询未进入 sing-box,可能出现以下问题:
- 查询
nas.jinmiaoluo.com - 运营商 DNS 不知道内网记录,返回公网 IP(或 NXDOMAIN)
- 内网 DNS 服务器(192.168.188.140)的记录无法生效
虽然 sing-box 配置了 hijack-dns 规则来劫持 DNS 流量,但 macOS 在某些场景下可能绕过 TUN 接口直接查询系统 DNS。
解决方案:手动将系统 DNS 指向 TUN 接口地址,让使用系统 DNS 的应用通过 sing-box 解析。应用内置的 DoH 需单独配置。
操作路径:系统设置 > Wi-Fi > 点击已连接网络旁的 详细信息 > DNS > 添加 172.19.0.1
配置后,DNS 查询流程变为:
- 应用发起 DNS 查询,目标地址 172.19.0.1
- 流量进入 TUN 接口,被
hijack-dns规则拦截 - sing-box DNS 模块按规则分流:
.jinmiaoluo.com走内网 DNS,其他按规则处理 - 内网 DNS 请求通过 WireGuard VPN 发出,返回正确的内网 IP
停用 sing-box 前,恢复原 DNS 设置,避免继续向不可用的 172.19.0.1 查询。
关于拦截 DoT 和 STUN
配置中有一条路由规则拦截了两类流量:
{
"type": "logical",
"mode": "or",
"rules": [
{ "port": 853 },
{ "protocol": "stun" }
],
"action": "reject"
}
为什么要拦截这两类流量:
- 端口 853:拦截使用该端口的 DoT/DoQ,不覆盖通常使用 443 的 DoH;是否回退到普通 DNS 取决于客户端设置
- STUN 协议(WebRTC):阻止经过该规则且被识别的 STUN 请求发现公网地址,但可能影响通话或连接建立,不能保证阻止所有 WebRTC 地址泄露
IPv6 仅本地链接
IPv6 代理需要服务器支持;当前方案仅配置 IPv4,暂不支持 IPv6。ipv4_only 不会更改系统 IPv6 设置。
打开 系统设置 > 网络,选择当前使用的网络服务,进入 详细信息 > TCP/IP。记录原设置,将 配置 IPv6 改为 仅本地链接(Link-local only),点按 好。
这会保留链路本地 IPv6 通信;需要恢复时,改回原设置。
总结
这是目前我在 macOS(26.2) 上能找到的比较完美的方案。同时解决下面的问题。
- 全局代理:所有流量经过 TUN 接口,无需为每个应用单独配置代理
- 多 VPN 网络共存:同时连接家庭和公司的 WireGuard VPN,按目标 IP 自动分流,并解决了路由冲突的问题
- 完全自动化:配置完成后通过
brew services开机自启,日常使用无需任何手动操作
2026 年 1 月 25 日深夜,于北京路 1200bookshop。