适合已经导入订阅、想进一步阅读 config.json 的 Mac 用户。先用一份仅含本机入站、直连与阻断的完整配置建立结构概念,再逐项检查端口、出站标签、规则顺序与 DNS。示例端口为明确指定的教学值,不代表所有客户端的默认设置。
config.json 与 v2rayN 设置的边界
config.json 是内核读取的配置文件,v2rayN 是管理节点、订阅和运行状态的桌面客户端。两者不是同一层:客户端可以把界面设置转换成内核运行配置,也可以管理自定义配置。看到一个名为 config.json 的文件,不能仅凭文件名认定它就是当前进程正在读取的文件。
本文采用 V2Ray 传统 JSON 配置结构。Xray 与 V2Ray 有相近的字段,但协议支持和具体参数并不完全相同。阅读配置前,应从客户端运行日志确认实际启动的内核、配置路径与启动错误;不要把适用于某一内核的协议参数直接粘贴到另一内核。
这条链路也说明了系统代理的作用:它让遵循系统代理设置的应用把请求送到本地入站。单独启动内核,不会让所有应用自动进入这条链路;不读取系统代理的应用,需要自身支持的代理选项或其他明确的接管方式。
inbounds:接收请求,描述监听地址、端口与本地代理协议。outbounds:处理请求,描述直连、代理服务器或阻断方式。routing:选择出站,把匹配到的请求交给指定标签。log、dns:分别控制日志与内核 DNS 行为,不代替前三项的职责。
完整示例:两个入站、两个出站
下面是一份用于本机理解结构的配置。它提供 SOCKS 与 HTTP 两个入口,默认直连,并阻断示例域名 example.com 及其子域名。它没有配置远程代理服务器,因此不能把它当作订阅节点配置。开始测试前,先确认这两个端口没有被正在运行的客户端占用。
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": false
}
},
{
"tag": "local-http",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": [
"domain:example.com"
],
"outboundTag": "blocked"
},
{
"type": "field",
"ip": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"outboundTag": "direct"
}
]
}
}
从最外层看,入站、出站和规则都是数组。方括号内可以放多个对象;对象之间用逗号分隔,最后一项后面不加逗号。为兼容常规 JSON 工具,保存时使用双引号,不插入注释,也不要把端口写成带引号的字符串。
inbounds:监听地址、端口与入站标签
listen 决定接收连接的网络接口。示例使用 127.0.0.1,只有本机能连接。把它改成 0.0.0.0 会监听所有 IPv4 接口,但不等于其他设备一定能访问,还要考虑防火墙、网络隔离和认证。仅在本机使用时,保留回环监听更容易控制访问范围。
SOCKS 入站
- 标签
local-socks- 监听
127.0.0.1:10808- 协议
socks- 认证
noauth,仅本机使用- UDP
false,本例关闭
应用需选择 SOCKS 代理;不能把这个端口当成 HTTP 代理端口填写。
HTTP 入站
- 标签
local-http- 监听
127.0.0.1:10809- 协议
http- 参数
settings: {}
可接收 HTTP 代理请求及 CONNECT 隧道请求;入口本身不等于 TLS 加密监听。
在 macOS 中,可进入「系统设置」→「网络」→当前网络连接→「详细信息」→「代理」核对地址与端口。采用本例 HTTP 入站时,网页代理与安全网页代理可按需要指向 127.0.0.1:10809;采用 SOCKS 入站时,对应端口是 10808。排查期间只启用需要的一种方式,避免多种设置交叉影响。
tag 是内核配置内部的标识,不是服务器地址,也不是任意应用都能识别的名称。路由中的 inboundTag 可以用它区分请求来源。例如一条规则只处理 local-http,另一条规则处理 local-socks,就能按入口分别制定策略。
outbounds:协议决定行为,tag 连接路由
示例中的 freedom 是直连出站,由当前设备直接访问目标;blackhole 用于阻断请求。真正的远程代理出站还需要协议对应的服务器地址、端口与身份参数,以及可能存在的传输层设置。仅把 tag 改成 proxy,不会把直连变成代理。
| 字段或协议 | 含义 | 检查重点 |
|---|---|---|
tag | 出站的内部名称 | 与规则的 outboundTag 完全一致 |
protocol | 选择出站实现 | 确认运行内核支持该协议 |
settings | 该协议的专属参数 | 不同协议不能套用同一结构 |
streamSettings | 传输与传输安全设置 | 与服务端要求逐项对应 |
freedom | 直接连接目标 | 使用本机的实际网络出口 |
blackhole | 阻断匹配请求 | 确认没有误匹配正常业务 |
在常规配置中,没有命中路由规则的请求会使用 outbounds 数组的第一个出站。本例将 direct 放在第一项,所以未命中请求直连。阅读客户端生成文件时,这个顺序尤其重要:文件里存在一个代理出站,并不说明未匹配流量必然使用它。
结论:先确认默认出站,再检查局部规则
排查“为什么仍然直连”时,先看 outbounds 第一项,再核对命中规则的 outboundTag。不要根据节点名称或界面颜色推断实际出口。
routing:首条匹配、条件组合与域名策略
routing.rules 按从上到下的顺序检查,命中一条就采用该条指定的出站,不再继续向下寻找更精确的规则。因此,明确的例外通常放在宽泛规则之前。数组顺序是配置行为的一部分,调整顺序可能在没有语法错误的情况下改变出口。
- 请求目标为
www.example.com:第一条的domain:example.com匹配主域名及其子域名,交给blocked。 - 请求目标为
192.168.1.20:第一条不匹配,第二条匹配私网地址段,交给direct。 - 目标不符合上述条件:没有规则命中,使用第一个出站
direct。
同一条规则中,不同条件字段通常需要同时满足;同一字段的多个候选值则按任一匹配处理。例如,下面这条规则只阻断从 HTTP 入站进入、目标端口为 25 的 TCP 请求。SOCKS 入站上的同端口请求不符合它的全部条件。
{
"type": "field",
"inboundTag": ["local-http"],
"network": "tcp",
"port": "25",
"outboundTag": "blocked"
}
这段对象应放进已有的 routing.rules 数组,并按需要安排位置;它不是完整配置。注意字段类型也有差别:入站监听的 port 是数字,而路由规则的 port 使用字符串表达端口或范围,例如 "80,443"。
AsIs:路由阶段不因 IP 规则主动解析域名;若请求目标本来就是 IP,仍可匹配 IP 规则。IPIfNonMatch:域名请求在现有条件下未匹配时,尝试解析地址,再进行路由匹配。IPOnDemand:匹配过程中遇到需要 IP 的规则时,按需解析域名。
使用 geosite: 或 geoip: 类条件时,还要确认内核能读取相应的数据文件,且条目名称存在。本文使用明确的域名与 CIDR 网段,便于把规则行为与外部数据依赖分开观察。不要把 routing.domainStrategy 与直连出站中同名或相近的 DNS 策略选项混为一谈。
DNS 与 sniffing:规则能看到什么目标
域名规则能否命中,取决于入站收到的目标信息。HTTP 代理请求与 CONNECT 通常携带域名;SOCKS 请求既可以传域名,也可以传应用已经解析好的 IP。如果应用先在本地解析,内核收到的可能只有地址,不能假设它一定知道原来的域名。
curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.com/
curl --proxy http://127.0.0.1:10809 --connect-timeout 5 https://example.com/
第一条命令的 socks5h 让 curl 把域名交给 SOCKS 代理处理;若改用 socks5,curl 会在本地解析目标。本例预期上述域名请求被阻断,因此命令失败是教学规则的预期结果,具体错误提示取决于连接阶段。这里的 5 是连接超时秒数,不是性能测量结果。
dns对象配置的是内核 DNS 功能,不会自动改写 macOS 的系统 DNS 设置。sniffing可以从部分流量中识别 HTTP Host 或 TLS 服务器名称等信息,但不是所有连接都能恢复域名。- 是否开启嗅探、是否覆盖目标,以及应用是否先解析域名,都会影响最终匹配结果。
- 排查时先固定代理类型与测试命令,再一次只改变一个变量,便于对照日志。
配置修改顺序与常见错误定位
先备份,再确定正在生效的文件,最后修改。v2rayN 可能在切换节点、更新订阅或重启内核时重新生成运行配置。临时修改生成文件适合验证问题,不适合长期保存规则;需要保留的变更应回到客户端对应设置或受管理的自定义配置中。
- 确认内核与路径。查看启动日志里的内核名称、加载文件和错误行。路径包含空格时,终端命令中的文件路径要使用引号。
- 检查 JSON 结构。在 macOS 终端执行下方命令。它只解析 JSON,不验证内核是否支持每个字段。
- 检查监听端口。确认
10808、10809未被其他进程占用,并与应用设置对应。 - 核对标签引用。逐项检查
outboundTag是否指向已有出站,inboundTag是否与入站标签一致。 - 重载并验证。从客户端重启或重载内核,确认启动成功,再分别测试已知命中与已知不命中的目标。
plutil -lint -s -r -- config.json
lsof -nP -iTCP:10808 -sTCP:LISTEN
lsof -nP -iTCP:10809 -sTCP:LISTEN
plutil 在这里用于 JSON 语法检查;即使语法通过,协议拼写错误、缺少必要参数或内核不兼容仍会导致启动失败。若监听检查没有输出,也不能单独认定配置语法错误,还要查看进程是否启动、是否读取了另一份文件。
| 现象 | 优先检查 | 下一步动作 |
|---|---|---|
| 内核启动即退出 | JSON、字段兼容性、必填参数 | 读取启动日志的第一条有效错误 |
| 提示地址已占用 | 入站端口及重复进程 | 定位监听者,再决定停用或改端口 |
| 规则没有命中 | 规则顺序、目标形式、入站标签 | 用固定代理命令复现并对照日志 |
| 改动重启后消失 | 是否修改了生成文件 | 将设置迁回客户端管理入口 |
| 浏览器可用,其他应用失败 | 应用是否遵循系统代理 | 检查应用自身代理类型与端口 |
结论:一次只修改一个层级
先验证入站能接收请求,再确认出站可用,最后调整路由。短时提高日志详细程度有助于排查,结束后恢复原级别;分享日志和配置前移除订阅地址、身份凭据及私人目标信息。