- Shell 100%
| .gitignore | ||
| config.json.example | ||
| proxy-lan-off.sh | ||
| proxy-lan-on.sh | ||
| proxy-local-off.sh | ||
| proxy-local-on.sh | ||
| proxy-status.sh | ||
| README.md | ||
Debian 13 使用 sing-box 搭建透明代理网关(便携旁路方案)
适用场景:笔记本随身携带,本机和手机通过 TUN 模式透明代理上网,国内直连、国外走代理节点。 支持 sing-box 所有协议(AnyTLS / VLESS / Trojan / Shadowsocks / Hysteria2 等),仅需替换配置文件中的出站节点。 一切都不开机自启,手动开启,回家后一键完全恢复。
一、架构总览
手机(网关指向本机,DNS 指向公网 IP) ──→ 策略路由 ──→ sing-tun ──┐
├── sing-box
本机浏览器/终端 ──→ TUN (auto_route) ──→ sing-tun ──┘ │
│
┌── DNS: 全量 → AliDNS DoH (223.5.5.5) │
├── 路由: geosite-geolocation-cn + geoip-cn → direct-out│
└── 路由: 其余 → 代理节点 │
两种使用模式:
| 模式 | 本机 | LAN 设备 | 原理 |
|---|---|---|---|
| 仅本机 | ✅ TUN auto_route |
— | sing-box 自动创建 TUN 路由接管本机流量 |
| 本机 + LAN | ✅ TUN | ✅ 策略路由 | ip rule + ip route 把手机流量送入 sing-tun,与本机共用一套 TUN 处理 |
二、安装 sing-box(apt 方式)
sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
echo 'Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc' | sudo tee /etc/apt/sources.list.d/sagernet.sources
sudo apt update && sudo apt install sing-box
更新 / 卸载
sudo apt update && sudo apt upgrade sing-box # 更新
sudo apt remove sing-box # 卸载
sudo rm /etc/apt/sources.list.d/sagernet.sources
sudo rm -r /etc/sing-box
三、版本兼容性
通过 apt 安装的 sing-box 会跟随发行版滚动。以下字段在不同版本已被废弃/移除,配置时注意:
| 废弃字段 | 移除版本 | 替代方案 |
|---|---|---|
dns.servers[].address |
1.12.0 | type + server |
dns.fakeip(顶层) |
1.12.0 | 合并到 type: "fakeip" |
inbounds[].sniff |
1.13.0 | route.rules[].action: "sniff" |
outbounds[].type: "dns" |
1.13.0 | route.rules[].action: "hijack-dns" |
inbounds[].inet4_address |
1.12.0 | address |
domain_strategy |
1.12.0 | domain_resolver.strategy |
迁移文档:https://sing-box.sagernet.org/migration/
四、配置文件 /etc/sing-box/config.json
{
"log": {
"level": "info",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "local",
"type": "https",
"server": "223.5.5.5"
}
]
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "sing-tun",
"address": "172.19.0.1/30",
"auto_route": true,
"strict_route": true,
"stack": "mixed"
},
{
"type": "mixed",
"tag": "mixed-in",
"listen": "0.0.0.0",
"listen_port": 7890,
"tcp_fast_open": true
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct-out"
},
{
"type": "anytls",
"tag": "proxy",
"server": "YOUR_SERVER",
"server_port": YOUR_PORT,
"password": "YOUR_PASSWORD",
"tls": {
"enabled": true,
"server_name": "YOUR_SNI",
"insecure": false
}
}
],
"route": {
"rule_set": [
{
"type": "remote",
"tag": "geosite-geolocation-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
},
{
"type": "remote",
"tag": "geosite-geolocation-!cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
},
{
"type": "remote",
"tag": "geoip-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
}
],
"rules": [
{
"action": "sniff"
},
{
"type": "logical",
"mode": "or",
"rules": [
{
"protocol": "dns"
},
{
"port": 53
}
],
"action": "hijack-dns"
},
{
"ip_is_private": true,
"outbound": "direct-out"
},
{
"type": "logical",
"mode": "or",
"rules": [
{
"port": 853
},
{
"network": "udp",
"port": 443
},
{
"protocol": "stun"
}
],
"action": "reject"
},
{
"rule_set": "geosite-geolocation-cn",
"outbound": "direct-out"
},
{
"type": "logical",
"mode": "and",
"rules": [
{
"rule_set": "geoip-cn"
},
{
"rule_set": "geosite-geolocation-!cn",
"invert": true
}
],
"outbound": "direct-out"
}
],
"auto_detect_interface": true,
"default_domain_resolver": "local",
"final": "proxy"
},
"experimental": {
"cache_file": {
"enabled": true,
"path": "/var/lib/sing-box/cache.db",
"store_rdrc": true
},
"clash_api": {
"default_mode": "Enhanced"
}
}
}
配置修改后先检查语法,通过后再启动:
sing-box check -c /etc/sing-box/config.json
节点信息填充
出站类型(
type)可替换为 sing-box 支持的任何协议:anytls/vless/trojan/shadowsocks/hysteria2等。以下以 AnyTLS 为例。
| 占位符 | 说明 |
|---|---|
YOUR_SERVER |
真实连接地址(server),如 tqvnarulexmo.com |
YOUR_PORT |
端口,如 6602 |
YOUR_PASSWORD |
密码 |
YOUR_SNI |
TLS SNI(server_name),如 sg3.ninjanode.pro |
tls.insecure |
按需设置 true/false |
可选字段(从 passwall 配置提取):
"min_idle_session": 5,
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s"
五、五个脚本
| 脚本 | 功能 |
|---|---|
proxy-local-on.sh |
仅本机代理:启动 sing-box |
proxy-local-off.sh |
仅本机代理:停止 sing-box |
proxy-lan-on.sh |
本机 + LAN:自动检测网段,ip_forward + 策略路由 → sing-tun |
proxy-lan-off.sh |
停止所有:停止 sing-box + 删除策略路由 + 关闭 ip_forward |
proxy-status.sh |
检查当前状态:运行中/未运行、local/LAN 模式 |
仅本机模式
./proxy-local-on.sh # 启动
./proxy-local-off.sh # 停止
本机 + LAN 模式
# 先停止 local 模式,再启动 LAN 模式(二者互斥)
./proxy-local-off.sh
./proxy-lan-on.sh
手机将网关设为本机 IP,DNS 设为国内公共 DNS(如 114.114.114.114 或 223.5.5.5),即可透明代理。
DNS 为什么不能设成本机或路由器 IP? 见第八关。
六、LAN 模式工作原理
手机(LAN_IP)发送 TCP 到 baidu.com:443
│
▼ (WAN_IF,如 wlp3s0)
┌─────────────────────────┐
│ ip rule: │
│ from $LAN_CIDR │
│ iif $WAN_IF │──→ table 101 ──→ default via sing-tun
│ lookup 101 │
└─────────────────────────┘
│
▼
sing-tun → sing-box TUN 入站 → 路由规则 → direct-out/proxy
LAN_CIDR 和 WAN_IF 由脚本自动检测,无需手动配置。
这是"软路由"方案:手机流量到笔记本后被策略路由送入 TUN 接口,和本机流量共享同一套 sing-box 处理。相比 TProxy,这完全规避了内核 TProxy TCP 投递的兼容性问题(详见第九节)。
七、服务管理
sudo systemctl status sing-box # 查看状态
sudo journalctl -u sing-box -f # 实时日志
不要执行
systemctl enable sing-box,确保不开机自启。
八、效果覆盖表
| 场景 | 实现方式 |
|---|---|
| 本机浏览器/终端 | TUN auto_route 自动接管 |
| 手机/平板等 LAN 设备 | 设备网关指向本机,DNS 指向公网 IP → 策略路由 → TUN |
| 国内域名/IP | geosite-geolocation-cn + geoip-cn → direct-out |
| 国外 IP | final → 代理节点 |
| 防 DNS 泄漏 | 阻断 DoT(853)/QUIC(443)/STUN,DNS 劫持端口 53 |
| 手机 Chrome 跳转 google.cn | 访问 google.com/ncr 一次种 cookie,永久解决 |
九、调通历程(辛酸史)
第一关:版本地狱
预想的配置基于 sing-box 旧版语法。实际 apt 装的是 1.13.12,以下字段全部失效:
- DNS server 的
address→ 需要type+server dns.fakeip顶层块 → 合并到type: "fakeip"内sniff在 inbound 里 → 改为route.rules[].action: "sniff"dns类型 outbound → 改为action: "hijack-dns"inet4_address→ 合并为address
反复踩坑,每修复一个就报下一个,前后迭代了 10+ 个配置版本才通过语法检查。
第二关:DNS 死循环
cn-dns(DoH to doh.pub)用 detour: direct-out,direct-out 又依赖 default_domain_resolver: cn-dns 解析域名 → 无限循环。最终砍掉 DoH,改用 local 类型(系统 DNS),简化 DNS 链。
最终方案: 经过多轮测试(Google DNS 8.8.8.8 被 GFW 封锁、Cloudflare 1.1.1.1 同样被封锁、fakeip 的非 SNI 协议缺陷),最终采用单 AliDNS DoH(223.5.5.5)。阿里公共递归 DNS 在国内直连零延迟,对国外域名同样返回真实 IP。路由规则独立负责直连/代理分流,DNS 与路由完全解耦。手机 Chrome 跳转 google.cn/m 是 Android 浏览器的网络检测行为,访问一次
google.com/ncr永久解决。
第三关:规则集下载走代理死循环
raw.githubusercontent.com 在国内无法直连,规则集下载失败。去掉 download_detour 后下载走代理。但代理需要节点信息——前期一直用占位符 YOUR_SERVER 导致代理不通,规则集也下不了。
第四关:TProxy 的 TCP 黑洞(最耗时的关卡)
为了让手机能用,先后尝试了:
- nftables TProxy:UDP DNS 能劫持,TCP 经过 counter 计数但永远送不到 sing-box
- iptables TPROXY:同样的结果——UDP 通 TCP 不通
- iptables TPROXY + fwmark 策略路由:还是不行
- iptables TPROXY + MARK --set-mark 1:依然不行
tcpdump 确认手机 TCP SYN 到了 wlp3s0,iptables/nftables counter 显示数百个 TCP 包被 TPROXY 处理,但 sing-box 的 TProxy 入站零 TCP 日志。最终确认是 Debian 内核(实际上并非 debian 13,而是我的 debian testing/debian forky,内核版本 7.0.10+deb14-amd64)上 nftables/iptables TProxy 对 TCP 的投递存在兼容性问题。
ps: 下面说到内核或 TCP 投递存在的兼容性问题均一样,都是我在我的 debian testing 上测试不通过,有可能是内核
7.0.10+deb14-amd64过新,debian 源当中的各个包没有经过足够测试,导致的问题。
第五关:策略路由 → TUN(破局)
放弃 TProxy 方案,改用"软路由"思路:
ip rule add from 10.10.10.0/24 iif wlp3s0 table 101
ip route add default dev sing-tun table 101
手机流量不再做包重定向,而是在路由层面直接送入 sing-tun 接口,和本机一样走 sing-box 的 TUN 入站处理。手机 DNS 也被 hijack-dns 自动接管。
第六关:FakeIP 引爆的 Android 联网检测
手机上网功能正常后,Chrome 始终显示"未连接到互联网"。根因是 Android 的 connectivitycheck.gstatic.com 被 fake-dns 解析成假 IP 198.18.0.x。此域名触发联网检测逻辑——Android 向这个假 IP 发 HTTP 请求,永远得不到 204 响应,系统判定无网。
修复:DNS 规则中让 .gstatic.com 和 .googleapis.com 走 local-dns 解析真实 IP。路由不做任何特殊处理,随 final → proxy 正常代理即可。
注意: 当前配置已完全移除 FakeIP。全量使用 AliDNS DoH 做真实 IP 解析,此问题不再需要特殊处理。
第七关:FakeIP 黑洞——SSH 等非 SNI 协议的直连站点连通失败
浏览器访问一个非 geosite-cn 域名的国内站(如自建 Forgejo 的 git.example.xyz)完全正常,但 SSH / git pull / git push 一直卡住。nslookup 返回的是 198.18.0.x 的假 IP。
根本原因在于 FakeIP + 代理出站对域名信息传递的依赖差异:
HTTPS 浏览器:DNS → fakeip 198.18.0.x → TUN → 路由 → proxy
代理节点嗅探 TLS SNI(git.example.xyz) → 连接真实服务器 ✅
SSH / git: DNS → fakeip 198.18.0.x → TUN → 路由 → proxy
代理节点看到的是 198.18.0.4:22222 → 无 SNI 可嗅探 → 无法连接 ❌
HTTPS 的 TLS 握手中携带了 SNI 字段,代理节点能从中提取域名并解析连接到真实的 frps 服务器。SSH 是裸 TCP 协议,没有域名信息,代理节点只能看到一个假 IP,无法确定真实目的地。
修复:在 DNS 规则的 domain_suffix 中添加你的域名,走 local-dns 解析真实 IP:
"domain_suffix": [".gstatic.com", ".googleapis.com", "your-domain.xyz"],
"server": "local-dns"
local-dns 返回真实的国内 frps 服务器 IP,路由规则中的 geoip-cn 自动命中 → direct-out 直连。无需额外路由规则。
如果你需要添加多个私有域名,直接追加到
domain_suffix数组即可。此方案适用于任何不依赖 SNI 的 TCP 协议(SSH、FTP、rsync 等)指向国内服务器的场景。注意: 当前配置已完全移除 FakeIP。全量使用 AliDNS DoH 做真实 IP 解析,非 SNI 协议(SSH/git 等)直连国内站点不再有此问题。
第八关:LAN 设备的 DNS 劫持陷阱
手机 DNS 如果设为旁路网关自身的 IP(如 192.168.5.106),DNS 包的目标地址就是本机。内核识别为本地投递,包直接进 INPUT 链,不经过 ip_forward,也不进入策略路由表 table 101,更不会到达 sing-tun。hijack-dns 完全抓不到,手机 DNS 超时后回退到 ISP 的污染 DNS。
为什么 ImmortalWrt+passwall 可以把 DNS 设成旁路网关 IP? Passwall 自带 dnsmasq/smartdns 等本地 DNS 服务,直接监听在旁路网关的 53 端口上。LAN 设备的 DNS 查询到达网关 IP:53 → INPUT 链 → 本地 DNS 服务处理 → 返回结果。这是"本地服务响应",不需要 ip_forward 或 TUN 劫持。而本项目没有本地 DNS 服务,完全依赖 hijack-dns 在转发路径(FORWARD → TUN)中拦截,因此 DNS 目标必须是公网 IP 才能进入转发路径。
❌ DNS → 192.168.5.106(本机)→ INPUT 链 → 被本地 DNS 栈处理或丢弃 → 不进 TUN
✅ DNS → 114.114.114.114(公网 IP)→ 策略路由 → sing-tun → hijack-dns 劫持
正确做法: LAN 设备的 DNS 设为国内公共 DNS 服务的 IP,如 114.114.114.114(DNSPod)或 223.5.5.5(AliDNS)。不要设成路由器 IP,也不要设成旁路网关 IP。DNS 目标是公网 IP 才能通过策略路由送入 TUN 被劫持。
主 DNS 设公共 DNS IP 即可,不要配置辅助 DNS。否则第一条超时后辅助 DNS 走直连返回污染结果。
十、扩展阅读:Linux 透明代理方案对比
方案一:TProxy(内核包重定向)
原理: 用 iptables/nftables 在 PREROUTING 钩子上拦截所有流量,通过 TPROXY 目标将包"透明地"重定向到本地代理进程端口。代理使用 IP_TRANSPARENT socket 选项接收,保留原始目标地址。
客户端 → 网卡 → [PREROUTING TPROXY] → 代理端口 → 出站
优点:
- 不修改路由表,纯包级别操作
- 性能损耗极低(内核层面处理)
- 可以按端口、协议、源 IP 精细化控制哪些流量走代理
缺点:
- 依赖内核模块
nf_tproxy_ipv4/xt_TPROXY - 不同内核/发行版兼容性差异大(我们在 Debian 13 内核上 TCP 投递彻底失败)
- 调试困难,出了问题只能抓包 + 看内核 counter
rp_filter、ip_forward、fwmark 策略路由等因素相互影响,排查链路长
方案二:策略路由 + TUN(本项目最终方案)
原理: 不为包做"手术",而是用 ip rule 把特定流量(如手机所在的 LAN 子网)的策略路由指向 TUN 接口。流量到了 TUN 就和本机流量一样被 sing-box 接管。
客户端 → 网卡 → [策略路由] → sing-tun → sing-box → 出站
为什么叫"软路由": 本质上是让笔记本扮演路由器的角色——收到手机发来的 IP 包后,根据策略路由表决定下一跳(dev sing-tun),而非按默认路由转发。和硬路由的唯一区别是转发逻辑由 Linux 内核 + sing-box 实现,不依赖专用硬件。
与 ImmortalWrt/passwall 软路由的区别
ImmortalWrt(OpenWrt 分支)+ passwall 是常见的 x86 软路由方案。我们的 Debian + sing-box + 策略路由与之有本质不同:
| ImmortalWrt + passwall | Debian + sing-box(本项目) | |
|---|---|---|
| 角色 | 网络主网关——所有设备流量必经此地 | 旁路设备——LAN 上的一台普通机器 |
| 网络拓扑 | 路由器 NAT + DHCP + DNS + 防火墙 | 只做透明代理,不干预 DHCP/NAT/防火墙 |
| 流量入口 | 所有设备默认网关就是它 | 只有主动将网关指向它的设备才走代理 |
| 透明代理方式 | iptables TProxy / REDIRECT(内核模块成熟) | 策略路由 → TUN(绕开 TProxy 兼容性问题) |
| DNS | 自己跑 dnsmasq/smartdns | 不提供 DNS 服务,sing-box hijack-dns 劫持即可 |
| 接管范围 | 全家设备自动覆盖 | 谁改网关谁用,灵活但需手动配置 |
| 部署影响 | 断网部署——路由器下线全家断网 | 无感部署——停掉 sing-box 网络立即恢复原状 |
| 适用场景 | 家庭/办公室固定部署 | 便携笔记本,随走随用 |
简而言之:ImmortalWrt 是"修路的人"(改网络基础设施),我们是"指路的人"(给个别的设备指一条经过代理的路)。前者全家自动生效、断电全网瘫痪;后者随意插拔、停服务零影响。
与 ImmortalWrt/passwall 旁路网关的区别
当你把 ImmortalWrt+passwall 部署成旁路网关(主路由是硬路由),这和本项目的定位其实更接近。但实现上有本质不同:
| ImmortalWrt 旁路网关 | Debian + sing-box(本项目) | |
|---|---|---|
| 操作系统 | OpenWrt/ImmortalWrt(专为路由优化的 Linux) | Debian 通用桌面 Linux |
| 内核 | OpenWrt 定制内核 | Debian 主线内核 |
| TProxy 可用性 | ✅ 内核完整支持,passwall 标准方案 | ❌ Debian 内核 TProxy TCP 投递 bug,无法使用 |
| 透明代理方式 | iptables TProxy + fwmark 策略路由 | 策略路由 → TUN(ip rule + sing-tun) |
| 配置界面 | Web UI(passwall 面板) | JSON 配置文件 + shell 脚本 |
| 网络接管 | 同网段设备改网关指向旁路 IP | 同网段设备改网关指向本机 IP |
| 自身上网 | 勾选"路由器本机代理"+"客户端代理"即可,Web UI 两个复选框覆盖所有设备 | 本机 TUN auto_route 自动接管,无需额外配置 |
| 便携性 | 固定在局域网,需要外接电源和网线 | 笔记本 WiFi 连接,随走随用 |
| 管理 | 作为独立盒子运行,需远程 SSH 或 Web | 直接在桌面终端操作 |
| 调试复杂度 | passwall 封装了细节,出问题靠猜 | 全栈可见——规则、路由、日志全透明 |
核心差异就一个:Debian 主线内核的 TProxy 有 TCP 投递 bug,迫使我们必须走策略路由 → TUN 这条路。在 ImmortalWrt 上 TProxy 是标准答案,在 Debian 上反而成了死胡同。
优点:
- 不依赖任何防火墙模块,只用标准
ip route/ip rule - 完全绕开了 TProxy 的内核兼容性问题
- 和本机流量共享同一套 TUN 处理逻辑,配置统一
- 调试直观:
ip rule show/ip route show table 101一眼看出流向
缺点:
- 需要开启
ip_forward(本机变成路由器角色,增加攻击面,但在信任的局域网内可接受) - 不能按端口/协议精细化控制(所有匹配子网的流量全部进 TUN)
- 依赖 sing-box 的 TUN 入站正常工作
方案三:REDIRECT / DNAT(端口重定向)
原理: 用 iptables/nftables 的 REDIRECT 或 DNAT 将流量目标地址改写为本地代理端口。代理收到后解析原始目标(通过 SO_ORIGINAL_DST 获取 DNAT 前的地址)。
客户端 → [DNAT dst→127.0.0.1:port] → 代理端口 → 出站
优点: 最简单,REDIRECT 一条规则就能工作,无需内核模块(标准 NAT 即可)。
缺点:
- 目标地址被改写,代理必须支持获取 SO_ORIGINAL_DST
- 仅适用于 TCP 和部分 UDP 场景
- 代理看到的来源地址是 127.0.0.1,无法区分客户端
方案四:TUN 全局路由(VPN 模式)
原理: 创建 TUN 虚拟网卡,将所有流量(0.0.0.0/0)路由到 TUN,代理进程完全接管 IP 层。sing-box 的 auto_route 就是这个模式。
所有流量 → sing-tun → sing-box → 出站
优点: 最彻底,接管一切;Android/iOS VPN API 的标准做法。
缺点:
- 本机流量和路由复杂的需要精细配置 bypass 规则(如局域网、DNS 等)
- 适用本机;做网关给其他设备用需要配合方案二
方案五:HTTP/SOCKS 正向代理(非透明)
原理: 客户端主动配置代理地址(如 localhost:7890),浏览器/App 显式连接代理。
优点: 不需要任何系统级配置,最简单可靠。
缺点: 非透明——终端 App 必须支持代理设置,无法覆盖所有流量。
本项目的选择
| 场景 | 方案 |
|---|---|
| 本机 | TUN 全局路由(方案四,sing-box auto_route) |
| LAN 设备 | 策略路由 → TUN(方案二) |
这是 方案二 + 方案四 的组合,用 TUN 的全局代理能力配合策略路由的流量引导,做到了本机和手机统一的透明代理效果,且完全绕开了 TProxy 的内核兼容性问题。
十一、参考资料
- sing-box 官方文档:https://sing-box.sagernet.org
- GitHub 仓库:https://github.com/SagerNet/sing-box
- AnyTLS 出站配置:https://sing-box.sagernet.org/configuration/outbound/anytls/
- 版本迁移指南:https://sing-box.sagernet.org/migration/