<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Jam a Man of Fortune</title><description>mmx</description><link>https://blog.cobweb11.top/</link><language>zh_CN</language><item><title>VPS 上的分流：境内外判断交给 IP，域名只用来选解析器</title><link>https://blog.cobweb11.top/posts/geo-routing-on-overseas-vps/</link><guid isPermaLink="true">https://blog.cobweb11.top/posts/geo-routing-on-overseas-vps/</guid><description>geosite 的 cn 表是为客户端建的，把它搬到服务端做境内外出站判定，误判是普遍存在的。</description><pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;环境与说明&lt;/h2&gt;
&lt;p&gt;Xray-core v26.6.1，配置里的 &lt;code&gt;warp&lt;/code&gt; 出站是保守侧的中转，它可以是 Cloudflare WARP 或其他出站，不重要。&lt;/p&gt;
&lt;p&gt;文中的域名数据来自 &lt;a href=&quot;https://github.com/Loyalsoldier/v2ray-rules-dat&quot;&gt;@Loyalsoldier/v2ray-rules-dat&lt;/a&gt; 的 &lt;code&gt;geosite.dat&lt;/code&gt;，构建日期 2026-08-30。换个上游或者换个构建时间，具体条目会有出入。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Loyalsoldier/v2ray-rules-dat&quot;}&lt;/p&gt;
&lt;p&gt;下面假设读者自己写过分流规则，知道域名规则和 IP 规则各自什么时候生效。示例是 Xray 的服务端配置，不过整套思路和内核无关，sing-box 和 mihomo 一样能落地，客户端也适用，只是方向要反过来。&lt;/p&gt;
&lt;h2&gt;geosite 的 cn 里有什么&lt;/h2&gt;
&lt;p&gt;Xray 官方的教程对这张表的描述是 &lt;a href=&quot;https://xtls.github.io/document/level-1/routing-with-dns.html&quot;&gt;「大杂烩，只要沾点中国关系的都往里丢」&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;把两张表拉出来对一遍，能看到不少域名同时命中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CN                full:www.gstatic.com
CN                full:www.apple.com
CN                full:init.itunes.apple.com
CN                full:ocsp.apple.com
CN                full:dl.google.com
CN                full:fonts.gstatic.com
CN                full:c.pki.goog

GEOLOCATION-!CN   domain:gstatic.com
GEOLOCATION-!CN   domain:www.apple.com
GEOLOCATION-!CN   domain:init.itunes.apple.com
GEOLOCATION-!CN   domain:ocsp.apple.com
GEOLOCATION-!CN   domain:dl.google.com
GEOLOCATION-!CN   domain:fonts.gstatic.com
GEOLOCATION-!CN   domain:c.pki.goog
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ocsp.globalsign.com&lt;/code&gt; 的情况稍有不同，它只命中 &lt;code&gt;cn&lt;/code&gt; 里的 &lt;code&gt;domain:globalsign.com&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;条目这样写是存在合理性的。对于一个在北京的客户端，把 &lt;code&gt;fonts.gstatic.com&lt;/code&gt; 当境内域名是合理的，Google Fonts（谷歌字体）服务确实有境内节点，直连比走代理快得多。&lt;/p&gt;
&lt;p&gt;那它什么时候就不适用了呢？把同一份表搬到海外的服务端，「做路由决策的机器在境内」这个前提就没了，这些域名被归进 &lt;code&gt;geosite:cn&lt;/code&gt; 也就从优势变成了误判。&lt;/p&gt;
&lt;p&gt;这种归类也不是没人质疑，&lt;a href=&quot;https://github.com/Loyalsoldier/v2ray-rules-dat/pull/328&quot;&gt;PR#328&lt;/a&gt; 的提议方觉得这类域名该单独成表，理由是客户端直连它们有 IP「送中」的风险，维护者不认可。&lt;/p&gt;
&lt;h2&gt;路由：境内外交给 IP 判断&lt;/h2&gt;
&lt;p&gt;平时容易混在一起的，其实是不同层面的几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这条连接的目的 IP 落在哪一侧？只取决于地址本身。&lt;/li&gt;
&lt;li&gt;这个域名由哪一侧的节点提供服务？取决于从哪里问，以及权威 DNS 怎么回。&lt;/li&gt;
&lt;li&gt;这个域名属于谁，会不会被干扰？这是策略问题，和地理位置相关但不等价。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;geosite:cn&lt;/code&gt; 回答的是第二个，而且前提是提问的人在境内。&lt;/p&gt;
&lt;p&gt;在一台不希望代理回国流量的服务器上，更应当使用 ip 进行境内外的判断。服务器用自己的 IP 同时代理出国和回国流量，出入境时两条方向相反的流量在时间上可以被关联起来，且可以被识别（这件事社区讨论过很多，也有&lt;a href=&quot;https://github.com/net4people/bbs/issues/129#issuecomment-1308102504&quot;&gt;实测&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;那么连接国内公司在海外的边缘节点会被关联与识别吗？ 对于跨境链路上的观测方，答案是不会，&lt;strong&gt;但是站在服务提供方那一侧要&lt;/strong&gt; &lt;a href=&quot;#%E8%AE%BE-ecs-%E9%98%B2%E7%9A%84%E6%98%AF%E5%93%AA%E4%B8%80%E7%A7%8D%E6%9A%B4%E9%9C%B2&quot;&gt;&lt;strong&gt;另算&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;比换列表更省事的一步，是换成不依赖社区维护的判据，例如在客户端分流的场景下，对于微信和 QQ 这类程序，与其枚举腾讯的域名段（会滞后，而且它们的 CDN 域名还和别人共用），不如直接写进程规则。&lt;/p&gt;
&lt;p&gt;对应的配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;routing&quot;: {
  &quot;domainStrategy&quot;: &quot;IPIfNonMatch&quot;,   // 域名规则不命中时解析成 IP 再匹配，IP 判据靠它生效
  &quot;rules&quot;: [
    // api 必须在 geoip:private 之前，否则 API 自己的连接（目的是 127.0.0.1）会被拦掉
    { &quot;type&quot;: &quot;field&quot;, &quot;inboundTag&quot;: [&quot;api&quot;], &quot;outboundTag&quot;: &quot;api&quot; },
    { &quot;type&quot;: &quot;field&quot;, &quot;inboundTag&quot;: [&quot;dns-remote&quot;, &quot;dns-remote-backup&quot;], &quot;outboundTag&quot;: &quot;direct&quot; },
    { &quot;type&quot;: &quot;field&quot;, &quot;ip&quot;: [&quot;geoip:private&quot;], &quot;outboundTag&quot;: &quot;blocked&quot; },
    { &quot;type&quot;: &quot;field&quot;, &quot;protocol&quot;: [&quot;bittorrent&quot;], &quot;outboundTag&quot;: &quot;blocked&quot; },  // 需要 inbound 开 sniffing
    { &quot;type&quot;: &quot;field&quot;, &quot;ip&quot;: [&quot;geoip:cn&quot;], &quot;outboundTag&quot;: &quot;warp&quot; }
    // 到这里结束，一条指向 direct 的例外都没有，因为 direct 就是默认出站
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;境内解析器的查询请求会命中最后一条规则走 &lt;code&gt;warp&lt;/code&gt; 出站，用不着也不应该为它单独写一条直连。&lt;/p&gt;
&lt;h2&gt;内置 DNS 服务器&lt;/h2&gt;
&lt;h3&gt;域名表只用来选解析器&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;expectedIPs&lt;/code&gt; 对返回的 IP 的进行校验，只保留包含其列表中的地址；&lt;code&gt;unexpectedIPs&lt;/code&gt; 反之。&lt;/p&gt;
&lt;p&gt;Xray 构建解析器列表的顺序是命中 &lt;code&gt;domains&lt;/code&gt; 条件的解析器按配置顺序排在前面，其余没标 &lt;code&gt;skipFallback&lt;/code&gt; 的按配置顺序追加，核心将依次使用最终服务器列表中的服务器进行查询，并使用 &lt;code&gt;expectedIPs&lt;/code&gt; 和 &lt;code&gt;unexpectedIPs&lt;/code&gt; 过滤结果，为空则尝试列表中的下一个，第一个产出非空结果的解析器终止查询。&lt;/p&gt;
&lt;p&gt;设置 &lt;code&gt;expectedIPs: [&quot;geoip:!cn&quot;]&lt;/code&gt; 保留境外解析器答出的境外 IP，为空则使用列表中下一个解析器再次查询。境内解析器无过滤，并用 &lt;code&gt;finalQuery&lt;/code&gt; 终止列表构建。&lt;/p&gt;
&lt;p&gt;:::warning[&lt;code&gt;finalQuery&lt;/code&gt; 和 &lt;code&gt;expectedIPs&lt;/code&gt; 不要一起开]
Xray 的 &lt;code&gt;finalQuery&lt;/code&gt; 为真的时候后面的解析器不再参与，而过滤到空恰恰要靠后面的解析器来兜，两个一起开，一次过滤失败就等于解析失败。&lt;/p&gt;
&lt;p&gt;如果希望请求不至于失败，&lt;code&gt;expectedIPs&lt;/code&gt; 里可以放一个 &lt;code&gt;*&lt;/code&gt; 条目，过滤后为空时保留原始结果。
:::&lt;/p&gt;
&lt;p&gt;:::caution[&lt;code&gt;enableParallelQuery&lt;/code&gt; 会破坏这里依赖的顺序语义]
这套做法整个建立在顺序语义上。&lt;code&gt;enableParallelQuery:&quot;true&quot;&lt;/code&gt; 将启用分组并发，相邻且 &lt;code&gt;clientIP&lt;/code&gt;、&lt;code&gt;skipFallback&lt;/code&gt;、&lt;code&gt;queryStrategy&lt;/code&gt;、&lt;code&gt;tag&lt;/code&gt;、&lt;code&gt;domains&lt;/code&gt;、过滤条件全都相同的服务器合成一组，组内谁先成功就用谁的地址，组间按顺序回落。
:::&lt;/p&gt;
&lt;h3&gt;选用更合适的域名表&lt;/h3&gt;
&lt;p&gt;这份 &lt;code&gt;geosite.dat&lt;/code&gt; 里，&lt;code&gt;cn&lt;/code&gt; 有十一万条多，绝大部分来自 &lt;code&gt;china-list&lt;/code&gt;，也就是 dnsmasq-china-list 的 accelerated-domains，剩下的是 &lt;code&gt;geolocation-cn&lt;/code&gt; 和 &lt;code&gt;tld-cn&lt;/code&gt;，以及 apple.china 和 google.china 这两个 opt-in 文件。&lt;/p&gt;
&lt;p&gt;dnsmasq-china-list 本身为选择解析器而建立，Apple 和 Google 是单独的 opt-in，符合在服务端选择解析器的需求。&lt;/p&gt;
&lt;p&gt;部分域名需单独列出，比如 OCSP 域名，其是否单独处理需要取舍。Chrome 对叶证书不做在线 OCSP，实际依赖 CRLSets，Let&apos;s Encrypt 在 2025 年停止 OCSP 服务并从证书里移除了 OCSP URL；&lt;/p&gt;
&lt;p&gt;但 Windows 和 Safari 还是会查，这类客户端上它确实卡在 TLS 握手的关键路径上，一次境内解析加一跳绕行是能感觉到的。&lt;/p&gt;
&lt;h3&gt;设 ECS 防的是哪一种暴露&lt;/h3&gt;
&lt;p&gt;为了让保守规则命中，可以给境内解析器设一个境内的 EDNS Client Subnet，让国内公司的服务返回大陆地址，IP 规则就会命中，绕行也就生效了。&lt;/p&gt;
&lt;p&gt;上述域名虽然从境外解析会落到海外边缘节点，不存在回国流量，但提供服务的仍然是国内公司，例如使用代理登录微信、支付宝等行为会留下一条机房 IP 和一个国内身份的关联记录，这是存在风险的。&lt;/p&gt;
&lt;p&gt;机器直连海外边缘节点留下的是机器的 IP，而走 ECS 并分流绕行至保守侧留下的是中转方的 IP，直接缓解了上述风险。但在实际中，搭建服务端的人有这个意识，用的人未必有，分流出问题，或者干脆故意连国内服务，代价都会记在机器的 IP 上。&lt;/p&gt;
&lt;p&gt;这使得在服务端考虑这样的策略是有必要的，虽然 ban 掉所有回国流量也能解决风险，但代价是会影响用户的使用体验，&lt;a href=&quot;https://xtls.github.io/document/level-2/warp.html&quot;&gt;官方文档&lt;/a&gt; 选 WARP 而不选黑洞也是这个理由。&lt;/p&gt;
&lt;p&gt;不过要强调的是，这一套只是服务端的兜底。真正兼顾使用体验和安全的配置是在客户端用 &lt;code&gt;process&lt;/code&gt; 进程名规则或者分应用代理。&lt;/p&gt;
&lt;p&gt;对应的配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;dns&quot;: {
  &quot;queryStrategy&quot;: &quot;UseIP&quot;,
  &quot;servers&quot;: [
    // 主力解析器
    {
      &quot;address&quot;: &quot;https://1.1.1.1/dns-query&quot;,
      &quot;tag&quot;: &quot;dns-remote&quot;,
      &quot;domains&quot;: [
        &quot;geosite:google-cn&quot;,
        &quot;geosite:apple-cn&quot;,
        &quot;geosite:microsoft@cn&quot;,
        &quot;geosite:steam@cn&quot;,
        &quot;geosite:gfw&quot;,
        &quot;domain:ocsp.globalsign.com&quot;,
        &quot;domain:status.rapidssl.com&quot;,
        &quot;domain:ocsp.r2m01.amazontrust.com&quot;,
        &quot;domain:oneocsp.microsoft.com&quot;,
        &quot;domain:c.lencr.org&quot;,
        &quot;domain:c.pki.goog&quot;
      ],
      &quot;expectedIPs&quot;: [&quot;geoip:!cn&quot;],
      &quot;skipFallback&quot;: false,     // 同时作为主力/默认解析器
      &quot;finalQuery&quot;: false,
      &quot;timeoutMs&quot;: 1000
    },
    // 境内解析器
    // clientIP 是 per-server 的 EDNS Client Subnet 字段（全局的那个键叫 clientIp）
    {
      &quot;address&quot;: &quot;223.5.5.5&quot;,
      &quot;tag&quot;: &quot;dns-cn-direct&quot;,
      &quot;domains&quot;: [&quot;geosite:china-list&quot;],
      &quot;clientIP&quot;: &quot;192.0.2.1&quot;,   // 换成境内 ISP 的 IP
      &quot;skipFallback&quot;: true,      // 只服务命中的域名，不参与回落
      &quot;finalQuery&quot;: true,
      &quot;timeoutMs&quot;: 1500
    },
    // 兜底
    { &quot;address&quot;: &quot;https://8.8.8.8/dns-query&quot;, &quot;tag&quot;: &quot;dns-remote-backup&quot;, &quot;timeoutMs&quot;: 2000 }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;运行时有四种情况：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;域名&lt;/th&gt;
&lt;th&gt;最终列表&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;google-cn / apple-cn / c.lencr.org 等（&lt;strong&gt;仅&lt;/strong&gt;命中 dns-remote）&lt;/td&gt;
&lt;td&gt;[1.1.1.1 DoH, 8.8.8.8 DoH]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;仅命中 china-list&lt;/td&gt;
&lt;td&gt;[223.5.5.5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;未命中任何规则&lt;/td&gt;
&lt;td&gt;[1.1.1.1 DoH, 8.8.8.8 DoH]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;同时命中两者&lt;/td&gt;
&lt;td&gt;[1.1.1.1 DoH, 223.5.5.5]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果有 DNS 解锁服务，将 &lt;code&gt;&quot;address&quot;: &quot;https://1.1.1.1/dns-query&quot;&lt;/code&gt; 替换为服务提供商的地址即可，解锁服务故障时会自动回落 &lt;code&gt;dns-remote-backup&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;应用层判定漏洞&lt;/h2&gt;
&lt;p&gt;当 &lt;code&gt;IPIfNonMatch&lt;/code&gt; 下的一个域名解析出多个地址时，每条 IP 规则会拿全部地址去试，任一命中就算命中。例如 &lt;code&gt;www.bilibili.com&lt;/code&gt; 一次返回 20 个地址，只要其中一个落在 &lt;code&gt;geoip:cn&lt;/code&gt; 里，整条连接就走保守侧，而实际建连用的可能是另一个地址：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;22:48:55.438714 UDP:223.5.5.5:53 got answer: www.bilibili.com. -&amp;gt; [61.147.236.101, 183.131.147.48, 183.131.147.30, 183.131.147.29, 183.131.147.28, 183.131.147.27, 175.4.62.129, 175.4.62.128, 175.4.62.127, 119.84.174.97, 119.84.174.96, 119.84.174.68, 119.84.174.67, 114.230.222.141, 114.230.222.140, 114.230.222.139, 114.230.222.138, 61.147.236.104, 61.147.236.103, 61.147.236.102] 337.538933ms
22:48:55.438724 accepted tcp:www.bilibili.com:443 [inbound-443 -&amp;gt; warp]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在时序方面：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;22:49:13.697898 UDP:223.5.5.5:53 got answer: www.jd.com. -&amp;gt; [] 4.000179582s &amp;lt;app/dns: record not found&amp;gt;
22:49:14.108768 DOH//1.1.1.1 got answer: www.jd.com. -&amp;gt; [140.150.36.51, 138.113.102.14] 411.088937ms
22:49:14.108794 accepted tcp:www.jd.com:443 [inbound-443 &amp;gt;&amp;gt; direct]
22:49:14.108804 UDP:223.5.5.5:53 cache HIT: www.jd.com. -&amp;gt; [1.194.193.81]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;境内解析器用默认 4 秒超时返回空，回落至境外解析器，得到海外节点 ip ，路由据此判定直连。10 微秒后出站建连再次解析，这次命中缓存得到 &lt;code&gt;1.194.193.81&lt;/code&gt;，一个大陆地址。判定用的是海外 IP，连接用的是大陆 IP。&lt;/p&gt;
&lt;p&gt;缓存里的记录从哪来，日志没有直接记下，可能是境内解析器的应答在超时判定之后才到。&lt;/p&gt;
&lt;p&gt;出站再解析一次不是默认行为，Xray 的 freedom 出站 &lt;code&gt;domainStrategy&lt;/code&gt; 默认 &lt;code&gt;AsIs&lt;/code&gt;，此时 Xray 将域名交给 Go 按操作系统 DNS 设置解析并连接，当填写其他值时使用内置 DNS 模块进行解析，才出现第二次内置查询。&lt;/p&gt;
&lt;p&gt;只要有两个解析器可能对同一个域名给出不同答案，而其中一个不稳定，这个窗口就在。为了解决该问题，将 &lt;code&gt;dns-cn-direct&lt;/code&gt; 的 &lt;code&gt;timeoutMs&lt;/code&gt; 调整为 1500；并设置 &lt;code&gt;finalQuery&lt;/code&gt; 阻止其使用 fallback 解析器，同样的超时行为将直接作为失败的解析，以防止分流错误。&lt;/p&gt;
&lt;p&gt;仅内核判定存在局限性，可以考虑在内核之外加一道防火墙级的过滤器，监测物理网卡的出站，目的地址落在一个独立维护的 CN 地址集合里就拦截。该集合最好来源和内核不同并定时刷新，和内核的数据形成两份并集。&lt;/p&gt;
&lt;h3&gt;验证&lt;/h3&gt;
&lt;p&gt;对访问日志的分析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 域名 + 出站
sed -nE &apos;s#.*accepted tcp:([^ ]+) \[([^]]*)\].*#\2\t\1#p&apos; access.log | sort -u

# 域名 + 解析器 + IP
sed -nE &apos;s#.*(DOH//[^ ]+|UDP:[^ ]+) (got answer|cache HIT): ([^ ]+)\. -&amp;gt; \[([^]]*)\].*#\3\t\1\t\4#p&apos; access.log | sort -u
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;被路由规则命中是 &lt;code&gt;[inbound-443 -&amp;gt; warp]&lt;/code&gt;，回落默认出站为 &lt;code&gt;[inbound-443 &amp;gt;&amp;gt; direct]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;批量测试期望明确的域名：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;例子&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;外国公司的境内服务&lt;/td&gt;
&lt;td&gt;www.gstatic.com, dl.google.com, www.apple.com, ocsp.globalsign.com, c.pki.goog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;真正的境内服务&lt;/td&gt;
&lt;td&gt;www.baidu.com, www.bilibili.com, www.taobao.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.cn&lt;/code&gt; 顶级域&lt;/td&gt;
&lt;td&gt;nvidia.cn, mirrors.tuna.tsinghua.edu.cn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;境外对照组&lt;/td&gt;
&lt;td&gt;github.com, www.youtube.com, www.cloudflare.com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;while read -r d; do curl -sI --max-time 5 &quot;https://$d&quot; &amp;gt;/dev/null; done &amp;lt; domains.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测服务端行为的时候客户端要切到全局模式，防止客户端分流的干扰。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;先确认社区建这张表要解决的问题，和自己的需求是不是一回事。&lt;/li&gt;
&lt;li&gt;服务端对境内外的判定适合用 IP，选解析器才用域名表，而且要用专为选解析器而建的表。&lt;/li&gt;
&lt;li&gt;对于服务端，IP 是比通用域名分类更合适的判据，但它仍然受 DNS、CDN、多地址、缓存、时序影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样列表滞后带来的后果，就从「某天某个域名会走错，而且可能是有风险的那个方向」变成「某天某个域名会慢一点，扫一遍日志就能发现」。&lt;/p&gt;
&lt;h2&gt;附录：完整配置&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;warp&lt;/code&gt; 是保守侧的中转出站，&lt;code&gt;direct&lt;/code&gt; 是默认出站。&lt;/p&gt;
&lt;p&gt;如果要用 &lt;code&gt;warp&lt;/code&gt; 出站需要注意：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mtu&lt;/code&gt; 为 1280，Xray 的 wireguard 出站默认 1420 偏大，Cloudflare 自家客户端是 1280。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;keepAlive&lt;/code&gt; 写 25，是由于正常情况下该出站几乎空闲，握手过期后重新握手要是撞上丢包，就足以让一个 5 秒超时的探测失败。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;dns&quot;: {
    &quot;queryStrategy&quot;: &quot;UseIP&quot;,
    &quot;servers&quot;: [
      {
        &quot;address&quot;: &quot;https://1.1.1.1/dns-query&quot;,
        &quot;tag&quot;: &quot;dns-remote&quot;,
        &quot;domains&quot;: [
          &quot;geosite:google-cn&quot;,
          &quot;geosite:apple-cn&quot;,
          &quot;geosite:microsoft@cn&quot;,
          &quot;geosite:steam@cn&quot;,
          &quot;geosite:gfw&quot;,
          &quot;domain:ocsp.globalsign.com&quot;,
          &quot;domain:status.rapidssl.com&quot;,
          &quot;domain:ocsp.r2m01.amazontrust.com&quot;,
          &quot;domain:oneocsp.microsoft.com&quot;,
          &quot;domain:c.lencr.org&quot;,
          &quot;domain:c.pki.goog&quot;
        ],
        &quot;expectedIPs&quot;: [&quot;geoip:!cn&quot;],
        &quot;skipFallback&quot;: false,
        &quot;finalQuery&quot;: false,
        &quot;timeoutMs&quot;: 1000
      },
      {
        &quot;address&quot;: &quot;223.5.5.5&quot;,
        &quot;tag&quot;: &quot;dns-cn-direct&quot;,
        &quot;domains&quot;: [&quot;geosite:china-list&quot;],
        &quot;clientIP&quot;: &quot;192.0.2.1&quot;, //换成境内 ISP 的 IP
        &quot;skipFallback&quot;: true,
        &quot;finalQuery&quot;: true,
        &quot;timeoutMs&quot;: 1500
      },
      {
        &quot;address&quot;: &quot;https://8.8.8.8/dns-query&quot;,
        &quot;tag&quot;: &quot;dns-remote-backup&quot;,
        &quot;timeoutMs&quot;: 2000
      }
    ]
  },
  &quot;routing&quot;: {
    &quot;domainStrategy&quot;: &quot;IPIfNonMatch&quot;,
    &quot;rules&quot;: [
      { &quot;type&quot;: &quot;field&quot;, &quot;inboundTag&quot;: [&quot;api&quot;], &quot;outboundTag&quot;: &quot;api&quot; },
      {
        &quot;type&quot;: &quot;field&quot;,
        &quot;inboundTag&quot;: [&quot;dns-remote&quot;, &quot;dns-remote-backup&quot;],
        &quot;outboundTag&quot;: &quot;direct&quot;
      },
      { &quot;type&quot;: &quot;field&quot;, &quot;ip&quot;: [&quot;geoip:private&quot;], &quot;outboundTag&quot;: &quot;blocked&quot; },
      { &quot;type&quot;: &quot;field&quot;, &quot;protocol&quot;: [&quot;bittorrent&quot;], &quot;outboundTag&quot;: &quot;blocked&quot; },
      { &quot;type&quot;: &quot;field&quot;, &quot;ip&quot;: [&quot;geoip:cn&quot;], &quot;outboundTag&quot;: &quot;warp&quot; }
    ]
  },
  &quot;outbounds&quot;: [
    {
      &quot;protocol&quot;: &quot;freedom&quot;,
      &quot;tag&quot;: &quot;direct&quot;,
      &quot;settings&quot;: { &quot;domainStrategy&quot;: &quot;UseIPv4&quot; }
    },
    {
      &quot;protocol&quot;: &quot;wireguard&quot;,
      &quot;tag&quot;: &quot;warp&quot;,
      &quot;settings&quot;: {
        &quot;mtu&quot;: 1280,
        &quot;secretKey&quot;: &quot;...&quot;,
        &quot;peers&quot;: [{ &quot;publicKey&quot;: &quot;...&quot;, &quot;endpoint&quot;: &quot;...&quot;, &quot;keepAlive&quot;: 25 }]
      }
    },
    { &quot;protocol&quot;: &quot;blackhole&quot;, &quot;tag&quot;: &quot;blocked&quot; }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>让 Claude Code 长任务结束后通知用户</title><link>https://blog.cobweb11.top/posts/cc-inform-you/</link><guid isPermaLink="true">https://blog.cobweb11.top/posts/cc-inform-you/</guid><description>用 hooks 和 ntfy.sh 让 Claude Code 在长任务结束后向手机推送通知。</description><pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;Claude Code 跑长任务时，人往往就去做自己的事了。&lt;/p&gt;
&lt;p&gt;例如子代理编排型的任务，拆计划、并行/串行派子代理、写代码、功能审查、质量审查，任务时间在几十分钟到几小时不定，等人回来一看会话早就结束了，白白浪费时间。&lt;/p&gt;
&lt;p&gt;当任务结束时，让 Claude Code 往手机发一条通知就能很好地解决这个问题。但是直接和模型模型说 “任务结束后发消息通知我” 显然是没有任何稳定性的（几小时的任务下来模型早就忘了），所以应当选择 hooks 触发。&lt;/p&gt;
&lt;p&gt;这里选择用免服务端的 &lt;a href=&quot;https://ntfy.sh&quot;&gt;ntfy.sh&lt;/a&gt; 做推送管道。&lt;/p&gt;
&lt;h2&gt;整体思路&lt;/h2&gt;
&lt;p&gt;Claude Code 提供两个关键钩子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt;：每次有用户输入时触发&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Stop&lt;/code&gt;：每当 Claude 停下、把控制权交还时触发&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最直接的想法是 &lt;code&gt;Stop&lt;/code&gt; 一触发就推通知，但这样每个子代理的来回都会收到通知，而预期是在&lt;strong&gt;整个任务收尾&lt;/strong&gt;时收到一条。&lt;/p&gt;
&lt;p&gt;所以更合适的方案是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt; 记下任务开始时刻（&lt;code&gt;mark&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Stop&lt;/code&gt; 时把这一轮的耗时累加进一个状态文件（&lt;code&gt;notify&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;累计活跃时间&lt;/strong&gt;达到阈值后，派生一个独立的 &lt;code&gt;settle&lt;/code&gt; 进程，进入一个静默窗口继续观察&lt;/li&gt;
&lt;li&gt;静默窗口内如果没有新的动静，判定任务结束，推送并清零；如果又有动静，窗口作废、重新等&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;状态存在临时目录下、以 &lt;code&gt;session_id&lt;/code&gt; 命名的 JSON 文件里，&lt;code&gt;mark&lt;/code&gt;/&lt;code&gt;notify&lt;/code&gt;/&lt;code&gt;settle&lt;/code&gt; 三个进程通过它协作。&lt;/p&gt;
&lt;h3&gt;合成消息不能算作用户又输入了&lt;/h3&gt;
&lt;p&gt;Claude Code 在后台子代理回报时，会注入 &lt;code&gt;&amp;lt;task-notification&amp;gt;&lt;/code&gt; 这类合成消息，而它们走的是和真实用户输入一样的 &lt;code&gt;UserPromptSubmit&lt;/code&gt; 通道。&lt;/p&gt;
&lt;p&gt;如果计时逻辑把它们当成真实指令，子代理编排型的长任务每收到一次后台回报就被清零一次，永远攒不够阈值。最后表现是 5 分钟的单会话小任务有提醒，一个多小时的子代理编排型大任务反而没提醒。&lt;/p&gt;
&lt;p&gt;所以计时钩子必须识别合成注入，优先看 hook 输入的 &lt;code&gt;prompt&lt;/code&gt; 字段是不是以这些标记开头，为空时退回扫原始 stdin：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;task-notification&amp;gt;  &amp;lt;command-name&amp;gt;  &amp;lt;command-message&amp;gt;
&amp;lt;command-args&amp;gt;  &amp;lt;local-command-caveat&amp;gt;  &amp;lt;local-command-stdout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;合成消息的出现说明任务还在继续，所以要作废正在等待的 &lt;code&gt;settle&lt;/code&gt;，真正的结束必须是主循环和后台都安静下来。&lt;/p&gt;
&lt;h3&gt;如何判断主会话是否真的结束&lt;/h3&gt;
&lt;p&gt;主循环发起一个异步子代理后，自己会停下来等回报，这一刻就会触发 &lt;code&gt;Stop&lt;/code&gt;，从钩子的角度看，它和任务彻底结束完全无法区分。&lt;/p&gt;
&lt;p&gt;如果这时候就启动 &lt;code&gt;settle&lt;/code&gt;，而后台子代理的回报时间并不确定，指定的静默窗口内不一定能等到下一条回报来作废它，于是任务中途就误报完成并清零了状态，等真正结束时反而一声不响。&lt;/p&gt;
&lt;p&gt;解决方案是 &lt;code&gt;notify&lt;/code&gt; 达到阈值后，先判断后台是否还有任务在跑，有就不启动 &lt;code&gt;settle&lt;/code&gt; 进程；只有当 &lt;code&gt;Stop&lt;/code&gt; 时后台安静下来才启动。&lt;/p&gt;
&lt;p&gt;判据来自 &lt;code&gt;Stop&lt;/code&gt; 的 stdin 字段，取两路信号的并集：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;background_tasks&lt;/code&gt;&lt;/strong&gt;：检测实时后台任务清单（元素形如 &lt;code&gt;{id, type, status, description, command}&lt;/code&gt;），如果 &lt;code&gt;status&lt;/code&gt; 不在已结束的集合（&lt;code&gt;completed&lt;/code&gt;/&lt;code&gt;failed&lt;/code&gt;/&lt;code&gt;cancelled&lt;/code&gt;/…）中就算有子代理在运行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;transcript_path&lt;/code&gt;&lt;/strong&gt;：扫描 transcript 作为兜底，对已通过 &lt;code&gt;async_launched&lt;/code&gt; 发起、但还没收到对应 &lt;code&gt;&amp;lt;task-id&amp;gt;&lt;/code&gt; + &lt;code&gt;&amp;lt;status&amp;gt;completed&lt;/code&gt; 回报的子代理进行计数（按任务开始时间过滤，不把上一个任务的残留算进来）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
&lt;code&gt;Stop&lt;/code&gt; 的 stdin 实测带这些字段：&lt;code&gt;session_id&lt;/code&gt;、&lt;code&gt;transcript_path&lt;/code&gt;、&lt;code&gt;cwd&lt;/code&gt;、&lt;code&gt;stop_hook_active&lt;/code&gt;、&lt;code&gt;background_tasks&lt;/code&gt;、&lt;code&gt;last_assistant_message&lt;/code&gt;。&lt;code&gt;session_id&lt;/code&gt; 就是用作状态文件名的那个外层 session id。
:::&lt;/p&gt;
&lt;h2&gt;配置项&lt;/h2&gt;
&lt;p&gt;行为通过环境变量设置，写在 &lt;code&gt;settings.json&lt;/code&gt; 的 &lt;code&gt;env&lt;/code&gt; 里即可：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;变量&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTFY_THRESHOLD_MS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;300000（5min）&lt;/td&gt;
&lt;td&gt;累计活跃时间达到它才可能发通知&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTFY_QUIET_MS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;90000（90s）&lt;/td&gt;
&lt;td&gt;达阈值后的静默窗口，窗口内无动静才判定结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTFY_IDLE_RESET_MS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;600000（10min）&lt;/td&gt;
&lt;td&gt;两条真实指令间隔超过它，视为新任务、清零重算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTFY_MAX_TRANSCRIPT_BYTES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;33554432（32MB）&lt;/td&gt;
&lt;td&gt;transcript 兜底扫描的体积上限，超过就只靠 &lt;code&gt;background_tasks&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTFY_DRY_RUN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;未设&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=1&lt;/code&gt; 走完整流程但不真推送（测试用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTFY_DEBUG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;未设&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=1&lt;/code&gt; 把决策过程写进临时目录的 &lt;code&gt;claude-ntfy.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;NTFY_THRESHOLD_MS&lt;/code&gt; 判定的是&lt;strong&gt;累计活跃时间&lt;/strong&gt;，而非纯墙钟，&lt;code&gt;accMs&lt;/code&gt; 在每次 &lt;code&gt;Stop&lt;/code&gt; 累加距上一个锚点的时长：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;子代理编排型任务&lt;/strong&gt;：主循环等子代理的墙钟也会累计进去，所以这类任务的 &lt;code&gt;accMs&lt;/code&gt; 基本等于任务开始到现在的总时长&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单会话任务&lt;/strong&gt;：你思考、离开的间隔（只要不超过 &lt;code&gt;IDLE_RESET_MS&lt;/code&gt;）同样算进活跃时间，它衡量的是任务窗口内累计流逝的时间&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;接入方式&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;settings.json&lt;/code&gt; 里挂两个钩子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;env&quot;: {
    &quot;NTFY_THRESHOLD_MS&quot;: &quot;600000&quot; // 10分钟阈值
  },
  &quot;hooks&quot;: {
    &quot;UserPromptSubmit&quot;: [
      {
        &quot;hooks&quot;: [
          {
            &quot;command&quot;: &quot;node \&quot;C:\\Users\\&amp;lt;你&amp;gt;\\.claude\\hooks\\ntfy-notify.js\&quot; mark&quot;,
            &quot;timeout&quot;: 10,
            &quot;type&quot;: &quot;command&quot;
          }
        ]
      }
    ],
    &quot;Stop&quot;: [
      {
        &quot;hooks&quot;: [
          {
            &quot;async&quot;: true,
            &quot;command&quot;: &quot;node \&quot;C:\\Users\\&amp;lt;你&amp;gt;\\.claude\\hooks\\ntfy-notify.js\&quot; notify&quot;,
            &quot;timeout&quot;: 15,
            &quot;type&quot;: &quot;command&quot;
          }
        ]
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Stop&lt;/code&gt; 钩子挂 &lt;code&gt;async: true&lt;/code&gt;，因为它可能派生 &lt;code&gt;settle&lt;/code&gt; 子进程，不该阻塞主流程。&lt;/p&gt;
&lt;p&gt;手机上安装 &lt;a href=&quot;https://ntfy.sh&quot;&gt;ntfy&lt;/a&gt; 软件，订阅一个 topic（建议使用随机或复杂的字符）即可。&lt;/p&gt;
&lt;h2&gt;完整实现&lt;/h2&gt;
&lt;p&gt;由 Claude 自行编写并验证，主题名和服务地址（&lt;code&gt;NTFY_TOPIC&lt;/code&gt;、&lt;code&gt;NTFY_URL&lt;/code&gt;）硬编码在脚本里。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node
&quot;use strict&quot;;

// 用法：node ntfy-notify.js mark | notify | settle &amp;lt;token&amp;gt;

const fs = require(&quot;fs&quot;);
const os = require(&quot;os&quot;);
const path = require(&quot;path&quot;);
const crypto = require(&quot;crypto&quot;);
const { spawn } = require(&quot;child_process&quot;);

const NTFY_TOPIC = &quot;你的-topic&quot;;
const NTFY_URL = &quot;https://ntfy.sh&quot;;

const THRESHOLD_MS = envMs(&quot;NTFY_THRESHOLD_MS&quot;, 5 * 60 * 1000);
const QUIET_MS = envMs(&quot;NTFY_QUIET_MS&quot;, 90 * 1000);
const IDLE_RESET_MS = envMs(&quot;NTFY_IDLE_RESET_MS&quot;, 10 * 60 * 1000);
const MAX_TRANSCRIPT_BYTES = envMs(
  &quot;NTFY_MAX_TRANSCRIPT_BYTES&quot;,
  32 * 1024 * 1024,
);
const DRY_RUN = process.env.NTFY_DRY_RUN === &quot;1&quot;;
const DEBUG = process.env.NTFY_DEBUG === &quot;1&quot;;

// Claude Code 注入的合成消息，与真实用户输入走同一个 UserPromptSubmit 通道
// 必须排除在计时之外，否则子代理编排型长任务会被每次后台回报反复清零，永远攒不够阈值
const SYNTHETIC_MARKERS = [
  &quot;&amp;lt;task-notification&amp;gt;&quot;,
  &quot;&amp;lt;command-name&amp;gt;&quot;,
  &quot;&amp;lt;command-message&amp;gt;&quot;,
  &quot;&amp;lt;command-args&amp;gt;&quot;,
  &quot;&amp;lt;local-command-caveat&amp;gt;&quot;,
  &quot;&amp;lt;local-command-stdout&amp;gt;&quot;,
];

// status 落在这里才算结束；其余一律按&quot;还在跑&quot;处理
const DONE_STATUSES = new Set([
  &quot;completed&quot;,
  &quot;complete&quot;,
  &quot;done&quot;,
  &quot;failed&quot;,
  &quot;error&quot;,
  &quot;cancelled&quot;,
  &quot;canceled&quot;,
  &quot;killed&quot;,
  &quot;timeout&quot;,
]);

const LOG_FILE = path.join(os.tmpdir(), &quot;claude-ntfy.log&quot;);

function envMs(name, fallback) {
  const raw = process.env[name];
  if (raw === undefined || raw === &quot;&quot;) return fallback;
  const n = Number(raw);
  return Number.isFinite(n) &amp;amp;&amp;amp; n &amp;gt;= 0 ? n : fallback;
}

function log(msg) {
  if (!DEBUG) return;
  try {
    fs.appendFileSync(LOG_FILE, `${new Date().toISOString()} ${msg}\n`);
  } catch {}
}

function logError(where, err) {
  // 发送失败已无处上报，但绝不能连失败本身都静默吞掉，至少留个痕迹能查
  try {
    fs.appendFileSync(
      LOG_FILE,
      `${new Date().toISOString()} ERROR ${where}: ${err &amp;amp;&amp;amp; err.message}\n`,
    );
  } catch {}
}

function stateFile(sessionId) {
  return path.join(os.tmpdir(), `claude-ntfy-${sessionId || &quot;default&quot;}.json`);
}

function readState(file) {
  try {
    const s = JSON.parse(fs.readFileSync(file, &quot;utf8&quot;));
    return s &amp;amp;&amp;amp; typeof s === &quot;object&quot; ? s : null;
  } catch {
    return null;
  }
}

function writeState(file, state) {
  // 先写临时文件再改名：mark/notify/settle 三个进程可能同时读改写，原子替换避免读到半截 JSON
  const tmp = `${file}.${process.pid}.tmp`;
  try {
    fs.writeFileSync(tmp, JSON.stringify(state));
    fs.renameSync(tmp, file);
  } catch (err) {
    logError(&quot;writeState&quot;, err);
  }
}

function blankState(prev) {
  return {
    taskStart: null,
    lastSeen: (prev &amp;amp;&amp;amp; prev.lastSeen) || null,
    anchor: null,
    accMs: 0,
    cwd: (prev &amp;amp;&amp;amp; prev.cwd) || &quot;&quot;,
    token: crypto.randomUUID(),
    pending: false,
    pendingAt: 0,
  };
}

function isSynthetic(input, raw) {
  const prompt = typeof input.prompt === &quot;string&quot; ? input.prompt : &quot;&quot;;
  if (prompt.trim()) {
    return SYNTHETIC_MARKERS.some((m) =&amp;gt; prompt.trimStart().startsWith(m));
  }
  // prompt 缺失时退回扫原始报文，这样即便 Claude Code 改了字段名，本判断也不会静默失效
  return SYNTHETIC_MARKERS.some((m) =&amp;gt; raw.includes(m));
}

function hasLiveBackgroundWork(input, state) {
  // 信号 1：Claude Code 的实时后台任务清单，能覆盖被 resume 的子代理
  const tasks = Array.isArray(input.background_tasks)
    ? input.background_tasks
    : [];
  const running = tasks.filter(
    (t) =&amp;gt; t &amp;amp;&amp;amp; !DONE_STATUSES.has(String(t.status || &quot;&quot;).toLowerCase()),
  );
  if (running.length &amp;gt; 0) {
    log(
      `live: background_tasks 有 ${running.length} 个未结束（${running.map((t) =&amp;gt; t.id).join(&quot;,&quot;)}）`,
    );
    return true;
  }

  // 信号 2：兜底，防某些版本的 background_tasks 不枚举子代理
  const out = outstandingAgents(
    input.transcript_path,
    state &amp;amp;&amp;amp; state.taskStart,
  );
  if (out &amp;gt; 0) {
    log(`live: transcript 有 ${out} 个已发起未回报的异步子代理`);
    return true;
  }
  return false;
}

function outstandingAgents(transcriptPath, since) {
  if (!transcriptPath || typeof transcriptPath !== &quot;string&quot;) return 0;
  let text;
  try {
    const st = fs.statSync(transcriptPath);
    if (st.size &amp;gt; MAX_TRANSCRIPT_BYTES) {
      // transcript 过大时全量读会拖慢每次 Stop，宁可放弃兜底也不阻塞主流程
      log(`outstandingAgents: transcript 过大(${st.size}B)，跳过兜底`);
      return 0;
    }
    text = fs.readFileSync(transcriptPath, &quot;utf8&quot;);
  } catch (err) {
    log(`outstandingAgents: 读 transcript 失败 ${err &amp;amp;&amp;amp; err.message}`);
    return 0;
  }

  const launched = new Set();
  const finished = new Set();
  for (const line of text.split(/\r?\n/)) {
    if (!line) continue;
    let j;
    try {
      j = JSON.parse(line);
    } catch {
      continue;
    }
    const ts = Date.parse(j.timestamp || &quot;&quot;) || 0;

    const tur = j.toolUseResult;
    if (tur &amp;amp;&amp;amp; tur.isAsync &amp;amp;&amp;amp; tur.status === &quot;async_launched&quot; &amp;amp;&amp;amp; tur.agentId) {
      // 按 taskStart 过滤，否则上一个任务残留的子代理会被算进这次，永远压制通知
      if (!since || ts &amp;gt;= since) launched.add(tur.agentId);
    }

    if (j.origin &amp;amp;&amp;amp; j.origin.kind === &quot;task-notification&quot;) {
      const content = j.message &amp;amp;&amp;amp; j.message.content;
      const s = typeof content === &quot;string&quot; ? content : &quot;&quot;;
      const idMatch = s.match(/&amp;lt;task-id&amp;gt;([^&amp;lt;]+)&amp;lt;\/task-id&amp;gt;/);
      const statusMatch = s.match(/&amp;lt;status&amp;gt;([^&amp;lt;]+)&amp;lt;\/status&amp;gt;/);
      const status = statusMatch ? statusMatch[1].trim().toLowerCase() : &quot;&quot;;
      if (
        idMatch &amp;amp;&amp;amp;
        (status === &quot;completed&quot; || status === &quot;failed&quot; || status === &quot;error&quot;)
      ) {
        finished.add(idMatch[1]);
      }
    }
  }

  let n = 0;
  for (const id of launched) if (!finished.has(id)) n++;
  return n;
}

function mark(input, raw, file) {
  if (isSynthetic(input, raw)) {
    // 合成注入不动计时，但它说明活儿还在继续，所以作废正在等待的 settle，等真正安静下来再判结束
    const prev = readState(file);
    if (prev &amp;amp;&amp;amp; prev.pending) {
      prev.pending = false;
      prev.token = crypto.randomUUID();
      writeState(file, prev);
      log(&quot;mark: synthetic activity, cancelled pending settle&quot;);
    } else {
      log(`mark skipped (synthetic): ${raw.slice(0, 100)}`);
    }
    return;
  }
  const now = Date.now();
  const prev = readState(file);
  const gap = prev &amp;amp;&amp;amp; prev.lastSeen ? now - prev.lastSeen : Infinity;
  // 两条真实指令间隔过长视为新任务：上一个任务早已结束，累计值不该带到这次
  const newTask = !prev || !prev.taskStart || gap &amp;gt; IDLE_RESET_MS;

  const state = newTask ? blankState(prev) : prev;
  state.taskStart = newTask ? now : state.taskStart;
  state.anchor = now;
  state.lastSeen = now;
  state.cwd = input.cwd || state.cwd || &quot;&quot;;
  state.pending = false; // 新指令作废正在等待的 settle：token 变了，它醒来后会自己退出
  state.token = crypto.randomUUID();
  writeState(file, state);
  log(`mark ${newTask ? &quot;new-task&quot; : &quot;continue&quot;} acc=${state.accMs}ms`);
}

function notify(input, file) {
  const state = readState(file);
  if (!state || !state.taskStart) {
    log(&quot;notify: no active task, skip&quot;);
    return;
  }
  const now = Date.now();
  const from = state.anchor || state.taskStart;
  state.accMs = (state.accMs || 0) + Math.max(0, now - from);
  state.anchor = now;
  state.lastSeen = now;

  if (state.accMs &amp;lt; THRESHOLD_MS) {
    writeState(file, state);
    log(`notify: acc=${Math.round(state.accMs / 1000)}s below threshold`);
    return;
  }

  // 主循环停下等子代理这一刻和&quot;任务真的结束&quot;从钩子看无法区分，靠后台是否有活来区分：
  // 有活就作废 settle、继续累计，留到它们都安静下来的那次 Stop 再启动，避免中途误报
  if (hasLiveBackgroundWork(input, state)) {
    state.pending = false;
    state.token = crypto.randomUUID();
    writeState(file, state);
    log(
      `notify: 后台仍有活，压制 settle，acc=${Math.round(state.accMs / 1000)}s`,
    );
    return;
  }

  // 超过三个静默窗口还没回来才认为上一个 settle 子进程已死，避免重复派生
  const pendingStale =
    state.pending &amp;amp;&amp;amp; now - (state.pendingAt || 0) &amp;gt; QUIET_MS * 3;
  if (state.pending &amp;amp;&amp;amp; !pendingStale) {
    writeState(file, state);
    log(&quot;notify: settle already pending&quot;);
    return;
  }

  state.pending = true;
  state.pendingAt = now;
  writeState(file, state);
  spawnSettle(state.token, file);
  log(`notify: armed settle acc=${Math.round(state.accMs / 1000)}s`);
}

function spawnSettle(token, file) {
  // 钩子进程有超时会被回收，把静默窗口的等待放进 detached 子进程才不受它约束；
  // 子进程 stdio 是 ignore 读不到 stdin，所以状态文件路径走 argv 传进去
  const child = spawn(process.execPath, [__filename, &quot;settle&quot;, token, file], {
    detached: true,
    stdio: &quot;ignore&quot;,
    windowsHide: true,
  });
  child.unref();
}

async function settle(token, file) {
  await new Promise((r) =&amp;gt; setTimeout(r, QUIET_MS));

  // token 对不上说明这段静默期内有新动静把它顶掉了，直接退出；多个 settle 竞争时只有最新的会推送
  const state = readState(file);
  if (!state || state.token !== token || !state.pending) {
    log(`settle: superseded (token ${token})`);
    return;
  }

  const mins = Math.max(1, Math.round((state.accMs || 0) / 60000));
  const project = state.cwd ? path.basename(state.cwd) : &quot;&quot;;

  if (DRY_RUN) {
    log(`settle: DRY_RUN would send &quot;${project}&quot; ${mins}min`);
  } else {
    try {
      const res = await fetch(NTFY_URL, {
        method: &quot;POST&quot;,
        headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
        body: JSON.stringify({
          topic: NTFY_TOPIC,
          title: project ? `Claude Code - ${project}` : &quot;Claude Code&quot;,
          message: `任务完成，耗时约 ${mins} 分钟`,
        }),
      });
      await res.text();
      log(`settle: sent status=${res.status} mins=${mins}`);
      if (!res.ok) throw new Error(`ntfy 返回 ${res.status}`);
    } catch (err) {
      // 发送失败不清零累计值，下一次 Stop 会重新派生 settle 再试，一次网络抖动不至于永久丢掉通知
      logError(&quot;ntfy send&quot;, err);
      const cur = readState(file);
      if (cur &amp;amp;&amp;amp; cur.token === token) {
        cur.pending = false;
        writeState(file, cur);
      }
      return;
    }
  }

  const cur = readState(file);
  if (cur &amp;amp;&amp;amp; cur.token === token) writeState(file, blankState(cur));
}

async function main() {
  const mode = process.argv[2];
  if (mode !== &quot;mark&quot; &amp;amp;&amp;amp; mode !== &quot;notify&quot; &amp;amp;&amp;amp; mode !== &quot;settle&quot;)
    process.exit(0);

  let raw = &quot;&quot;;
  try {
    raw = fs.readFileSync(0, &quot;utf8&quot;);
  } catch {}
  let input = {};
  try {
    input = JSON.parse(raw);
  } catch {
    // stdin 不是合法 JSON 时按无上下文处理，仅影响通知文案与项目名，不影响判断逻辑
  }
  const file = stateFile(input.session_id);

  if (mode === &quot;mark&quot;) mark(input, raw, file);
  else if (mode === &quot;notify&quot;) notify(input, file);
  else await settle(process.argv[3], process.argv[4] || file);
}

main().catch((err) =&amp;gt; logError(&quot;main&quot;, err));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实现细节&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;settle&lt;/code&gt; 用 &lt;code&gt;detached + unref&lt;/code&gt; 派生&lt;/strong&gt;，不跑在钩子进程里。钩子有超时、会被回收&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;token&lt;/code&gt; 机制&lt;/strong&gt;：每次启动 &lt;code&gt;settle&lt;/code&gt; 生成一个新 token 写进状态；&lt;code&gt;settle&lt;/code&gt; 醒来后比对 token，对不上就说明期间有新动静把它顶掉了，直接退出。这样多个 &lt;code&gt;settle&lt;/code&gt; 竞争时只有最新的那个会真正推送&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态文件先写临时文件再 rename&lt;/strong&gt;：&lt;code&gt;mark&lt;/code&gt;/&lt;code&gt;notify&lt;/code&gt;/&lt;code&gt;settle&lt;/code&gt; 三个进程可能同时读改写，原子替换避免读到半截 JSON&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推送失败不清零&lt;/strong&gt;：&lt;code&gt;accMs&lt;/code&gt; 保留，下一次 &lt;code&gt;Stop&lt;/code&gt; 会重新派生 &lt;code&gt;settle&lt;/code&gt; 再试，网络抖动不会永久丢掉通知&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可接受的边界&lt;/strong&gt;：如果某个子代理被强杀、从此不再回报，&lt;code&gt;background_tasks&lt;/code&gt; 会一直认为它在运行而压制这次通知。但这会在你下一条真实指令时自愈（两条真实指令间隔超过 &lt;code&gt;IDLE_RESET_MS&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>XHTTP 配置与玩法：过 CDN、上下行分离与 Encryption</title><link>https://blog.cobweb11.top/posts/xhttp/</link><guid isPermaLink="true">https://blog.cobweb11.top/posts/xhttp/</guid><description>对 XHTTP 官方文档和社区讨论以及源码的研读与实践：三种模式、XMUX 与请求混淆的取舍，过 CF 与 Nginx 前置、上下行分离、Browser Dialer、FinalMask 混搭等玩法，以及过 CDN 时的 Encryption、ECH 与证书配置。</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本文来自对 &lt;a href=&quot;https://github.com/XTLS/Xray-core/discussions/4113&quot;&gt;XHTTP: Beyond REALITY&lt;/a&gt; 和社区讨论以及 Xray-core v26.6.1 源码的研读和实践。&lt;/p&gt;
&lt;p&gt;配置基于 Xray v26.9.8 的字段名，示例统一使用以下占位：&lt;code&gt;domain.com&lt;/code&gt; 为主域名，&lt;code&gt;cf1.domain.com&lt;/code&gt; / &lt;code&gt;cf2.domain.com&lt;/code&gt; / &lt;code&gt;cf3.domain.com&lt;/code&gt; 为开启橙云的子域，&lt;code&gt;/yourpath&lt;/code&gt; 为 XHTTP path，&lt;code&gt;vpsadmin&lt;/code&gt; 账户运行 Xray，证书目录 &lt;code&gt;~/xray_cert&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;优势&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;XMUX：H2/H3 的 0-RTT 多路复用控制&lt;/li&gt;
&lt;li&gt;上下行分离：服务端仅按 path 中随机生成的 UUID 关联上下行，两个方向可以走完全不同的入口&lt;/li&gt;
&lt;li&gt;请求元数据混淆：会话 ID、seq、上行数据的载体（path / query / header / cookie）与形态都可配置、随机化，避免在 CDN 里留下固定模式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;extra&lt;/code&gt; 分享机制：&lt;code&gt;host&lt;/code&gt;、&lt;code&gt;path&lt;/code&gt;、&lt;code&gt;mode&lt;/code&gt; 以外的所有参数可整块塞进分享链接，由服务提供者下发&lt;/li&gt;
&lt;li&gt;Browser Dialer：用真浏览器的网络栈和 TLS 指纹发请求&lt;/li&gt;
&lt;li&gt;无需 gRPC 库，性能更好，下行是独立 GET 不受 CDN 对 gRPC 的限速，相比 WS/HTTPUpgrade，没有 &lt;code&gt;ALPN = http/1.1&lt;/code&gt; 的特征&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;模式&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;上行&lt;/th&gt;
&lt;th&gt;下行&lt;/th&gt;
&lt;th&gt;HTTP 请求数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;packet-up&lt;/td&gt;
&lt;td&gt;分包&lt;code&gt;POST /path/UUID/seq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GET 流式&lt;/td&gt;
&lt;td&gt;N 个&lt;/td&gt;
&lt;td&gt;兼容性最强&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stream-up&lt;/td&gt;
&lt;td&gt;流式&lt;code&gt;POST /path/UUID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GET 流式&lt;/td&gt;
&lt;td&gt;2 个&lt;/td&gt;
&lt;td&gt;上行不牺牲效率，上下行可分离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stream-one&lt;/td&gt;
&lt;td&gt;单个&lt;code&gt;POST /path/&lt;/code&gt;，响应即下行&lt;/td&gt;
&lt;td&gt;同一请求&lt;/td&gt;
&lt;td&gt;1 个&lt;/td&gt;
&lt;td&gt;最接近普通请求，REALITY 默认&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;&quot;mode&quot; 四选一，客户端、服务端默认值都是 &quot;auto&quot;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;auto&quot; - 客户端：一律 packet-up，REALITY 时 stream-one（有 &lt;code&gt;downloadSettings&lt;/code&gt; 时 stream-up）/ 服务端：同时接受三种模式&lt;/li&gt;
&lt;li&gt;&quot;packet-up&quot; - 客户端：分包上行 + 流式下行（单独的子连接）/ 服务端：仅接受 packet-up&lt;/li&gt;
&lt;li&gt;&quot;stream-up&quot; - 客户端：流式上行 + 流式下行（另一条子连接）/ 服务端：仅接受 stream-up 和 stream-one&lt;/li&gt;
&lt;li&gt;&quot;stream-one&quot; - 客户端：流式上行 + 流式下行（同一条子连接），不能有 downloadSettings / 服务端：仅接受 stream-one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;模式细节&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;packet-up 的 seq 从 0 开始，必须发完上一个 POST 的 body 再发下一个；乱序到达由服务端按 seq 重组，默认最多缓存 30 个，超限断连。会话 ID 与 seq 默认拼在 path（&lt;code&gt;/yourpath/UUID/seq&lt;/code&gt;），开启混淆后也可挪到 query、header 或 cookie&lt;/li&gt;
&lt;li&gt;stream-up / stream-one 的上行默认带 &lt;code&gt;Content-Type: application/grpc&lt;/code&gt; 伪装（配置 &lt;code&gt;noGRPCHeader&lt;/code&gt; 可关闭），加上这个 header 后 H2 流式上行可穿透 CF，需面板开 gRPC 支持&lt;/li&gt;
&lt;li&gt;stream-one 的 path 若末尾无 &lt;code&gt;/&lt;/code&gt; 会自动补上&lt;/li&gt;
&lt;li&gt;下行响应头与 packet-up 一致；stream-one 会出现以 SSE 回应 gRPC 的情况，遇到问题可尝试配置 &lt;code&gt;noSSEHeader&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;packet-up 专属参数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scMaxEachPostBytes&lt;/code&gt;：每个 POST 最多携带的字节数，默认 1000000（1MB），应小于中间盒允许的最大值，服务端会拒绝超限 POST&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scMinPostsIntervalMs&lt;/code&gt;：仅客户端，单个代理请求内 POST 的最小间隔，默认 30ms&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scMaxBufferedPosts&lt;/code&gt;：仅服务端，最多缓存的 POST 数，默认 30&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前两项建议填范围字符串（如 &lt;code&gt;&quot;500000-1000000&quot;&lt;/code&gt;）每次随机，减少指纹，三者均基于单个代理请求独立计数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;stream-up 专属参数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scStreamUpServerSecs&lt;/code&gt;：仅服务端，默认 &lt;code&gt;&quot;20-80&quot;&lt;/code&gt; 随机，每隔该时长向 stream-up 上行 POST 的响应方向写 &lt;code&gt;xPaddingBytes&lt;/code&gt; 个字节保活（前提是请求携带 padding，默认总是携带），设 &lt;code&gt;-1&lt;/code&gt; 停发保活数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;HTTP 版本（H1 / H2 / H3）&lt;/h2&gt;
&lt;p&gt;三种 mode 决定工作模式，ALPN 决定底层 HTTP 版本，它们之间是解耦、可以任意组合的；ALPN 由客户端单方面决定：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;客户端配置&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;只要有&lt;code&gt;realitySettings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;H2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;无&lt;code&gt;tlsSettings&lt;/code&gt; 也无 REALITY&lt;/td&gt;
&lt;td&gt;HTTP/1.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpn&lt;/code&gt; 为 &lt;code&gt;[&quot;http/1.1&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP/1.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpn&lt;/code&gt; 为 &lt;code&gt;[&quot;h3&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;H3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;其余情况（不写&lt;code&gt;alpn&lt;/code&gt;、写多项、写别的值）&lt;/td&gt;
&lt;td&gt;H2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H3 的条件是 &lt;code&gt;alpn&lt;/code&gt; 仅有一项且值为 &lt;code&gt;h3&lt;/code&gt;，&lt;code&gt;&quot;alpn&quot;: [&quot;h3&quot;, &quot;h2&quot;]&lt;/code&gt; 得到的是 H2，客户端不会优先 H3、失败退回 H2&lt;/li&gt;
&lt;li&gt;服务端 &lt;code&gt;alpn&lt;/code&gt; 为 &lt;code&gt;[&quot;h3&quot;]&lt;/code&gt; 时才监听 UDP/QUIC，否则一律监听 TCP&lt;/li&gt;
&lt;li&gt;套 CF 时客户端 H3 会被降成 H1/H2 回源，服务端无需监听 UDP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;XMUX&lt;/h2&gt;
&lt;p&gt;XMUX 仅在客户端生效，H2/H3 均为 0-RTT 多路复用，XMUX 是控制它们的核心接口：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;省略 xmux 时的取值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxConcurrency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每条连接最多同时承载的代理请求数，达到后建新连接&lt;/td&gt;
&lt;td&gt;0（不限）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxConnections&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;最多连接数，达到前每个新请求开新连接，之后开始复用&lt;/td&gt;
&lt;td&gt;3（固定）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cMaxReuseTimes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;一条连接最多被复用几次&lt;/td&gt;
&lt;td&gt;0（不限）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hMaxRequestTimes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;一条连接累计承载的 HTTP 请求上限（对付 Nginx 每连接 1000 请求上限）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;600-900&quot;&lt;/code&gt; 随机&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hMaxReusableSecs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;一条连接的最长复用时长（对付 Nginx 一小时上限）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;1800-3000&quot;&lt;/code&gt; 随机&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hKeepAlivePeriod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;空闲时 H2/H3 保活间隔（秒），0 为 Chrome H2 45s / quic-go 10s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;多线程测速前可设置 &lt;code&gt;&quot;maxConcurrency&quot;: 1&lt;/code&gt;，即连接数不限，每个线程各占一条连接。&lt;/p&gt;
&lt;p&gt;一条底层连接复用到底可设置 &lt;code&gt;&quot;maxConnections&quot;: 1&lt;/code&gt;，但须同时显式设置 &lt;code&gt;hMaxRequestTimes&lt;/code&gt; / &lt;code&gt;hMaxReusableSecs&lt;/code&gt;，否则二者值为 0 即不限，连接会被无限复用直到 Nginx 达到 &lt;code&gt;keepalive_requests&lt;/code&gt; / &lt;code&gt;keepalive_time&lt;/code&gt; 主动断开。&lt;/p&gt;
&lt;p&gt;日常可保持默认值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maxConcurrency&lt;/code&gt; 与 &lt;code&gt;maxConnections&lt;/code&gt; 冲突，只能二选一&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hKeepAlivePeriod&lt;/code&gt; 是唯一不允许填范围的项（该值取随机本身才是特征），且允许负数（-1 关闭空闲保活）&lt;/li&gt;
&lt;li&gt;省略 &lt;code&gt;xmux&lt;/code&gt; 或整组都为 0 时取默认值；参数只要有一项非 0，其余项就都没有默认值了，须全部显式填写&lt;/li&gt;
&lt;li&gt;H3 下若 &lt;code&gt;quicParams.keepAlivePeriod&lt;/code&gt; 非 0，则 &lt;code&gt;hKeepAlivePeriod&lt;/code&gt; 被忽略&lt;/li&gt;
&lt;li&gt;使用 XHTTP 时不要启用 mux.cool，新版服务端已检查，只接受纯 XUDP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;请求混淆&lt;/h2&gt;
&lt;p&gt;padding 默认以 &lt;code&gt;Referer: /yourpath?x_padding=...&lt;/code&gt; 的形式发出，这些在 CDN、反代的访问日志与 WAF 规则里都是显眼的模式，而混淆参数可以把元数据挪走并随机化：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xPaddingBytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;padding 长度范围（不可关闭，填 0 或负数会报错）&lt;/td&gt;
&lt;td&gt;100-1000 随机&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xPaddingObfsMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;总开关，开启后 padding 的位置与样式按下列参数走&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;（固定 &lt;code&gt;Referer&lt;/code&gt; + &lt;code&gt;x_padding&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xPaddingPlacement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;padding 放哪：&lt;code&gt;queryInHeader&lt;/code&gt; / &lt;code&gt;cookie&lt;/code&gt; / &lt;code&gt;header&lt;/code&gt; / &lt;code&gt;query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;queryInHeader&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xPaddingMethod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;padding 内容：&lt;code&gt;repeat-x&lt;/code&gt;（重复 &lt;code&gt;X&lt;/code&gt;）/ &lt;code&gt;tokenish&lt;/code&gt;（随机 Base62）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;repeat-x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xPaddingKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;query / cookie 的键名&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x_padding&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xPaddingHeader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;承载 query 的头名（例如&lt;code&gt;Referer&lt;/code&gt;、&lt;code&gt;Origin&lt;/code&gt; 等）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Padding&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessionIDPlacement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;会话 ID 放哪：&lt;code&gt;path&lt;/code&gt; / &lt;code&gt;query&lt;/code&gt; / &lt;code&gt;header&lt;/code&gt; / &lt;code&gt;cookie&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;path&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessionIDKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;非 path 放置时承载会话 ID 的键名（头名 / 参数名 / cookie 名）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Session&lt;/code&gt; / &lt;code&gt;x_session&lt;/code&gt;（由放置位置决定）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessionIDTable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;会话 ID 的字符表（预置&lt;code&gt;Base62&lt;/code&gt;、&lt;code&gt;Alphabet&lt;/code&gt;、&lt;code&gt;hex&lt;/code&gt; 等）&lt;/td&gt;
&lt;td&gt;空（用 UUID）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessionIDLength&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;会话 ID 的长度范围&lt;/td&gt;
&lt;td&gt;空（用 UUID）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seqPlacement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;seq 放哪：&lt;code&gt;path&lt;/code&gt; / &lt;code&gt;query&lt;/code&gt; / &lt;code&gt;header&lt;/code&gt; / &lt;code&gt;cookie&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;path&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seqKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;非 path 放置时承载 seq 的键名（头名 / 参数名 / cookie 名）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Seq&lt;/code&gt; / &lt;code&gt;x_seq&lt;/code&gt;（由放置位置决定）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uplinkDataPlacement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;packet-up 上行数据放哪：&lt;code&gt;body&lt;/code&gt; / &lt;code&gt;header&lt;/code&gt; / &lt;code&gt;cookie&lt;/code&gt;（后两者仅 packet-up）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;body&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uplinkDataKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;非 body 放置时承载上行数据的键名（头名 / cookie 名）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Data&lt;/code&gt; / &lt;code&gt;x_data&lt;/code&gt;（由放置位置决定）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uplinkChunkSize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;数据放 header / cookie 时每块编码后的大小&lt;/td&gt;
&lt;td&gt;header 3-4KB / cookie 2-3KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uplinkHTTPMethod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上行 HTTP 方法，&lt;code&gt;GET&lt;/code&gt; 仅 packet-up 可用&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serverMaxHeaderBytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;服务端接受的最大请求头字节数&lt;/td&gt;
&lt;td&gt;8192&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::note[混淆参数随着封锁逐步加入]
最先是 CDNVideo 只要请求包含 &lt;code&gt;x_padding=XXXXX&lt;/code&gt; 参数，就抛出 403(&lt;a href=&quot;https://github.com/XTLS/Xray-core/issues/4346#issuecomment-3545201732&quot;&gt;issue #4346&lt;/a&gt;)，于是有了换键名、换字符表（&lt;code&gt;tokenish&lt;/code&gt;）和挪位置。&lt;/p&gt;
&lt;p&gt;接着 Yandex Cloud、VK Cloud 等禁掉 POST（&lt;a href=&quot;https://yandex.cloud/en/docs/cdn/operations/resources/configure-http&quot;&gt;Yandex 文档&lt;/a&gt;），于是 &lt;code&gt;uplinkHTTPMethod&lt;/code&gt; 允许改用 PUT、PATCH；再往后有 CDN 按 UUID 的 &lt;code&gt;8-4-4-4-12&lt;/code&gt; 形状封 session ID（&lt;a href=&quot;https://github.com/XTLS/Xray-core/issues/6264&quot;&gt;issue #6264&lt;/a&gt;），于是 &lt;code&gt;sessionIDTable&lt;/code&gt;、&lt;code&gt;sessionIDLength&lt;/code&gt; 能将其伪装成普通 token。
:::&lt;/p&gt;
&lt;p&gt;Xray 的思路是不应把手里的牌一下子打完(&lt;a href=&quot;https://github.com/XTLS/BBS/issues/19&quot;&gt;issue #19&lt;/a&gt;)，所以混淆默认关闭且默认值保守，等某个特征真被针对了再使用对应参数，防止过度配置本身成了新特征，日常使用保持默认值即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据放 header 时要相应调大 &lt;code&gt;serverMaxHeaderBytes&lt;/code&gt;（如 16384，过 CDN 还要留意中间盒的请求头上限）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tokenish&lt;/code&gt; 生成随机 Base62 串，按 HPACK huffman 编码后长度落在 &lt;code&gt;xPaddingBytes&lt;/code&gt; 区间内，服务端校验同样按 huffman 长度算，比一串 &lt;code&gt;X&lt;/code&gt; 更像真实数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;queryInHeader&lt;/code&gt; 仍是把 padding 塞进某个头（可自定义，默认 &lt;code&gt;Referer&lt;/code&gt;）的 URL query 里，对 CF 兼容性最好；&lt;code&gt;cookie&lt;/code&gt; / &lt;code&gt;header&lt;/code&gt; / &lt;code&gt;query&lt;/code&gt; 则完全离开 URL&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;客户端 extra&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;extra&lt;/code&gt; 字段用于向客户端分享配置，服务端会校验 &lt;code&gt;xhttpSettings&lt;/code&gt; 里的同名参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;两端须一致：&lt;/strong&gt; &lt;code&gt;padding&lt;/code&gt; 的 6 项以及 &lt;code&gt;sessionIDPlacement&lt;/code&gt;、&lt;code&gt;sessionIDKey&lt;/code&gt;、&lt;code&gt;seqPlacement&lt;/code&gt;、&lt;code&gt;seqKey&lt;/code&gt;、&lt;code&gt;uplinkDataPlacement&lt;/code&gt;、&lt;code&gt;uplinkDataKey&lt;/code&gt;。服务端会校验参数，两端不一致时请求会被 400 拒绝&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;仅客户端：&lt;/strong&gt; &lt;code&gt;sessionIDTable&lt;/code&gt;、&lt;code&gt;sessionIDLength&lt;/code&gt;、&lt;code&gt;uplinkHTTPMethod&lt;/code&gt;、&lt;code&gt;uplinkChunkSize&lt;/code&gt;、&lt;code&gt;noGRPCHeader&lt;/code&gt;、&lt;code&gt;scMinPostsIntervalMs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;仅服务端：&lt;/strong&gt; &lt;code&gt;serverMaxHeaderBytes&lt;/code&gt;、&lt;code&gt;scStreamUpServerSecs&lt;/code&gt;、&lt;code&gt;scMaxBufferedPosts&lt;/code&gt;、&lt;code&gt;noSSEHeader&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;scMaxEachPostBytes&lt;/code&gt; 是单向约束，客户端按自己的值分包，服务端拿自己的 &lt;code&gt;To&lt;/code&gt; 做上限，不小于客户端即可。&lt;/p&gt;
&lt;p&gt;示例为 packet-up 模式，覆盖 &lt;code&gt;extra&lt;/code&gt; 可用的全部字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;xhttpSettings&quot;: {
  &quot;path&quot;: &quot;/yourpath&quot;,
  &quot;extra&quot;: {
    // 追加的自定义请求头
    &quot;headers&quot;: { &quot;User-Agent&quot;: &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64)&quot; },
    // 两端一致：padding 与 placement / key
    &quot;xPaddingBytes&quot;: &quot;100-1000&quot;,
    &quot;xPaddingObfsMode&quot;: true,
    &quot;xPaddingPlacement&quot;: &quot;queryInHeader&quot;,
    &quot;xPaddingMethod&quot;: &quot;tokenish&quot;,
    &quot;xPaddingKey&quot;: &quot;x&quot;,
    &quot;xPaddingHeader&quot;: &quot;Origin&quot;,
    &quot;sessionIDPlacement&quot;: &quot;query&quot;,
    &quot;sessionIDKey&quot;: &quot;s&quot;,
    &quot;seqPlacement&quot;: &quot;query&quot;,
    &quot;seqKey&quot;: &quot;q&quot;,
    &quot;uplinkDataPlacement&quot;: &quot;header&quot;,
    &quot;uplinkDataKey&quot;: &quot;X-Data&quot;,
    // 仅客户端：会话 ID 形态、上行形态与分包节奏
    &quot;sessionIDTable&quot;: &quot;Base62&quot;,
    &quot;sessionIDLength&quot;: &quot;12-20&quot;,
    &quot;uplinkChunkSize&quot;: &quot;2000-3000&quot;,
    &quot;uplinkHTTPMethod&quot;: &quot;GET&quot;,
    &quot;noGRPCHeader&quot;: false,
    &quot;scMaxEachPostBytes&quot;: &quot;4000-8000&quot;,
    &quot;scMinPostsIntervalMs&quot;: &quot;10-50&quot;,
    // 仅客户端：XMUX，填了任意一项后须全部显式填写
    &quot;xmux&quot;: {
      &quot;maxConcurrency&quot;: 0, &quot;maxConnections&quot;: 0, &quot;cMaxReuseTimes&quot;: 0,
      &quot;hMaxRequestTimes&quot;: 0, &quot;hMaxReusableSecs&quot;: 0, &quot;hKeepAlivePeriod&quot;: 0
    }
    // &quot;downloadSettings&quot;: { ... }   // 上下行分离时的完整下行 streamSettings
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;downloadSettings.xhttpSettings&lt;/code&gt; 里也可写 &lt;code&gt;extra&lt;/code&gt;，与上行的 &lt;code&gt;extra&lt;/code&gt; 完全一致，XMUX 和请求混淆的字段与规则全部适用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下行配置不继承上行的任何配置&lt;/strong&gt;，XMUX 参数 roll 出的具体数也是各自独立随机的，随时间推移上下行复用完全不对称&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;选型&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;RAW + REALITY + Vision&lt;/th&gt;
&lt;th&gt;XHTTP + REALITY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;新连接延迟&lt;/td&gt;
&lt;td&gt;每条代理连接一次 TCP+TLS 握手&lt;/td&gt;
&lt;td&gt;XMUX 复用，新请求 0-RTT，延迟更低&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多线程测速&lt;/td&gt;
&lt;td&gt;更强，每条连接独立拥塞窗口&lt;/td&gt;
&lt;td&gt;不如 Vision，除非&lt;code&gt;maxConcurrency: 1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU / 吞吐&lt;/td&gt;
&lt;td&gt;Linux 下自动 Splice，内核直接转发&lt;/td&gt;
&lt;td&gt;无 Splice，H2 帧处理走用户态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下行分离&lt;/td&gt;
&lt;td&gt;没有&lt;/td&gt;
&lt;td&gt;有（packet-up/stream-up）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;中间盒/CDN&lt;/td&gt;
&lt;td&gt;不可能&lt;/td&gt;
&lt;td&gt;本身为此设计&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;抗单连接时序分析&lt;/td&gt;
&lt;td&gt;Vision 内层握手随机填充&lt;/td&gt;
&lt;td&gt;padding + XMUX 随机化 + 多流混合&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组合&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vision + REALITY 直连&lt;/td&gt;
&lt;td&gt;单流拉满带宽、服务器 CPU 不富裕、Linux 环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XHTTP + REALITY 直连&lt;/td&gt;
&lt;td&gt;网页浏览、有大量小连接、要上下行分离、要过 CDN 或前置反代&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上行 XHTTP+TLS+CDN，下行 XHTTP+REALITY&lt;/td&gt;
&lt;td&gt;去程差、回程好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上行 XHTTP+REALITY，下行 XHTTP+TLS+CDN&lt;/td&gt;
&lt;td&gt;去程好、回程差&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XHTTP+TLS+CDN&lt;/td&gt;
&lt;td&gt;去程回程都差或无法直连&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;玩法与示例配置&lt;/h2&gt;
&lt;p&gt;以下示例共用基线结构：客户端 SOCKS 入站 + VLESS 出站，服务端 VLESS 入站 + freedom 出站。&lt;/p&gt;
&lt;h3&gt;基线：XHTTP + REALITY 直连&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;inbounds&quot;: [
    {
      &quot;listen&quot;: &quot;0.0.0.0&quot;,
      &quot;port&quot;: 443,
      &quot;protocol&quot;: &quot;vless&quot;,
      &quot;settings&quot;: {
        &quot;users&quot;: [{ &quot;id&quot;: &quot;你的UUID&quot; }],
        &quot;decryption&quot;: &quot;none&quot;
      },
      &quot;streamSettings&quot;: {
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; }, // mode 不填 = auto
        &quot;security&quot;: &quot;reality&quot;,
        &quot;realitySettings&quot;: {
          &quot;target&quot;: &quot;www.some-site.com:443&quot;,
          &quot;serverNames&quot;: [&quot;www.some-site.com&quot;],
          &quot;privateKey&quot;: &quot;PrivateKey&quot;,
          &quot;shortIds&quot;: [&quot;&quot;]
        }
      }
    }
  ],
  &quot;outbounds&quot;: [{ &quot;protocol&quot;: &quot;freedom&quot; }]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;inbounds&quot;: [
    {
      &quot;listen&quot;: &quot;127.0.0.1&quot;,
      &quot;port&quot;: 10808,
      &quot;protocol&quot;: &quot;socks&quot;,
      &quot;settings&quot;: { &quot;udp&quot;: true }
    }
  ],
  &quot;outbounds&quot;: [
    {
      &quot;protocol&quot;: &quot;vless&quot;,
      &quot;settings&quot;: {
        &quot;address&quot;: &quot;你的VPS_IP&quot;,
        &quot;port&quot;: 443,
        &quot;id&quot;: &quot;你的UUID&quot;,
        &quot;encryption&quot;: &quot;none&quot;
      },
      &quot;streamSettings&quot;: {
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
        &quot;security&quot;: &quot;reality&quot;,
        &quot;realitySettings&quot;: {
          &quot;serverName&quot;: &quot;www.some-site.com&quot;,
          &quot;password&quot;: &quot;xray x25519 -i 私钥 得到的 Password&quot;,
          &quot;shortId&quot;: &quot;&quot;,
          &quot;fingerprint&quot;: &quot;chrome&quot;,
          &quot;spiderX&quot;: &quot;/&quot;
        }
      },
      // XHTTP 不能开 mux.cool；concurrency 填负数关掉 TCP 复用，XUDP 走聚合隧道
      &quot;mux&quot;: { &quot;enabled&quot;: true, &quot;concurrency&quot;: -1, &quot;xudpConcurrency&quot;: 16 }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;过 CDN（TLS）&lt;/h3&gt;
&lt;p&gt;cf1.domain.com 开启橙云、CF 面板 SSL 模式为 Full (strict)，服务端持证书。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;streamSettings&quot;: {
  &quot;method&quot;: &quot;xhttp&quot;,
  &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
  &quot;security&quot;: &quot;tls&quot;,
  &quot;tlsSettings&quot;: {
    &quot;alpn&quot;: [&quot;h2&quot;, &quot;http/1.1&quot;],
    &quot;certificates&quot;: [{
      &quot;certificateFile&quot;: &quot;/home/vpsadmin/xray_cert/xray.crt&quot;,
      &quot;keyFile&quot;: &quot;/home/vpsadmin/xray_cert/xray.key&quot;
    }]
  },
  // CF 回源必带 CF-Connecting-IP，用它当哨兵头决定是否信任 XFF
  &quot;sockopt&quot;: { &quot;trustedXForwardedFor&quot;: [&quot;CF-Connecting-IP&quot;] }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端 H2 版，&lt;code&gt;address&lt;/code&gt; 填优选 IP，&lt;code&gt;serverName&lt;/code&gt; 填域名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;settings&quot;: {
    &quot;address&quot;: &quot;优选 IP&quot;,
    &quot;port&quot;: 443,
    &quot;id&quot;: &quot;你的UUID&quot;,
    &quot;encryption&quot;: &quot;none&quot;
  },
  &quot;streamSettings&quot;: {
    &quot;method&quot;: &quot;xhttp&quot;,
    &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
    &quot;security&quot;: &quot;tls&quot;,
    &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;fingerprint&quot;: &quot;chrome&quot; }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 H3 需显式指定 &lt;code&gt;alpn&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;alpn&quot;: [&quot;h3&quot;], &quot;fingerprint&quot;: &quot;chrome&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 H2 且要流式上行时显式指定 &lt;code&gt;mode&lt;/code&gt;（需 CF 面板开 gRPC 支持）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;xhttpSettings&quot;: {
  &quot;path&quot;: &quot;/yourpath&quot;,
  &quot;mode&quot;: &quot;stream-up&quot;   // 或 stream-one
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有的 CDN 会限速 stream-one 但不限 stream-up，遇到断流或降速尝试切换模式或调整混淆参数；若 CDN 对请求体大小敏感，可调分包节奏：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;xhttpSettings&quot;: {
  &quot;path&quot;: &quot;/yourpath&quot;,
  &quot;extra&quot;: {
    &quot;scMaxEachPostBytes&quot;: &quot;500000-1000000&quot;,   // 需小于 CDN 允许的最大请求体
    &quot;scMinPostsIntervalMs&quot;: &quot;10-50&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端连接 CF 边缘 IP，CF 用边缘证书握手，按 Host 回源到 VPS，验证 Origin 证书后转发给 Xray。&lt;/p&gt;
&lt;p&gt;CF 会掐断下行 100 秒无实际数据的 HTTP，代理长连接需应用层保活，比如使用 sshd 的 &lt;code&gt;ClientAliveInterval&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;对于 Fastly、Gcore、CloudFront 这类非 CF 的 CDN 时，&lt;code&gt;trustedXForwardedFor&lt;/code&gt; 需使用对应 CDN 每次回源都稳定携带的头（比如Fastly 的 &lt;code&gt;Fastly-Client-IP&lt;/code&gt;，CloudFront 的 &lt;code&gt;CloudFront-Viewer-Address&lt;/code&gt;），并确认 CDN 把真实客户端 IP 放进了 &lt;code&gt;X-Forwarded-For&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;常见问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;服务端日志报 &lt;code&gt;invalid x_padding length:0&lt;/code&gt;、请求被 400。优先考虑是 CDN 未将 URL 的 query string 透传回源，例如 CloudFront 的缓存策略默认不带 query string，需在 Cache Policy 里把 query string 设为转发全部。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CDN 或 WAF 识别并拦截 &lt;code&gt;?x_padding=&lt;/code&gt; 导致 403，请求无法回源。考虑把 padding 挪出 URL，开启 &lt;code&gt;xPaddingObfsMode&lt;/code&gt; 后把 &lt;code&gt;xPaddingPlacement&lt;/code&gt; 设为 &lt;code&gt;cookie&lt;/code&gt; 或 &lt;code&gt;header&lt;/code&gt;，或尝试更改 &lt;code&gt;xPaddingKey&lt;/code&gt;，做法见 &lt;a href=&quot;#%E5%85%83%E6%95%B0%E6%8D%AE%E6%90%AC%E5%87%BA-url&quot;&gt;把元数据搬出 URL&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Nginx 前置（TLS）&lt;/h3&gt;
&lt;p&gt;Nginx 持真证书监听 443，XHTTP 入站监听本地明文：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 443 ssl;
    http2 on;
    server_name cf1.domain.com;
    ssl_certificate     /etc/ssl/fullchain.pem;
    ssl_certificate_key /etc/ssl/privkey.pem;

    location /yourpath {
        grpc_pass grpc://127.0.0.1:1234;          # stream-up / stream-one 用这个
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        grpc_set_header X-From-Front 1;           # 哨兵头，供 trustedXForwardedFor 匹配
        grpc_read_timeout 1h;
        grpc_send_timeout 1h;
    }

    location / { root /var/www/html; }            # 其余路径是正常网站
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Xray 入站：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;listen&quot;: &quot;127.0.0.1&quot;,
  &quot;port&quot;: 1234,
  &quot;protocol&quot;: &quot;vless&quot;,
  &quot;settings&quot;: { &quot;users&quot;: [{ &quot;id&quot;: &quot;你的UUID&quot; }], &quot;decryption&quot;: &quot;none&quot; },
  &quot;streamSettings&quot;: {
    &quot;method&quot;: &quot;xhttp&quot;,
    &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
    &quot;sockopt&quot;: { &quot;trustedXForwardedFor&quot;: [&quot;X-From-Front&quot;] }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TLS 在 Nginx 终结，按 path 把 &lt;code&gt;/yourpath&lt;/code&gt; 以 h2c 转发本地 1234，其余路径当普通网站服务。防御主动探测，TLS 指纹是 Nginx 的而非 Go 的。&lt;/p&gt;
&lt;p&gt;TLS 版本由 Nginx 决定，特殊情况可回退 TLS 1.2，用于规避审查方对来自某些机房的 TLS 1.3 进行阻断的策略。&lt;/p&gt;
&lt;p&gt;packet-up 模式下 &lt;code&gt;grpc_pass&lt;/code&gt; 不适用，更换普通反代并关闭缓冲：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location /yourpath {
    proxy_pass http://127.0.0.1:1234;
    proxy_http_version 1.1;
    proxy_buffering off;              // 下行必须关，否则流式下行退化
    proxy_request_buffering off;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-From-Front 1;
    proxy_read_timeout 1h;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端不出现新块，沿用 &lt;a href=&quot;#%E8%BF%87-cdntls&quot;&gt;上文&lt;/a&gt; 的字段。&lt;/p&gt;
&lt;p&gt;如需使用 H3，使用 Nginx ≥ 1.25 并监听 QUIC 即可，回源的 location 不变：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;listen 443 quic reuseport;   # H3，需 Nginx ≥ 1.25
listen 443 ssl;
http2 on;
http3 on;
add_header Alt-Svc &apos;h3=&quot;:443&quot;; ma=86400&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端 &lt;code&gt;alpn&lt;/code&gt; 指定 &lt;code&gt;[&quot;h3&quot;]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Caddy 默认开启 H3，&lt;code&gt;reverse_proxy&lt;/code&gt; 到同一个入站即可。&lt;/p&gt;
&lt;h3&gt;Cloudflare Worker / Snippet 反代前置&lt;/h3&gt;
&lt;p&gt;不想让源站域名直接开橙云回源，可以用 Worker（或更轻量的 Snippet）把请求改写到后端域名再转发，客户端连接 Worker 路由绑定的域名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async fetch(request) {
    const url = new URL(request.url);
    const backends = [&quot;b1.origin.com&quot;, &quot;b2.origin.com&quot;]; // 后端域名，多填即随机挑
    url.hostname = backends[Math.floor(Math.random() * backends.length)];
    return fetch(new Request(url, request));
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CF 面板里给 Worker 绑定 &lt;code&gt;front.domain.com/yourpath*&lt;/code&gt; 路由，客户端 &lt;code&gt;address&lt;/code&gt;/&lt;code&gt;serverName&lt;/code&gt;/&lt;code&gt;host&lt;/code&gt; 指向 &lt;code&gt;front.domain.com&lt;/code&gt;，path、UUID、padding 原样透传。&lt;/p&gt;
&lt;p&gt;前置域名与源站解耦，适用于随机挑后端、给已被阻断的源站套层 CF 或希望把前置逻辑与回源分开的情况，受 Worker 的 CPU 与子请求配额限制。&lt;/p&gt;
&lt;h3&gt;Cloudflare Argo 隧道（cloudflared 内网穿透）&lt;/h3&gt;
&lt;p&gt;前面几种过 CDN 的方案都要求源站有公网 IP 且要监听端口。依赖 Argo 隧道允许源站无监听端口，公网 IP 和证书。&lt;/p&gt;
&lt;p&gt;适合 NAT VPS、无 DDNS 且只有动态 IPv6 的机器、回源端口受限或源站 IP 无法直连的情况。&lt;/p&gt;
&lt;p&gt;XHTTP 入站监听本地明文，由 cloudflared 的 ingress 按域名直接转发：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;listen&quot;: &quot;127.0.0.1&quot;,
  &quot;port&quot;: 1234,
  &quot;protocol&quot;: &quot;vless&quot;,
  &quot;settings&quot;: { &quot;users&quot;: [{ &quot;id&quot;: &quot;你的UUID&quot; }], &quot;decryption&quot;: &quot;none&quot; },
  &quot;streamSettings&quot;: {
    &quot;method&quot;: &quot;xhttp&quot;,
    &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cloudflared 固定隧道配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tunnel: 你的隧道ID
credentials-file: /home/vpsadmin/.cloudflared/你的隧道ID.json
protocol: auto # 隧道到 CF 边缘的传输，优先尝试 QUIC(UDP 7844)，异常时自动回落 H2，如已确定 UDP 被封锁或环境对 QUIC 支持不佳，可直接指定为 http2
ingress:
  - hostname: cf1.domain.com
    service: http://127.0.0.1:1234
    originRequest:
      http2Origin: true # 回源本地入站用 H2，XHTTP 需要
  - service: http_status:404
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端沿用 &lt;a href=&quot;#%E8%BF%87-cdntls&quot;&gt;过 CDN（TLS）&lt;/a&gt; 的字段，&lt;code&gt;serverName&lt;/code&gt; 与 &lt;code&gt;host&lt;/code&gt; 填隧道绑定的 &lt;code&gt;cf1.domain.com&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须用固定（命名）隧道，&lt;code&gt;trycloudflare&lt;/code&gt; 临时隧道不支持 XHTTP&lt;/li&gt;
&lt;li&gt;回源经隧道只有 H2，TLS 由 CF 边缘和隧道负责，Xray 入站明文 h2c&lt;/li&gt;
&lt;li&gt;一条隧道可按 hostname 分流给多个入站，也能和 Worker 前置配合使用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;上下行分离&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;downloadSettings&lt;/code&gt; 是一套完整的 &lt;code&gt;streamSettings&lt;/code&gt; 外加 &lt;code&gt;address&lt;/code&gt;/&lt;code&gt;port&lt;/code&gt;，&lt;code&gt;method&lt;/code&gt; 必须为 &lt;code&gt;&quot;xhttp&quot;&lt;/code&gt;（不可省略），&lt;code&gt;security&lt;/code&gt; 可为 &lt;code&gt;&quot;tls&quot;&lt;/code&gt; 或 &lt;code&gt;&quot;reality&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sockopt&lt;/code&gt; 项也可被分享，上行 &lt;code&gt;sockopt&lt;/code&gt; 设 &lt;code&gt;&quot;penetrate&quot;: true&lt;/code&gt; 可覆盖下行，适合使用 &lt;code&gt;mark&lt;/code&gt; 的情况。&lt;/p&gt;
&lt;h4&gt;同一 CDN 上行 IPv4 H2，下行 IPv6 H3&lt;/h4&gt;
&lt;p&gt;只改客户端：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;streamSettings&quot;: {
  &quot;method&quot;: &quot;xhttp&quot;,
  &quot;security&quot;: &quot;tls&quot;,
  &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;fingerprint&quot;: &quot;chrome&quot; },
  &quot;xhttpSettings&quot;: {
    &quot;path&quot;: &quot;/yourpath&quot;,
    &quot;mode&quot;: &quot;stream-up&quot;,    // 不能使用 stream-one
    &quot;extra&quot;: {
      &quot;downloadSettings&quot;: {
        &quot;address&quot;: &quot;优选 IPv6&quot;,
        &quot;port&quot;: 443,
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;security&quot;: &quot;tls&quot;,
        &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;alpn&quot;: [&quot;h3&quot;], &quot;fingerprint&quot;: &quot;chrome&quot; },
        &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; }   // path 必须一致
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端随机生成 UUID，上行 &lt;code&gt;POST /yourpath/UUID&lt;/code&gt; 走 IPv4 的 H2 到边缘 IP-A，下行 &lt;code&gt;GET /yourpath/UUID&lt;/code&gt; 走 IPv6 的 QUIC H3 到边缘 IP-B。两个方向的源 IP、目标 IP、四层协议、HTTP 版本均不同。服务端按 UUID 把两半缝合，30 秒内没缝上就终止会话，基于单条连接的检测只能看到半条流。&lt;/p&gt;
&lt;h4&gt;上行下行不同 CDN&lt;/h4&gt;
&lt;p&gt;上行和下行使用两家不同的 CDN。例如上行套 CF，下行套另一家 CDN：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;streamSettings&quot;: {
  &quot;method&quot;: &quot;xhttp&quot;,
  &quot;security&quot;: &quot;tls&quot;,
  &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;fingerprint&quot;: &quot;chrome&quot; },
  &quot;xhttpSettings&quot;: {
    &quot;path&quot;: &quot;/yourpath&quot;,
    &quot;mode&quot;: &quot;stream-up&quot;,
    &quot;extra&quot;: {
      &quot;downloadSettings&quot;: {
        &quot;address&quot;: &quot;另一家 CDN 的 IP&quot;,
        &quot;port&quot;: 443,
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;security&quot;: &quot;tls&quot;,
        &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;b.other-cdn.com&quot;, &quot;fingerprint&quot;: &quot;chrome&quot; },
        &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两家 CDN 按 SNI 回源到同一台 VPS 的同一个 XHTTP 入站、&lt;code&gt;path&lt;/code&gt; 一致，按 UUID 缝合。两个方向所属的 CDN 基础设施不同，任何一方手里都只有半条流。&lt;/p&gt;
&lt;h4&gt;同域域前置&lt;/h4&gt;
&lt;p&gt;三个地址字段在一次 XHTTP over CDN 的请求里互相独立：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;是什么&lt;/th&gt;
&lt;th&gt;谁能看见&lt;/th&gt;
&lt;th&gt;决定什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实际拨号目标&lt;/td&gt;
&lt;td&gt;链路上所有人&lt;/td&gt;
&lt;td&gt;包发到哪个 IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tlsSettings.serverName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TLS ClientHello 的 SNI&lt;/td&gt;
&lt;td&gt;链路上所有人，可用 ECH 加密&lt;/td&gt;
&lt;td&gt;CDN 用哪张证书握手&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xhttpSettings.host&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP Host 头（H2/H3 为&lt;code&gt;:authority&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;TLS 加密，只有 CDN 能看见&lt;/td&gt;
&lt;td&gt;CDN 回源到哪台机器&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;SNI 与 Host 不一致即域前置，两者是同一 zone 内不同子域即同域域前置。主流商业级 CDN 均已全面禁止未经授权的跨 zone 域前置，所以采用同域域前置。&lt;/p&gt;
&lt;p&gt;cf1 上行、cf2 下行，cf3 为共同 Host，均开启橙云并指向同一 VPS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;streamSettings&quot;: {
  &quot;method&quot;: &quot;xhttp&quot;,
  &quot;security&quot;: &quot;tls&quot;,
  &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;fingerprint&quot;: &quot;chrome&quot; },
  &quot;xhttpSettings&quot;: {
    &quot;host&quot;: &quot;cf3.domain.com&quot;,       // 客户端发送优先级 host &amp;gt; serverName &amp;gt; address
    &quot;path&quot;: &quot;/yourpath&quot;,
    &quot;mode&quot;: &quot;stream-up&quot;,
    &quot;extra&quot;: {
      &quot;downloadSettings&quot;: {
        &quot;address&quot;: &quot;优选 IP&quot;,
        &quot;port&quot;: 443,
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;security&quot;: &quot;tls&quot;,
        &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf2.domain.com&quot;, &quot;alpn&quot;: [&quot;h3&quot;], &quot;fingerprint&quot;: &quot;chrome&quot; },
        &quot;xhttpSettings&quot;: { &quot;host&quot;: &quot;cf3.domain.com&quot;, &quot;path&quot;: &quot;/yourpath&quot; }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两条 TLS 握手 SNI 分别是 cf1 和 cf2，两个方向的 Host 都是 cf3，CF 按 cf3 回源。&lt;/p&gt;
&lt;p&gt;当 cf1 和 cf2 橙云均指向同一台 VPS 时，不填 &lt;code&gt;host&lt;/code&gt; 也可以，各方向按 SNI 回源至同一台机器同一 path，按 UUID 缝合。需要 &lt;code&gt;host&lt;/code&gt; 的场景：源站前有 Nginx 按 &lt;code&gt;server_name&lt;/code&gt; 分流、只想为一个域名配回源规则、或想让两个方向走完全一样的回源逻辑。&lt;/p&gt;
&lt;h4&gt;上下行非对称 XMUX&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&quot;xhttpSettings&quot;: {
  &quot;path&quot;: &quot;/yourpath&quot;,
  &quot;mode&quot;: &quot;stream-up&quot;,
  &quot;extra&quot;: {
    // 上行数据少，全挤在一条底层连接上；过 Nginx 时须限制 hMaxRequestTimes 和 hMaxReusableSecs
    &quot;xmux&quot;: {
      &quot;maxConcurrency&quot;: 0, &quot;maxConnections&quot;: 1, &quot;cMaxReuseTimes&quot;: 0,
      &quot;hMaxRequestTimes&quot;: 0, &quot;hMaxReusableSecs&quot;: 0, &quot;hKeepAlivePeriod&quot;: 0
    },
    &quot;downloadSettings&quot;: {
      &quot;address&quot;: &quot;回程优的 IP&quot;,
      &quot;port&quot;: 443,
      &quot;method&quot;: &quot;xhttp&quot;,
      &quot;security&quot;: &quot;tls&quot;,
      &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;cf1.domain.com&quot;, &quot;fingerprint&quot;: &quot;chrome&quot; },
      &quot;xhttpSettings&quot;: {
        &quot;path&quot;: &quot;/yourpath&quot;,
        &quot;extra&quot;: {
          // 下行数据多，每条底层连接只跑一个请求，摊到多条链路
          &quot;xmux&quot;: {
            &quot;maxConcurrency&quot;: 1, &quot;maxConnections&quot;: 0, &quot;cMaxReuseTimes&quot;: 0,
            &quot;hMaxRequestTimes&quot;: 0, &quot;hMaxReusableSecs&quot;: 0, &quot;hKeepAlivePeriod&quot;: 0
          }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上行所有 POST 复用同一条 TCP，省握手；下行每个 GET 各开一条 TCP，避开单连接拥塞窗口和队头阻塞；两个方向各走最优路径。&lt;/p&gt;
&lt;h4&gt;上/下行 REALITY 直连 + 下/上行过 CDN&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;inbounds&quot;: [
    // XHTTP 明文入站，监听本地
    {
      &quot;listen&quot;: &quot;127.0.0.1&quot;,
      &quot;port&quot;: 1234,
      &quot;protocol&quot;: &quot;vless&quot;,
      &quot;settings&quot;: { &quot;users&quot;: [{ &quot;id&quot;: &quot;你的UUID&quot; }], &quot;decryption&quot;: &quot;none&quot; },
      &quot;streamSettings&quot;: {
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
        &quot;sockopt&quot;: { &quot;trustedXForwardedFor&quot;: [&quot;CF-Connecting-IP&quot;] }
      }
    },
    // 入口 A：REALITY 监听 443，非法 VLESS 首包回落到 1234
    {
      &quot;listen&quot;: &quot;0.0.0.0&quot;,
      &quot;port&quot;: 443,
      &quot;protocol&quot;: &quot;vless&quot;,
      &quot;settings&quot;: {
        &quot;users&quot;: [{ &quot;id&quot;: &quot;一个用不到的UUID&quot; }],
        &quot;decryption&quot;: &quot;none&quot;,
        &quot;fallbacks&quot;: [{ &quot;dest&quot;: 1234, &quot;xver&quot;: 0 }]
      },
      &quot;streamSettings&quot;: {
        &quot;method&quot;: &quot;raw&quot;,
        &quot;security&quot;: &quot;reality&quot;,
        &quot;realitySettings&quot;: {
          &quot;target&quot;: &quot;www.some-site.com:443&quot;,
          &quot;serverNames&quot;: [&quot;www.some-site.com&quot;],
          &quot;privateKey&quot;: &quot;PrivateKey&quot;,
          &quot;shortIds&quot;: [&quot;&quot;]
        }
      }
    }
  ],
  &quot;outbounds&quot;: [{ &quot;protocol&quot;: &quot;freedom&quot; }]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;入口 B 是 Nginx，监听回源端口 8443，持真证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 8443 ssl;
    http2 on;
    server_name cf1.domain.com;
    ssl_certificate     /etc/ssl/fullchain.pem;
    ssl_certificate_key /etc/ssl/privkey.pem;

    location /yourpath {
        proxy_pass http://127.0.0.1:1234;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;settings&quot;: {
    &quot;address&quot;: &quot;你的VPS_IP&quot;,
    &quot;port&quot;: 443,
    &quot;id&quot;: &quot;你的UUID&quot;,
    &quot;encryption&quot;: &quot;none&quot;
  },
  &quot;streamSettings&quot;: {
    &quot;method&quot;: &quot;xhttp&quot;,
    &quot;security&quot;: &quot;reality&quot;,
    &quot;realitySettings&quot;: {
      &quot;serverName&quot;: &quot;www.some-site.com&quot;,
      &quot;password&quot;: &quot;Password&quot;,
      &quot;shortId&quot;: &quot;&quot;,
      &quot;fingerprint&quot;: &quot;chrome&quot;
    },
    &quot;xhttpSettings&quot;: {
      &quot;path&quot;: &quot;/yourpath&quot;,
      &quot;mode&quot;: &quot;stream-up&quot;,
      &quot;extra&quot;: {
        &quot;downloadSettings&quot;: {
          &quot;address&quot;: &quot;优选 IP&quot;,
          &quot;port&quot;: 443,
          &quot;method&quot;: &quot;xhttp&quot;,
          &quot;security&quot;: &quot;tls&quot;,
          &quot;tlsSettings&quot;: {
            &quot;serverName&quot;: &quot;cf1.domain.com&quot;,
            &quot;alpn&quot;: [&quot;h3&quot;],
            &quot;fingerprint&quot;: &quot;chrome&quot;
          },
          &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上行直连 443，REALITY 鉴权通过后，首包非法流量回落至 127.0.0.1:1234。下行走 CF 的 QUIC H3，CF 回源到 8443 的 Nginx 反代给同一个 127.0.0.1:1234。&lt;/p&gt;
&lt;p&gt;把客户端的上行、下行对调，就是上行过 CDN、下行 REALITY 直连，服务端配置不用改。&lt;/p&gt;
&lt;h3&gt;443 端口复用&lt;/h3&gt;
&lt;p&gt;443 监听 raw + REALITY 入站，REALITY 鉴权失败的流量回落本机 Nginx，由 Nginx 提供伪装站并将 &lt;code&gt;/yourpath&lt;/code&gt; 反代到内部 XHTTP 入站；REALITY 鉴权通过但首包非法的流量回落到同一个内部 XHTTP 入站：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;inbounds&quot;: [
    // 内部 XHTTP 入站，监听本地
    {
      &quot;listen&quot;: &quot;127.0.0.1&quot;, // 也可使用 Unix socket
      &quot;port&quot;: 1234,
      &quot;protocol&quot;: &quot;vless&quot;,
      &quot;settings&quot;: {
        &quot;users&quot;: [{ &quot;id&quot;: &quot;XHTTP 的 UUID&quot; }],
        &quot;decryption&quot;: &quot;none&quot;
      },
      &quot;streamSettings&quot;: {
        &quot;method&quot;: &quot;xhttp&quot;,
        &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
        // CF 回源带 CF-Connecting-IP，Nginx 前置注入 X-From-Front
        &quot;sockopt&quot;: {
          &quot;trustedXForwardedFor&quot;: [&quot;CF-Connecting-IP&quot;, &quot;X-From-Front&quot;]
        }
      }
    },
    // raw + REALITY入站，承载 Vision 直连，其余回落
    {
      &quot;listen&quot;: &quot;0.0.0.0&quot;,
      &quot;port&quot;: 443,
      &quot;protocol&quot;: &quot;vless&quot;,
      &quot;settings&quot;: {
        &quot;users&quot;: [{ &quot;id&quot;: &quot;Vision 的 UUID&quot;, &quot;flow&quot;: &quot;xtls-rprx-vision&quot; }],
        &quot;decryption&quot;: &quot;none&quot;,
        &quot;fallbacks&quot;: [{ &quot;dest&quot;: 1234, &quot;xver&quot;: 0 }] // XHTTP 的 H2 preface 非法，回落到 1234
      },
      &quot;streamSettings&quot;: {
        &quot;method&quot;: &quot;raw&quot;,
        &quot;security&quot;: &quot;reality&quot;,
        &quot;realitySettings&quot;: {
          &quot;target&quot;: &quot;127.0.0.1:8443&quot;,
          &quot;serverNames&quot;: [&quot;your.domain.com&quot;],
          &quot;privateKey&quot;: &quot;PrivateKey&quot;,
          &quot;shortIds&quot;: [&quot;&quot;]
        }
      }
    }
  ],
  &quot;outbounds&quot;: [{ &quot;protocol&quot;: &quot;freedom&quot; }]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;XHTTP 的 UUID 只在内部入站校验，几种客户端出站方案共用同一个 &lt;code&gt;path&lt;/code&gt; 和 UUID。Nginx 配置参考 &lt;a href=&quot;#nginx-%E5%89%8D%E7%BD%AEtls&quot;&gt;Nginx 前置&lt;/a&gt;，将监听端口改为8443，并给 &lt;code&gt;your.domain.com&lt;/code&gt; 配置 &lt;code&gt;server_name&lt;/code&gt; 用于主动探测回落即可。&lt;/p&gt;
&lt;p&gt;不能使用 &lt;code&gt;path&lt;/code&gt; 进行回落，REALITY/TLS 下为 H2，path 提取只解析 H1 的明文请求行，且 XHTTP 会在 path 后追加 UUID 和 seq，H1 下同样无法匹配。&lt;/p&gt;
&lt;h3&gt;元数据搬出 URL&lt;/h3&gt;
&lt;p&gt;默认情况下会话 ID、seq 拼在 path，padding 置于 &lt;code&gt;Referer&lt;/code&gt;，位置固定；&lt;a href=&quot;#%E8%AF%B7%E6%B1%82%E6%B7%B7%E6%B7%86&quot;&gt;请求混淆&lt;/a&gt; 的参数能把 packet-up 伪装成普通带 cookie 的 GET，请求变成一串没有 body 的 GET：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;xhttpSettings&quot;: {
  &quot;path&quot;: &quot;/yourpath&quot;,
  &quot;mode&quot;: &quot;packet-up&quot;,
  &quot;extra&quot;: {
    &quot;xPaddingObfsMode&quot;: true,
    &quot;xPaddingPlacement&quot;: &quot;cookie&quot;,
    &quot;xPaddingKey&quot;: &quot;_ga&quot;,
    &quot;xPaddingMethod&quot;: &quot;tokenish&quot;,
    &quot;sessionIDPlacement&quot;: &quot;cookie&quot;,
    &quot;sessionIDKey&quot;: &quot;sid&quot;,
    &quot;sessionIDTable&quot;: &quot;Base62&quot;,
    &quot;sessionIDLength&quot;: &quot;16-24&quot;,
    &quot;seqPlacement&quot;: &quot;cookie&quot;,
    &quot;seqKey&quot;: &quot;n&quot;,
    &quot;uplinkDataPlacement&quot;: &quot;cookie&quot;,
    &quot;uplinkDataKey&quot;: &quot;d&quot;,
    &quot;uplinkHTTPMethod&quot;: &quot;GET&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uplinkDataPlacement&lt;/code&gt; 和 &lt;code&gt;uplinkHTTPMethod&lt;/code&gt; 的 &lt;code&gt;GET&lt;/code&gt; 仅支持 packet-up 模式，会话 ID 从 UUID 换成 16-24 位的 Base62 串，更像普通 session token。&lt;/p&gt;
&lt;p&gt;cookie 每块只装 2 到 3 KB（&lt;code&gt;uplinkChunkSize&lt;/code&gt; 可调），上行一大就是一长串 cookie，所以只适合上行小或应对 body 检查的场景。&lt;/p&gt;
&lt;p&gt;下行 GET 的 &lt;code&gt;Content-Type: text/event-stream&lt;/code&gt; 可由服务端配置 &lt;code&gt;noSSEHeader&lt;/code&gt; 去掉；使用 stream-up/one 时，上行的 &lt;code&gt;application/grpc&lt;/code&gt; 伪装可由客户端配置 &lt;code&gt;noGRPCHeader&lt;/code&gt; 去掉。&lt;/p&gt;
&lt;h3&gt;Browser Dialer&lt;/h3&gt;
&lt;p&gt;使用真实浏览器的网络栈和 TLS 指纹发起连接，数据经本地 WebSocket 回到 Xray，有一定的性能损耗：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;XRAY_BROWSER_DIALER=127.0.0.1:8080 ./xray -c config.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器打开 &lt;code&gt;localhost:8080&lt;/code&gt; 并保持。注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;address&lt;/code&gt; 必须是域名，要指定 IP 就改系统 hosts 或内置 DNS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tlsSettings&lt;/code&gt; 将失效，HTTP 版本由浏览器决定，&lt;code&gt;SNI == host == address&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;浏览器到服务端必须直连&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Firefox 93+ 在严格追踪保护 / 隐私窗口下会无视 &lt;code&gt;unsafe-url&lt;/code&gt; 等宽松 referrer 策略并裁掉跨站请求的 &lt;code&gt;Referer&lt;/code&gt;(&lt;a href=&quot;https://blog.mozilla.org/security/2021/10/05/firefox-93-features-an-improved-smartblock-and-new-referrer-tracking-protections/&quot;&gt;Mozilla 安全博客&lt;/a&gt;)，padding 默认置于 &lt;code&gt;Referer&lt;/code&gt; 会连不上，把 padding 挪到 header 即可以解决。&lt;/p&gt;
&lt;h3&gt;四层调优（BBR / TFO / MPTCP）&lt;/h3&gt;
&lt;p&gt;XHTTP 走 H1/H2 时底层是一条 TCP，可在 &lt;code&gt;sockopt&lt;/code&gt; 里对它做拥塞控制、握手和多路径调优：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;streamSettings&quot;: {
  &quot;method&quot;: &quot;xhttp&quot;,
  &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
  &quot;sockopt&quot;: {
    &quot;tcpFastOpen&quot;: true,      // TFO，省一次握手往返
    &quot;tcpCongestion&quot;: &quot;bbr&quot;,   // 这条连接单独用 BBR
    &quot;tcpMptcp&quot;: true          // MPTCP 多路径
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tcpCongestion&lt;/code&gt; 使用 &lt;code&gt;bbr&lt;/code&gt; 前先确认内核支持，&lt;code&gt;sockopt&lt;/code&gt; 只改这条连接，全局启用需要 &lt;code&gt;sysctl&lt;/code&gt; 设置 &lt;code&gt;net.core.default_qdisc=fq&lt;/code&gt; 和 &lt;code&gt;net.ipv4.tcp_congestion_control=bbr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tcpMptcp&lt;/code&gt; 需要 Linux 5.6+ 且 &lt;code&gt;net.mptcp.enabled=1&lt;/code&gt;，客户端所在系统也要支持，只开一端等于没开&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tcpFastOpen&lt;/code&gt; 也可以填队列长度（整数），需客户端、服务端、中间盒都放行，任一环节失败回退普通握手&lt;/li&gt;
&lt;li&gt;过 CDN 时客户端到 CF 边缘的握手由 CF 决定，&lt;code&gt;sockopt&lt;/code&gt; 只对 CF 回源段、直连与自建反代链路有意义&lt;/li&gt;
&lt;li&gt;上下行分离时上行、下行各有各的 &lt;code&gt;sockopt&lt;/code&gt;；上行设 &lt;code&gt;&quot;penetrate&quot;: true&lt;/code&gt; 可覆盖下行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;FinalMask 给 H3 调拥塞控制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;streamSettings&quot;: {
  &quot;method&quot;: &quot;xhttp&quot;,
  &quot;xhttpSettings&quot;: { &quot;path&quot;: &quot;/yourpath&quot; },
  &quot;security&quot;: &quot;tls&quot;,
  // 直连，serverName 用解析到 VPS 的域名（灰云或主域）
  &quot;tlsSettings&quot;: { &quot;serverName&quot;: &quot;domain.com&quot;, &quot;alpn&quot;: [&quot;h3&quot;], &quot;fingerprint&quot;: &quot;chrome&quot; },
  &quot;finalmask&quot;: {
    &quot;quicParams&quot;: { &quot;congestion&quot;: &quot;force-brutal&quot;, &quot;brutalUp&quot;: &quot;30 mbps&quot; },
    &quot;udp&quot;: [
      // v26.9.9 起端口跳跃是独立的 UDP mask，须在最外层，仅客户端
      { &quot;type&quot;: &quot;udphop&quot;, &quot;settings&quot;: { &quot;remotePorts&quot;: &quot;20000-50000&quot;, &quot;interval&quot;: &quot;5-10&quot;, &quot;mode&quot;: &quot;intervalRemote&quot; } }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;XHTTP H3 无协商机制，不支持 &lt;code&gt;brutal&lt;/code&gt;，只能用免协商的 &lt;code&gt;force-brutal&lt;/code&gt;，它强制上行按 &lt;code&gt;brutalUp&lt;/code&gt; 定速发包。两者都只对 H3 直连有意义。&lt;/p&gt;
&lt;p&gt;不适用套 CDN 的方案。套 CF 时 &lt;code&gt;force-brutal&lt;/code&gt; 定速的是客户端到对端的 QUIC，到 CF 边缘终止并以 H2/H1 回源；端口跳跃改的是客户端 QUIC 的目标端口，而 CF 边缘只在标准端口接收 QUIC。&lt;/p&gt;
&lt;p&gt;端口跳跃在 v26.9.9 从 &lt;code&gt;quicParams.udpHop&lt;/code&gt; 挪到了 &lt;code&gt;finalmask.udp&lt;/code&gt; 下，字段 &lt;code&gt;ports&lt;/code&gt; 改为 &lt;code&gt;remotePorts&lt;/code&gt; 且必须显式指定 &lt;code&gt;mode&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;intervalRemote&quot;：周期换远端端口，底层 socket 和本地源端口不动，最经典的端口跳跃&lt;/li&gt;
&lt;li&gt;&quot;intervalLocal&quot;：周期换本地源端口，跳跃点新开本地 UDP socket&lt;/li&gt;
&lt;li&gt;&quot;perConnRemote&quot;：每条连接定一次，之后整条连接固定不变&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;老写法在新版会被静默丢弃；服务端要让被跳到的整段端口都能到达 QUIC 监听端口，一般在 nftables/iptables 对端口段进行重定向。&lt;/p&gt;
&lt;p&gt;H3 直连默认带着 quic-go 的 Chrome QUIC 指纹（零长 Connection ID），要关掉可在 &lt;code&gt;quicParams&lt;/code&gt; 里设 &lt;code&gt;disableChromeParrot&lt;/code&gt;。官方文档不建议服务端裸跑 quic-go H3，更推荐藏在真 Nginx/Caddy 后面。&lt;/p&gt;
&lt;h2&gt;CDN 相关&lt;/h2&gt;
&lt;h3&gt;Cloudflare&lt;/h3&gt;
&lt;p&gt;橙云子域：CF 代理流量，可做 CDN 优选、域前置、回源目标。&lt;/p&gt;
&lt;p&gt;灰云子域：CF 只做 DNS 解析、不代理，只有灰云才能解析出真实 IP，适合给直连节点当 &lt;code&gt;address&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;当面板 SSL 模式为 Flexible 时，CF 回源走明文 HTTP，建议使用 Full (strict) 并配证书。&lt;/p&gt;
&lt;p&gt;回源端口需要是 &lt;a href=&quot;https://developers.cloudflare.com/fundamentals/reference/network-ports/&quot;&gt;Cloudflare 支持的端口&lt;/a&gt;，非标端口（比如 10086）需使用 Origin Rule 重写回源端口。&lt;/p&gt;
&lt;p&gt;CF 面板可选设置 Cache Rules，按 CDN 主机名或 XHTTP path 匹配、缓存资格设为绕过（Bypass），虽然 XHTTP 下行本来就不进缓存，但可以预防 CF 版本行为变化。&lt;/p&gt;
&lt;h3&gt;VLESS Encryption&lt;/h3&gt;
&lt;p&gt;过 CDN 时外层 TLS 终结在 CF 边缘，CF 解密后能看到内层 VLESS 明文。纯 VLESS 自身不加密，能读到明文的包括 CF 本身，以及回源段若非 Full (strict) 时 CF 与 VPS 之间的中间人。&lt;/p&gt;
&lt;p&gt;VLESS Encryption 在 VLESS 内层端到端认证加密，独立于外层 TLS 与公共 CA 体系，客户端预置服务端静态公钥（X25519 或 ML-KEM-768），每条连接做临时密钥交换，兼具前向安全与后量子安全；载荷走 AES-256-GCM / ChaCha20-Poly1305，即使 CF 或链路上任何人拿着有效证书 MITM 掉外层 TLS，没有服务端静态私钥也无法伪造内层握手和读取内容。&lt;/p&gt;
&lt;p&gt;执行 &lt;code&gt;xray vlessenc&lt;/code&gt; 生成配对的 decryption/encryption，输出含 X25519 与 ML-KEM-768 两版，二选一不要混用（握手本身两者都后量子安全，ML-KEM-768 版额外能防客户端参数泄露后被未来量子计算机破解出私钥冒充服务端）&lt;/p&gt;
&lt;p&gt;配置串以 &lt;code&gt;.&lt;/code&gt; 分块:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;mlkem768x25519plus.&amp;lt;mode&amp;gt;.&amp;lt;rtt&amp;gt;.&amp;lt;padding...&amp;gt;.&amp;lt;key&amp;gt;[.&amp;lt;key&amp;gt;...]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mlkem768x25519plus&lt;/code&gt; 为握手方式，&lt;code&gt;&amp;lt;mode&amp;gt;&lt;/code&gt; 为流量外观：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;native&quot;：头部有公钥特征，流量为 TLSv1.3 的 &lt;code&gt;23 3 3 l&amp;gt;&amp;gt;8 l&lt;/code&gt; AEAD 头特征&lt;/li&gt;
&lt;li&gt;&quot;xorpub&quot;：头部无公钥特征，流量同上&lt;/li&gt;
&lt;li&gt;&quot;random&quot;：全随机数加密&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;rtt&amp;gt;&lt;/code&gt; 服务端为 0-RTT 有效期如 &lt;code&gt;600s&lt;/code&gt;（可写范围 &lt;code&gt;60-600s&lt;/code&gt;，&lt;code&gt;0&lt;/code&gt; 则关闭 0-RTT），客户端为 &lt;code&gt;0rtt&lt;/code&gt;/&lt;code&gt;1rtt&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;padding&amp;gt;&lt;/code&gt; 是可选参数，仅作用于 1-RTT 以消除握手的长度特征，每个字段为 probability-from-to 三段式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;probability&amp;gt;-&amp;lt;from&amp;gt;-&amp;lt;to&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;probability&amp;gt;&lt;/code&gt;：0–100 的百分比（实际命中率为 (probability+1)%）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;from&amp;gt; / &amp;lt;to&amp;gt;&lt;/code&gt;：左闭右开区间。len 类为字节数，gap 类为毫秒数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;字段按 len、gap 交替串联，以 &lt;code&gt;strings.Split(padding, &quot;.&quot;)&lt;/code&gt; 结果数组的下标判断，偶数（i%2 == 0）为 len，奇数（i%2 == 1）为 gap。&lt;/p&gt;
&lt;p&gt;双端默认值均为 &quot;100-111-1111.75-0-111.50-0-3333&quot;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;100-111-1111&quot;: 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding&lt;/li&gt;
&lt;li&gt;&quot;75-0-111&quot;: 以 75% 的概率等待随机 0 到 111 ms&lt;/li&gt;
&lt;li&gt;&quot;50-0-3333&quot;: 再次以 50% 的概率发送随机 0 到 3333 字节的 padding（若为 0 则不 Write()）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;服务端、客户端可以设置不同的 padding 参数，按 len、gap 的顺序无限串联；padding 参数必须整体位于 key 之前。&lt;/p&gt;
&lt;p&gt;第一个 padding 概率必须为 100% 且 &lt;code&gt;&amp;lt;from&amp;gt;-&amp;lt;to&amp;gt;&lt;/code&gt; 都必须 ≥ 18+17 = 35，所有 len 段最大值之和必须 ≤ 18+65535 = 65553。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt; 为身份认证密钥，至少选择 1 个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;decryption: X25519 私钥(32B) | ML-KEM-768 种子(64B)&lt;/li&gt;
&lt;li&gt;encryption: X25519 公钥(32B) | ML-KEM-768 封装密钥(1184B)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配置串在格式上支持多级 relay 密钥链，认证密钥块可重复。客户端按中继顺序依次列出链路上每一级的公钥或封装密钥，核心按这个顺序逐级完成身份认证，每一级都基于公私钥机制，且下一级被上一级加密绑定。&lt;/p&gt;
&lt;p&gt;客户端（出站 encryption）在 &lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt; 按中继顺序配置链路上每一级的密钥（即列出 N 个密钥，第 j 个是第 j 级节点的公钥或封装密钥），每一级独立完成一次身份认证。&lt;/p&gt;
&lt;p&gt;服务端（入站 decryption，即终止节点），只配置自己终止的级，通常就是一个密钥。中间节点转发时只剥离自己那一级，把剩余部分连同 iv 继续往下传，全程不解密流量。&lt;/p&gt;
&lt;p&gt;中间节点只配置自己的私钥以及下一级公钥的 hash32，并不解密流量。相邻两级通过一个 32 字节的 blake3 hash 绑定，发送方向第 j 级写入的是第 j+1 级公钥的 hash32，并用第 j 级协商出的密钥加密；第 j 级节点解出后与本地记录比对，不符合直接断连。&lt;/p&gt;
&lt;p&gt;例如对于一个三级密钥链 C → A → B → S：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;节点&lt;/th&gt;
&lt;th&gt;需要配置的密钥&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C（客户端）&lt;/td&gt;
&lt;td&gt;3 个公钥，按 A、B、S 的顺序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A（中转）&lt;/td&gt;
&lt;td&gt;自己的私钥 + 下一级信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B（中转）&lt;/td&gt;
&lt;td&gt;自己的私钥 + 下一级信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S（服务端）&lt;/td&gt;
&lt;td&gt;1 个私钥&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;级数直接决定握手长度：&lt;/p&gt;
&lt;p&gt;$L_{\text{relays}}=\sum_{i=1}^{n}\ell_i+32(n-1)$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$L_r$: relays 长度&lt;/li&gt;
&lt;li&gt;$\ell_i$: 第 $i$ 级密钥材料长度&lt;/li&gt;
&lt;li&gt;$n$: 级数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$P_h = \ell_{\mathrm{iv}} + L_r,\quad \ell_{\mathrm{iv}} = 16$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$P_h$: 握手前缀长度&lt;/li&gt;
&lt;li&gt;$\ell_{\mathrm{iv}}$: iv 的长度，固定为 $16$ 字节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::important
Xray-core &lt;strong&gt;尚未实现中转转发&lt;/strong&gt;。开发者表示：入站即出站，这个需要写代码来中转，Xray-core 暂无此功能，计划在 vless-relay 项目中实现（&lt;a href=&quot;https://github.com/XTLS/Xray-core/pull/5067#issuecomment-3233765726&quot;&gt;参考&lt;/a&gt;）。即关于多级密钥链，目前&lt;strong&gt;仅在格式上有除 hash32 字段以外的支持&lt;/strong&gt;，转发还尚未实现
:::&lt;/p&gt;
&lt;p&gt;最短可用形式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;decryption&quot;: &quot;mlkem768x25519plus.native.600s.&amp;lt;X25519 Privatekey | ML-KEM-768 Seed&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&quot;encryption&quot;: &quot;mlkem768x25519plus.native.0rtt.&amp;lt;X25519 PublicKey | ML-KEM-768 EncapsulationKey&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;REALITY 本身已在同一条直连链路上做了服务端认证与端到端加密，无需另外设置&lt;/li&gt;
&lt;li&gt;裸跑（即 &lt;code&gt;security: &quot;none&quot;&lt;/code&gt; 且无可靠 finalmask 时） 抗不住熵检测与主动探测，不要这样做&lt;/li&gt;
&lt;li&gt;只要 TLS 终结在你不完全信任的中间盒，VLESS Encryption 就有意义&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;XTLS Vision 穿透 Encryption&lt;/h4&gt;
&lt;p&gt;仅设置 VLESS Encryption 时，握手后所有数据经 AEAD 加密封装成伪 TLS 1.3 应用数据记录，核心将数据按 8192 字节分段，每段附一个 5 字节头（即 TLSv1.3 应用数据记录头）与 16 字节 AEAD tag，两个方向各一套 AEAD。&lt;/p&gt;
&lt;p&gt;全程只能以读入、解封、复制、转交的方式在用户态完成，没有 ReadV 或 splice 可用。这部分开销叠在外层 TLS 或 XHTTP 的帧处理之上，吞吐越高越明显。&lt;/p&gt;
&lt;p&gt;Vision 是 VLESS 的 flow: xtls-rprx-vision 所激活的一段流内协议，先分块填充外层去除 TLS-in-TLS 的长度特征，再在确认内层是 TLS 1.3（且套件非 TLS_AES_128_CCM_8_SHA256）后于第一条完整的应用数据记录处剥去外层加密层（无 Encryption 时即外层 TLS），穿透到裸 socket 做 splice（目前仅下行方向，上行 splice 尚未启用），从此该方向不再经过承载层的编解码，省掉双重加密的开销。&lt;/p&gt;
&lt;p&gt;:::note
Encryption 也有 padding，但其与 vision 中的分块填充分工不同且时段不重叠：&lt;/p&gt;
&lt;p&gt;Encryption 的 &lt;code&gt;&amp;lt;padding&amp;gt;&lt;/code&gt; 用于自身 1-RTT 握手，对端一次性读走丢弃。&lt;/p&gt;
&lt;p&gt;Vision 的分块填充只覆盖内层 TLS 握手期，从 VLESS 体的第一帧起，首帧加 21 字节（16 字节 UUID + 5 字节帧头），其后每帧只加 5 字节帧头，再按内容长短补随机填充；到内层出现第一条完整的应用数据记录即停止（内层是 TLS 1.2 或根本不是 TLS 时，另有更早的结束条件）。&lt;/p&gt;
&lt;p&gt;层次上 Vision 的填充在 AEAD 之内，AEAD 记录长度随填充变化；两者掩盖的特征也不同，前者处理伪 TLS 握手的定长块，后者处理 TLS-in-TLS 的长度特征。&lt;/p&gt;
&lt;p&gt;顺带一提，Vision 的默认填充参数是在 ClientHello 只有数百字节的年代定的，这套区间可能已经失效，维护者已在 &lt;a href=&quot;https://github.com/XTLS/Xray-core/pull/5391#issuecomment-3635680743&quot;&gt;#5391&lt;/a&gt; 提出要调整默认参数。
:::&lt;/p&gt;
&lt;p&gt;在 VLESS Encryption 下，穿透与 splice 独立判定：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Encryption 之下&lt;/th&gt;
&lt;th&gt;穿透（旁路 Encryption 的 AEAD）&lt;/th&gt;
&lt;th&gt;内核 splice（仅下行）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;裸 TCP（&lt;code&gt;security: &quot;none&quot;&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;是，Linux/Android 上零拷贝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS / REALITY&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;否，剥到 TLS 层为止，退回用户态复制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XHTTP / CDN 等带帧层的传输&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;确认内层为 TLS 1.3（套件非 &lt;code&gt;TLS_AES_128_CCM_8_SHA256&lt;/code&gt;）后，该方向在第一条完整的应用数据记录处切到 direct copy（接收方向以对端帧头里的 &lt;code&gt;direct&lt;/code&gt; 指令为准），读写落在 Encryption 下层，AEAD 不再参与，省一层加解密；&lt;code&gt;&amp;lt;mode&amp;gt;&lt;/code&gt; 为 &lt;code&gt;random&lt;/code&gt; 时 XOR 层保留，AEAD 同样被旁路。&lt;/p&gt;
&lt;p&gt;能否进一步做零拷贝 splice，取决于剥去 Encryption 后是否为裸 socket（核心不再往下剥 TLS/REALITY（避免二次穿透），也不剥 &lt;code&gt;random&lt;/code&gt; 的 XOR 层）。&lt;/p&gt;
&lt;p&gt;所以仅有 Encryption 下为裸 TCP 时才支持 splice，且要求出站侧同样可拼接（如 freedom）、TCP 层 finalmask 可旁路（sudoku、xmc、服务端 fragment 不支持）。不满足时数据同样会绕过 AEAD，但多几次用户态内存拷贝。&lt;/p&gt;
&lt;p&gt;两端的 &lt;code&gt;flow&lt;/code&gt; 必须一致，服务端漏写会直接拒绝该用户：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;users&quot;: [{ &quot;id&quot;: &quot;你的UUID&quot;, &quot;flow&quot;: &quot;xtls-rprx-vision&quot; }],
&quot;decryption&quot;: &quot;mlkem768x25519plus.native.600s.&amp;lt;X25519 Privatekey | ML-KEM-768 Seed&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&quot;users&quot;: [{ &quot;id&quot;: &quot;你的UUID&quot;, &quot;flow&quot;: &quot;xtls-rprx-vision&quot;, &quot;encryption&quot;: &quot;mlkem768x25519plus.native.0rtt.&amp;lt;X25519 PublicKey | ML-KEM-768 EncapsulationKey&amp;gt;&quot; }]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;xtls-rprx-vision&lt;/code&gt; 会拦掉目标是 443 的 UDP（QUIC），需要 QUIC 时改用 &lt;code&gt;xtls-rprx-vision-udp443&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;TLS-in-TLS 长度特征&lt;/h4&gt;
&lt;p&gt;内层 TLS 记录被外层原样封装，只要外层不把内层记录合并或拆分，密文流上的包长就与内层分别对应。审查方仅通过包长即可识别。&lt;/p&gt;
&lt;p&gt;内层 TLS 握手最初几个包的长度很有规律，CCS + Finished 约 80 字节，首个应用数据记录（通常是 h2 SETTINGS）约 60 字节，这两个短包近乎定值（&lt;a href=&quot;https://github.com/XTLS/Xray-core/pull/5391#issuecomment-3637154110&quot;&gt;参考&lt;/a&gt;）；&lt;/p&gt;
&lt;p&gt;ClientHello 在启用 X25519MLKEM768 后从数百字节涨到 1.5 KB 以上，ServerHello 因 key_share 回显也增大约 1.1 KB，证书 flight 则随站点证书链变化，且是数千字节的突发而非单条记录（&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8446.html#section-4&quot;&gt;RFC 8446 §4&lt;/a&gt; 允许握手消息跨记录与多条消息合进一条记录）。&lt;/p&gt;
&lt;p&gt;这样一串握手形状的包长出现在一条已建立好的加密连接里，几乎只可能来自嵌套的第二层 TLS。&lt;/p&gt;
&lt;p&gt;该特征是元数据问题，只能靠填充或重新分帧消除。同时填充只能把包撑大、无法缩小，多路复用只能增加往返、无法减少，两者都有原理性上限（&lt;a href=&quot;https://www.usenix.org/system/files/usenixsecurity24-xue-fingerprinting.pdf&quot;&gt;Xue et al., USENIX Security 2024&lt;/a&gt;（该论文的测量部分另有争议，见下））。&lt;/p&gt;
&lt;p&gt;是否需要 flow 的填充，取决于该特征对谁可见（下表均为关闭 flow 的状态）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景（无 flow）&lt;/th&gt;
&lt;th&gt;包长是否与内层对应&lt;/th&gt;
&lt;th&gt;谁看得见&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TLS / REALITY 直连&lt;/td&gt;
&lt;td&gt;是，真 TLS 记录&lt;/td&gt;
&lt;td&gt;on-path 上任何人&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption &lt;code&gt;native&lt;/code&gt;/&lt;code&gt;xorpub&lt;/code&gt;，裸 TCP&lt;/td&gt;
&lt;td&gt;是，伪 TLS 头明文&lt;/td&gt;
&lt;td&gt;on-path 上任何人&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption &lt;code&gt;random&lt;/code&gt;，裸 TCP&lt;/td&gt;
&lt;td&gt;否，仅帧头被 XOR 打乱&lt;/td&gt;
&lt;td&gt;无人&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XHTTP（H2/H3）且非 Encryption &lt;code&gt;random&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;否，外层 HTTP 分帧不对应&lt;/td&gt;
&lt;td&gt;on-path 不可见；外层 TLS 终结端可见&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KCP 传输、xdns、sudoku 等&lt;/td&gt;
&lt;td&gt;否，段化/封装/整条流变换&lt;/td&gt;
&lt;td&gt;无人&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::tip
为什么 Encryption &lt;code&gt;native&lt;/code&gt;/&lt;code&gt;xorpub&lt;/code&gt; 也有该特征？&lt;/p&gt;
&lt;p&gt;Encryption 与真 TLS 记录同构，如果将 Encryption 层看作长度透明（这里和后面的&lt;code&gt;透明&lt;/code&gt;指公开、可查、可验证）的封装层（即 1:1 分帧 + 明文长度字段），与真 TLS 是一致的（记录头明文，外长 = 内长 + tag）。&lt;/p&gt;
&lt;p&gt;而在 &lt;code&gt;random&lt;/code&gt; 下，帧头会被 XOR 运算打乱，无该特征。&lt;/p&gt;
&lt;p&gt;即当外层同时满足“内容不可读 + 帧头可读 + 分帧 1:1 + 长度可由内层计算”时，内层的分帧与长度可被原样透出。
:::&lt;/p&gt;
&lt;p&gt;Vision 通过 direct copy 动作实现穿透，穿透后该方向将由内层 TLS 而非 Encryption 的 AEAD 封装。此时对外层 TLS 终结端来说，载荷从不可解析的 AEAD 密文变成一串包长可见、内容不可读的嵌套 TLS 记录；但对于 on-path 仍不可见。&lt;/p&gt;
&lt;p&gt;另外，填充抹掉的只是长度维度，方向与往返节奏仍然存在。Xue 等人在 ISP 上实测 1.1 亿条流，XTLS Vision 的长度特征被填平后，只凭方向序列仍能识别出 51.281% 的 XTLS-vision（论文近似建模，&lt;strong&gt;非当前的原生完整实现&lt;/strong&gt;）流量，误报率 0.1989%；未填充的 vless over tls 对照组检测率为 74.830%（非同一检测模型）（&lt;a href=&quot;https://www.usenix.org/system/files/usenixsecurity24-xue-fingerprinting.pdf&quot;&gt;USENIX Security &apos;24 - Fingerprinting Obfuscated Proxy Traffic with Encapsulated TLS Handshakes&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;同时，开发者对该论文的测量部分提出过两点质疑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一是被测的被代理流量全是 TLS 1.2（现实世界以 TLS 1.3 为主，少一次握手、特征更少），而 TLS 1.2 不会触发 Vision 的穿透，握手之后仍带着 TLS-in-TLS 特征，测的并不是实际部署下的形态&lt;/li&gt;
&lt;li&gt;二是 Table 3 里 Vision 用的是专门逆向该协议定制的 c2 模型，而不是通用的 c1 模型，与其他协议的数字不具直接可比性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;据开发者转述，第一作者的解释是内层 TLS 1.3 与 c1 模型对 Vision 的检测效果都不佳，于是选了 TLS 1.2 并未放出 c1 的数据（&lt;a href=&quot;https://github.com/net4people/bbs/issues/496#issuecomment-3042177691&quot;&gt;评论一&lt;/a&gt;、&lt;a href=&quot;https://github.com/net4people/bbs/issues/496#issuecomment-3326599320&quot;&gt;评论二&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;方向与往返节奏仍然可辨的结论仍然成立，开发者自己也把“一短去一长回再开始传输数据”列为当前最大的问题，并表示“应该顺便把这个流量特征给消除掉”（&lt;a href=&quot;https://github.com/XTLS/Xray-core/pull/5391#issuecomment-3641749600&quot;&gt;#5391&lt;/a&gt;）。&lt;/p&gt;
&lt;h3&gt;ECH 加密&lt;/h3&gt;
&lt;p&gt;ECH（Encrypted Client Hello）将 ClientHello 拆成内外两层，内层为真实域名，用 ECHConfig 内的 HPKE 公钥加密后置于 &lt;code&gt;encrypted_client_hello&lt;/code&gt; 扩展；外层是 Decoy SNI。中间人因此无法得知真实的 SNI。&lt;/p&gt;
&lt;p&gt;客户端启用 ECH 需设置 &lt;code&gt;tlsSettings.echConfigList&lt;/code&gt;，有两种形式：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;形式&lt;/th&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;固定 ECHConfig&lt;/td&gt;
&lt;td&gt;base64 的 ECHConfigList&lt;/td&gt;
&lt;td&gt;无额外查询，服务端轮换密钥后需手动同步客户端&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;从 DNS 服务器查询&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;cf1.domain.com+https://1.1.1.1/dns-query&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动跟随轮换，按记录 TTL 缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;固定 ECHConfig&lt;/h4&gt;
&lt;p&gt;不依赖 DNS 服务器，执行 &lt;code&gt;xray tls ech&lt;/code&gt; 生成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xray tls ech --serverName domain.com    # 外层 SNI，输出 ECH config list 与 ECH server keys
xray tls ech --pem                      # 输出 PEM
xray tls ech -i &quot;&amp;lt;ECH server keys&amp;gt;&quot;     # 丢失 config list 可由 server keys 反推
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&quot;tlsSettings&quot;: {
  &quot;serverName&quot;: &quot;domain.com&quot;,
  &quot;certificates&quot;: [
    { &quot;certificateFile&quot;: &quot;/home/vpsadmin/xray_cert/xray.crt&quot;, &quot;keyFile&quot;: &quot;/home/vpsadmin/xray_cert/xray.key&quot; }
  ],
  &quot;echServerKeys&quot;: &quot;&amp;lt;ECH server keys&amp;gt;&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&quot;tlsSettings&quot;: {
  &quot;serverName&quot;: &quot;domain.com&quot;,
  &quot;echConfigList&quot;: &quot;&amp;lt;ECH config list&amp;gt;&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ECH 命中时客户端用内层 ClientHello 的域名（即 &lt;code&gt;serverName&lt;/code&gt;）验证服务端证书，所以服务端证书必须包含内层域名；服务端配置 ECH 后仍然接受非 ECH 连接。&lt;/p&gt;
&lt;p&gt;:::note
根据 &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9849.html#section-6.1.7&quot;&gt;RFC 9849 6.1.7&lt;/a&gt; 的要求，服务端拒绝 ECH（如密钥轮换，客户端配置过期）时，握手改用明文的 Decoy SNI 继续，此时需要使用 Decoy SNI 的证书。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;从规范层面来讲&lt;/strong&gt;，若服务端未持有外层 SNI 的证书，降级路径必然失败。&lt;strong&gt;但实际上&lt;/strong&gt;，uTLS/Go 在 ECH 被拒时会直接报 tls: server rejected ECH 终止连接，不进行 retry_configs 重试。所以对于 Xray，外层 SNI 可以任意选择合理的域名。
:::&lt;/p&gt;
&lt;h4&gt;从 DNS 服务器查询&lt;/h4&gt;
&lt;p&gt;CF 的 Decoy SNI 固定是 &lt;code&gt;cloudflare-ech.com&lt;/code&gt;，所有启用 ECH 的站点共用。开启 ECH 后域名的 HTTPS(TYPE65) 记录里会多出 &lt;code&gt;ech=&lt;/code&gt; 字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dig +short HTTPS cf1.domain.com
# 1 . alpn=&quot;h2,h3&quot; ipv4hint=... ech=&quot;AEX+DQBB...&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Xray 会通过 HTTPS 记录动态获取其 ECHConfig，DNS服务器支持 https://（DoH）、h2c://、udp:// 三种：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;tlsSettings&quot;: {
  &quot;serverName&quot;: &quot;cf1.domain.com&quot;,
  &quot;alpn&quot;: [&quot;h2&quot;],
  &quot;echConfigList&quot;: &quot;cf1.domain.com+udp://1.1.1.1:53&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;域名+DNS服务器&lt;/code&gt; 其中前缀是&quot;指定该域名用于查询 ECHConfig&quot;，不写前缀时按 &lt;code&gt;serverName&lt;/code&gt; 或 &lt;code&gt;address&lt;/code&gt;（如果 SNI 为空且目标为一个域名）查询；通常用于将查询域名与目标域名分开（比如你想从 DNS 获取 ECHConfig 但又不想暴露自己在查询这个域名的 HTTPS 记录或者在这个域名下发布 HTTPS 记录时）。&lt;/p&gt;
&lt;p&gt;Xray 遵守 DNS 下发的 TTL；首次同步阻塞查询，过期不足 4 小时使用旧值连接、后台 goroutine 异步刷新，过期超过 4 小时同步阻塞刷新。&lt;/p&gt;
&lt;p&gt;CF 轮换 ECH 密钥很快（记录 TTL 约 5 小时），一旦指定的解析器记录滞后，就会拿过期的 ECHConfig 去握手，导致 &lt;code&gt;tls: server rejected ECH&lt;/code&gt;，需留意。&lt;/p&gt;
&lt;p&gt;当查询失败或 base64 解不开时，Xray 会使用非法 ECHConfig 让握手必然失败，不会造成 SNI 泄露。&lt;/p&gt;
&lt;p&gt;ECH 查询的连接使用 &lt;code&gt;internet.DialSystem&lt;/code&gt;，不经过路由规则和出站，可用 echSockopt 的 &lt;code&gt;dialerProxy&lt;/code&gt; 指定 tag 出站：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;tlsSettings&quot;: {
  &quot;serverName&quot;: &quot;cf1.domain.com&quot;,
  &quot;echConfigList&quot;: &quot;https://1.1.1.1/dns-query&quot;,
  &quot;echSockopt&quot;: { &quot;dialerProxy&quot;: &quot;proxy&quot; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ECH 仅抵御基于 SNI 的识别与封锁，不防 MITM&lt;/li&gt;
&lt;li&gt;ECH 只对 &lt;code&gt;security: &quot;tls&quot;&lt;/code&gt; 有意义&lt;/li&gt;
&lt;li&gt;如果配置了 &lt;code&gt;minVersion&lt;/code&gt;，其值必须为 &lt;code&gt;1.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;不要再为 ECH 配置 &lt;code&gt;fingerprint: &quot;unsafe&quot;&lt;/code&gt; 了，早期 utls 的 &lt;a href=&quot;https://github.com/XTLS/Xray-core/pull/3813#issuecomment-2850168051&quot;&gt;BUG&lt;/a&gt; 已经被修复了&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;证书：ACME DNS-01 与 CF Origin CA&lt;/h2&gt;
&lt;p&gt;cf1/cf2 同属一个 zone，签一张 &lt;code&gt;*.domain.com&lt;/code&gt;（可加主域）的通配符即可：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ACME + DNS-01&lt;/th&gt;
&lt;th&gt;Origin CA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;签发&lt;/td&gt;
&lt;td&gt;需 API token&lt;/td&gt;
&lt;td&gt;面板点几下，复制粘贴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;有效期&lt;/td&gt;
&lt;td&gt;90 天，cron 自动续&lt;/td&gt;
&lt;td&gt;15 年，无续期&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;谁信任&lt;/td&gt;
&lt;td&gt;所有浏览器/系统&lt;/td&gt;
&lt;td&gt;只有 CF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;额外依赖&lt;/td&gt;
&lt;td&gt;acme.sh + token 存 VPS&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;ACME DNS-01 证书&lt;/h3&gt;
&lt;p&gt;登录 Cloudflare 获取 Cloudflare API Token&lt;/p&gt;
&lt;p&gt;验证 token 是否存活：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -s -H &quot;Authorization: Bearer 你的token&quot; https://api.cloudflare.com/client/v4/user/tokens/verify
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;签发证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export CF_Token=&quot;你的token&quot;
export CF_Account_ID=&quot;你的AccountID&quot;

acme.sh --set-default-ca --server letsencrypt
acme.sh --issue --dns dns_cf -d &quot;domain.com&quot; -d &quot;*.domain.com&quot; --keylength ec-256
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;token 会被明文存进 &lt;code&gt;~/.acme.sh/account.conf&lt;/code&gt; 供续期自动复用，机器需保证安全，如发生泄露需要在面板 Revoke。&lt;/p&gt;
&lt;p&gt;安装给 Xray：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir ~/xray_cert
acme.sh --install-cert -d &quot;domain.com&quot; --ecc \
    --fullchain-file ~/xray_cert/xray.crt \
    --key-file ~/xray_cert/xray.key
chmod +r ~/xray_cert/xray.key   # Xray 非 root 运行时
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;acme.sh 自带每日 cron，自动续期并重新执行 install-cert，Xray 默认热重载证书。&lt;/p&gt;
&lt;h3&gt;CF Origin CA 证书&lt;/h3&gt;
&lt;p&gt;只有 CF 信任，15 年免续：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;面板 -&amp;gt; SSL/TLS -&amp;gt; Origin Server -&amp;gt; Create Certificate&lt;/li&gt;
&lt;li&gt;保持默认：ECC 私钥，Hostnames 填 &lt;code&gt;domain.com&lt;/code&gt; 和 &lt;code&gt;*.domain.com&lt;/code&gt;，15 年有效期 -&amp;gt; Create&lt;/li&gt;
&lt;li&gt;页面给出 Origin Certificate 和 Private Key 两段 PEM，存成两个文件：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mkdir ~/xray_cert
vim ~/xray_cert/xray.crt   # 粘贴 Origin Certificate 整段，含 BEGIN/END 行
vim ~/xray_cert/xray.key   # 粘贴 Private Key 整段
chmod 600 ~/xray_cert/xray.key
chmod +r ~/xray_cert/xray.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Xray/Nginx 的引用方式与 ACME 完全一致，泄露或换机器时在 Origin Server 页面 Revoke。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;packet-up 和 &lt;code&gt;Referer&lt;/code&gt; 长 padding 会刷出大量长日志，建议在反代软件里指定不记录；使用请求混淆也可让日志形态不再扎眼&lt;/li&gt;
&lt;li&gt;&lt;code&gt;address&lt;/code&gt; 填优选 IP 时 &lt;code&gt;serverName&lt;/code&gt; 必填，且 IP 不能当 SNI&lt;/li&gt;
&lt;li&gt;v26.7.11 起 REALITY 服务端在 &lt;code&gt;minClientVer&lt;/code&gt; 留空时默认为 26.3.27，其他内核和旧客户端会被静默拒连，不建议随意降低 &lt;code&gt;minClientVer&lt;/code&gt; 放行，也不要让客户端发出非正常的 ClientHello&lt;/li&gt;
&lt;li&gt;v26.9.8 起 REALITY 服务端移除 &lt;code&gt;minClientVer&lt;/code&gt; 限制并强制要求 ClientHello 携带 X25519MLKEM768，奇怪和过时的指纹会直接被当回落流量处理&lt;/li&gt;
&lt;li&gt;XHTTP 目前只有 Xray 原生支持；sing-box 主线尚未内置，有社区 fork 支持；Mihomo 自 2026 年 3 月（约 v1.19.22）起支持 &lt;code&gt;xhttp-opts&lt;/code&gt;。一些订阅转换工具会把 XHTTP 的某些字段静默丢弃，实践中需留意&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>yt-dlp 入门笔记</title><link>https://blog.cobweb11.top/posts/yt-dlp/</link><guid isPermaLink="true">https://blog.cobweb11.top/posts/yt-dlp/</guid><description>yt-dlp 的常用参数与用法速查。</description><pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;准备&lt;/h2&gt;
&lt;p&gt;仓库地址：&lt;a href=&quot;https://github.com/yt-dlp/yt-dlp&quot;&gt;yt-dlp/yt-dlp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;依赖确认：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffmpeg -version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更新：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp -U
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置文件&lt;/h2&gt;
&lt;p&gt;路径 &lt;code&gt;%AppData%\yt-dlp\config&lt;/code&gt; 或 exe 同目录下的 &lt;code&gt;config.txt&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-f &quot;bv*[height&amp;lt;=1080]+ba/b[height&amp;lt;=1080]&quot;  # 选不超过1080p的最佳视频+音频，或退而选最佳单文件
--merge-output-format mp4                  # 合并输出为 MP4 容器
--embed-metadata --embed-chapters          # 嵌入元数据和章节信息
--embed-thumbnail --convert-thumbnails jpg # 嵌入封面，并统一转成 jpg
--embed-subs --sub-langs &quot;zh.*,en.*&quot; --convert-subs srt  # 嵌入中英文字幕，并转成 srt
--sponsorblock-remove default              # 按 SponsorBlock 移除默认类别片段（赞助、片头片尾等）
--windows-filenames --trim-filenames 150   # 文件名兼容 Windows，主体最长 150 字符
--newline                                  # 进度按新行输出，适合日志/脚本
-o &quot;%(uploader)s/%(upload_date&amp;gt;%Y-%m-%d)s - %(title)s [%(id)s].%(ext)s&quot;
# 输出路径：上传者/上传日期(YYYY-MM-DD) - 标题 [视频ID].扩展名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载 1080p 视频 -&amp;gt; 合并 MP4 -&amp;gt; 嵌入封面/字幕/章节/元数据 -&amp;gt; 去赞助片段 -&amp;gt; 按频道和日期自动归档&lt;/p&gt;
&lt;p&gt;建好之后 &lt;code&gt;yt-dlp &amp;lt;URL&amp;gt;&lt;/code&gt; 就是完整流程，对所有调用生效，忽略预设配置加 &lt;code&gt;--ignore-config&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;选格式&lt;/h2&gt;
&lt;h3&gt;按参数选&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;-f&lt;/code&gt; 的写法里，&lt;code&gt;bv*+ba/b&lt;/code&gt; 展开是 &lt;code&gt;bestvideo*+bestaudio/best&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bv&lt;/code&gt; 是纯视频流，&lt;code&gt;ba&lt;/code&gt; 是纯音频流，加 &lt;code&gt;*&lt;/code&gt; 表示不限于 video-only&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; 表示视频流和音频流分别下载后用 ffmpeg 合并&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; 是&quot;或&quot;，前面不满足时用后面的&lt;/li&gt;
&lt;li&gt;结尾的 &lt;code&gt;b&lt;/code&gt; 是含音视频的单文件，作兜底&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp -F &amp;lt;URL&amp;gt;                            # 列出格式
yt-dlp -f &quot;bv*+ba/b&quot; &amp;lt;URL&amp;gt;                 # 默认最佳
yt-dlp -S &quot;res:1080,vcodec:h264&quot; &amp;lt;URL&amp;gt;     # 限 1080p，优先 H.264
yt-dlp -f &quot;bv[height&amp;lt;=1080]+ba/b&quot; &amp;lt;URL&amp;gt;    # 硬限高度
yt-dlp -S &quot;ext&quot; &amp;lt;URL&amp;gt;                      # 优先 mp4 容器
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;想要 mp4 又不想踩兼容问题，用 &lt;code&gt;-f &quot;bv[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/bv*+ba/b&quot;&lt;/code&gt;，它在选流阶段就限定容器。用 &lt;code&gt;-f &quot;bv+ba&quot; --merge-output-format mp4&lt;/code&gt; 是先挑最佳流再硬封，可能把 VP9 装进 mp4。&lt;/p&gt;
&lt;h3&gt;按 ID 选&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;-F&lt;/code&gt; 列出来的 ID 可以直接写进 &lt;code&gt;-f&lt;/code&gt;，多条用 &lt;code&gt;+&lt;/code&gt; 合并：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp -f &amp;lt;格式ID&amp;gt; &amp;lt;&amp;lt;URL&amp;gt;&amp;gt;
yt-dlp -f 137+140 &amp;lt;URL&amp;gt;                 # 视频流 + 音频流
yt-dlp -f 30032+bestaudio &amp;lt;URL&amp;gt;         # ID 和选择器可以混着写
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只写视频流 ID 会得到没有声音的文件。ID 是每个视频各自的，换视频要重新 &lt;code&gt;-F&lt;/code&gt;，同一站点不同视频的可用 ID 也会不一样。&lt;/p&gt;
&lt;p&gt;别写 &lt;code&gt;[format_id=137]&lt;/code&gt; 这种过滤表达式，ID 是纯数字时它不生效，会报 &lt;code&gt;Requested format is not available&lt;/code&gt;，直接用裸 ID。&lt;/p&gt;
&lt;h2&gt;音频&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp -x --audio-format mp3 --audio-quality 0 &amp;lt;URL&amp;gt;   # 转 mp3，0 是最高
yt-dlp -f &quot;ba[ext=m4a]/ba&quot; -x &amp;lt;URL&amp;gt;                    # 直取 m4a，不重编码，最快
yt-dlp -x --audio-format best &amp;lt;URL&amp;gt;                    # 不转码，只抽流
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--audio-format&lt;/code&gt; 可选 &lt;code&gt;best&lt;/code&gt; / &lt;code&gt;aac&lt;/code&gt; / &lt;code&gt;alac&lt;/code&gt; / &lt;code&gt;flac&lt;/code&gt; / &lt;code&gt;m4a&lt;/code&gt; / &lt;code&gt;mp3&lt;/code&gt; / &lt;code&gt;opus&lt;/code&gt; / &lt;code&gt;vorbis&lt;/code&gt; / &lt;code&gt;wav&lt;/code&gt;，指定 &lt;code&gt;aac&lt;/code&gt; 得到 .m4a，&lt;code&gt;vorbis&lt;/code&gt; 得到 .ogg。&lt;/p&gt;
&lt;p&gt;YouTube 的音轨最高 256 kbps，转 320K mp3 就是最高。&lt;/p&gt;
&lt;h2&gt;字幕&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 列出该视频可用的字幕及语言代码
yt-dlp --list-subs &amp;lt;URL&amp;gt;

# 下载视频，并下载简体中文、繁体中文、英文人工字幕，转成 SRT 后嵌入视频
yt-dlp --write-subs --sub-langs &quot;zh-Hans,zh-Hant,en&quot; --convert-subs srt --embed-subs &amp;lt;URL&amp;gt;

# 下载视频，并额外下载英文自动生成字幕
yt-dlp --write-auto-subs --sub-langs &quot;en&quot; &amp;lt;URL&amp;gt;

# 不下载视频，只下载英文自动字幕，转成 SRT，并按“标题.srt”保存
yt-dlp --skip-download --write-auto-subs --convert-subs srt -o &quot;%(title)s.%(ext)s&quot; &amp;lt;URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;播放列表与批量&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp --batch-file &amp;lt;URL&amp;gt;s.txt &amp;lt;URL&amp;gt;                    # 文件里每行一个 &amp;lt;URL&amp;gt;
yt-dlp --download-archive archive.txt &amp;lt;URL&amp;gt;             # 下过的记进文件，重跑自动跳过
yt-dlp --playlist-items 1-10,15,20- &amp;lt;URL&amp;gt;               # 只下其中几集
yt-dlp --dateafter 20250101 &amp;lt;URL&amp;gt;                       # 只下这个日期之后的
yt-dlp --match-filters &quot;duration&amp;gt;120 &amp;amp; !is_live&quot; &amp;lt;URL&amp;gt;  # 只下时长超过 120 秒且非直播的视频
yt-dlp --flat-playlist --print &quot;%(playlist_index)s`t%(title)s&quot; &amp;lt;URL&amp;gt;   # 只看清单不下载
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;频道归档用 &lt;code&gt;--download-archive&lt;/code&gt; 配 &lt;code&gt;-o &quot;%(playlist_index)03d - %(title)s.%(ext)s&quot;&lt;/code&gt;，定时跑一次就是增量下载。&lt;/p&gt;
&lt;h2&gt;搜索与 cookies&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# YouTube 搜索前 10 个结果，只列出标题和链接
yt-dlp &quot;ytsearch10:关键词&quot; --flat-playlist --print &quot;%(title)s | %(url)s&quot;

# YouTube 搜索“关键词”，取前 10 个结果，最多只下载其中 3 个
yt-dlp &quot;ytsearch10:关键词&quot; --max-downloads 3

# YouTube 搜索“关键词”，取按上传日期排序的前 5 个结果并下载
yt-dlp &quot;ytsearchdate5:关键词&quot;

# 下载 YouTube 收藏夹（:ytfav），用 Firefox 的 cookies 完成登录认证
yt-dlp &quot;:ytfav&quot; --cookies-from-browser firefox
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同类的还有 &lt;code&gt;:ytwatchlater&lt;/code&gt;、&lt;code&gt;:ythistory&lt;/code&gt;、&lt;code&gt;:ytrec&lt;/code&gt;，都需要 cookies。&lt;/p&gt;
&lt;p&gt;配置里加 &lt;code&gt;--default-search &quot;ytsearch1:&quot;&lt;/code&gt; 之后可以直接 &lt;code&gt;yt-dlp 关键词&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;cookies 两种给法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp --cookies-from-browser edge &amp;lt;URL&amp;gt;
yt-dlp --cookies cookies.txt &amp;lt;URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chrome 开着时可能读不到它的 cookie 库，Edge 和 Firefox 稳一些。手动导出用浏览器扩展，Chrome 用 ExportThisCookies，Firefox 用 Export Cookies。&lt;/p&gt;
&lt;h2&gt;切片、直播、去赞助&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp --download-sections &quot;*10:00-15:00&quot; &amp;lt;URL&amp;gt;   # 需要 ffmpeg
yt-dlp --download-sections &quot;*from-2:30&quot; &amp;lt;URL&amp;gt;     # 从 2:30 到结尾
yt-dlp --split-chapters &amp;lt;URL&amp;gt;                     # 按章节切成多个文件
yt-dlp --live-from-start &amp;lt;URL&amp;gt;                    # 直播从头录
yt-dlp --wait-for-video 60 &amp;lt;URL&amp;gt;                  # 等开播，每 60 秒探一次
yt-dlp -N 8 &amp;lt;URL&amp;gt;                                 # 并发分片，提速明显

yt-dlp --sponsorblock-remove default &amp;lt;URL&amp;gt;        # 去掉赞助片段
yt-dlp --sponsorblock-mark all &amp;lt;URL&amp;gt;              # 只标记成章节，不删
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;default&lt;/code&gt; 等于 &lt;code&gt;all,-filler&lt;/code&gt;。需要能连上 sponsor.space。&lt;/p&gt;
&lt;h2&gt;代理&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp --proxy http://127.0.0.1:10086 &amp;lt;URL&amp;gt;
yt-dlp --proxy socks5://127.0.0.1:10086 &amp;lt;URL&amp;gt;
yt-dlp --proxy &quot;&quot; &amp;lt;URL&amp;gt;                        # 强制直连
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yt-dlp 不读代理环境变量，只认 &lt;code&gt;--proxy&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;YouTube 报 403 时先试 &lt;code&gt;--extractor-args &quot;youtube:player_client=web_safari&quot;&lt;/code&gt;，可选值会随 Google 的改动变，遇到问题去 issue 区看当下推荐值，有些站点要 &lt;code&gt;--impersonate chrome&lt;/code&gt; 才能过。&lt;/p&gt;
&lt;h2&gt;控制台里的中文&lt;/h2&gt;
&lt;p&gt;控制台显示的中文标题可能是乱码，但文件名和文件内容本身没问题。&lt;/p&gt;
&lt;p&gt;只要中文标题就写进文件再读：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yt-dlp --simulate --print-to-file &quot;%(title)s&quot; title.txt &amp;lt;URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其他注意&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PowerShell 里 &lt;code&gt;%&lt;/code&gt; 不用转义，cmd 里才要写成 &lt;code&gt;%%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;参数里有 &lt;code&gt;[&lt;/code&gt; &lt;code&gt;]&lt;/code&gt; &lt;code&gt;*&lt;/code&gt; 时加引号&lt;/li&gt;
&lt;li&gt;输出模板里的 &lt;code&gt;/&lt;/code&gt; 会建子目录&lt;/li&gt;
&lt;li&gt;文件名太长用 &lt;code&gt;--trim-filenames&lt;/code&gt;，非法字符用 &lt;code&gt;--windows-filenames&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;常见字段：&lt;code&gt;%(title)s&lt;/code&gt; 标题，&lt;code&gt;%(id)s&lt;/code&gt; 视频 ID，&lt;code&gt;%(ext)s&lt;/code&gt; 扩展名，&lt;code&gt;%(uploader)s&lt;/code&gt; 上传者，&lt;code&gt;%(upload_date)s&lt;/code&gt; 上传日期，&lt;code&gt;%(playlist_index)s&lt;/code&gt; 列表序号，&lt;code&gt;%(height)s&lt;/code&gt; 分辨率高度&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>