在服务器上跑一个本地代理,让终端和 Docker 都走它,是部署里很常见的一步。这篇按 sing-box 当前文档(1.14 线)整理,配置在本机用 sing-box check 与真实启动 + curl -x 双向验证过。
如果你照抄的是 2023–2024 年的教程,大概率会在启动时直接失败:inbounds[].sniff 在 1.13.0 被移除、geoip 路由规则在 1.12.0 被移除,两者都会让 1.13 以上的版本拒绝加载配置。

1. 什么是 Sing-box

sing-box 是一个通用代理平台,服务端与客户端共用一个二进制。它支持 VMess、VLESS、Trojan、Shadowsocks、Hysteria、WireGuard 等协议,可以在服务器上把远端代理转成(或直接暴露为)本机可用的 SOCKS5 / HTTP 代理。

它的配置格式在 1.x 期间改动频繁,每次大改都会把旧字段标记 deprecated,过一两个版本再移除。所以用 sing-box 的第一条经验是:配置以官方文档为准,并留意 Deprecated 页面

2. 准备工作

  • 一台能访问外网的服务器(Ubuntu 24.04 为例),有 sudo 权限
  • 可用的代理服务端信息(协议、地址、端口、UUID/密码、TLS 域名)
  • 已确认协议与 TLS 参数,这些要写进 outbounds

3. 安装

3.1 用官方 APT 仓库(推荐)

sing-box 有官方软件源,装完即带 systemd 服务,后续 apt upgrade 就能升级:

1
2
3
4
5
6
7
8
9
10
11
12
13
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-get update &&
sudo apt-get install sing-box

想要预览版把包名换成 sing-box-beta。这条命令取自官方 Package Manager 文档,keyring 与 Signed-By 是 apt 校验仓库签名的必需项,别省略。

3.2 用官方安装脚本(非 Debian 系或不想加源时)

1
curl -fsSL https://sing-box.app/install.sh | sh

脚本会从 GitHub Releases 取对应平台的最新包;需要指定版本时加 --version <version>,要 beta 加 --beta

3.3 手动下载二进制

只用某个版本的临时场景可以在 Releases 页面下包:

1
2
3
4
5
6
VERSION=1.14.1          # 到 https://github.com/SagerNet/sing-box/releases 确认当前版本
wget https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box-${VERSION}-linux-amd64.tar.gz
tar -xvf sing-box-${VERSION}-linux-amd64.tar.gz
cd sing-box-${VERSION}-linux-amd64
sudo cp sing-box /usr/local/bin/
sing-box version

发布页的文件名带版本号。旧教程里常见的 releases/latest/download/sing-box-linux-amd64.tar.gz 是固定链接的写法,现在会直接 404——这是我写这篇时实测确认的。Debian/Ubuntu 用 glibc 版本;Alpine 等 musl 系统要选文件名带 -musl 的包;ARM 服务器把 amd64 换成 arm64

4. 配置

4.1 配置文件位置

官方包把配置目录设在 /etc/sing-box

1
sudo vim /etc/sing-box/config.json

4.2 示例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
"log": {
"level": "info",
"timestamp": true
},
"inbounds": [
{
"type": "socks",
"tag": "socks-in",
"listen": "127.0.0.1",
"listen_port": 10808
},
{
"type": "http",
"tag": "http-in",
"listen": "127.0.0.1",
"listen_port": 10809
}
],
"outbounds": [
{
"type": "vmess",
"tag": "vmess-out",
"server": "your-server-ip",
"server_port": 443,
"uuid": "your-uuid-here",
"security": "auto",
"tls": {
"enabled": true,
"server_name": "your-domain.com"
}
},
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"ip_is_private": true,
"outbound": "direct"
}
]
}
}

要改的地方:

  • your-server-ip / your-uuid-here / your-domain.com 换成实际值
  • outbounds 按协议调整(Trojan、Shadowsocks 的字段名与 TLS 写法都不同,见官方 configuration 目录)
  • 想默认走代理,可在 route 里加 {"outbound": "vmess-out"} 作为兜底规则

4.3 两处与旧教程不同、且会直接导致启动失败的地方

域名嗅探不再是入站字段。 旧配置在每个 inbound 里写 "sniff": true;该字段在 1.11.0 弃用、1.13.0 移除,现在改为路由规则动作 {"action": "sniff"},效果等价于「在路由之前嗅探」。

GeoIP 规则已移除。 旧的 {"geoip": "private", "outbound": "direct"} 在 1.8.0 弃用、1.12.0 移除。判断内网地址改用 ip_is_private;更复杂的按国家/地区分流要用 rule_set 引用规则集文件,不再有内置 GeoIP 数据库。

两条都在官方 Deprecated 页有记录,升级或照抄配置前值得扫一眼:https://sing-box.sagernet.org/deprecated/

4.4 验证配置

1
sing-box check -c /etc/sing-box/config.json

配置正确时没有任何输出、退出码 0(用 echo $? 确认;旧版本会打印 config check passed,现在不打印了)。配置有问题会打印 FATAL 一行。把上面配置换回带 sniff 的旧写法,1.14.1 的实际输出是:

1
FATAL[0000] decode config at config.json: inbounds[0]: legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-legacy-inbound-fields-to-rule-actions

4.5 启动前先手动跑一次

check 只验证语法与字段,不证明代理真的能用。正式配服务之前先前台跑一遍:

1
sudo sing-box run -c /etc/sing-box/config.json

正常时能看到两个入站开始监听:

1
2
3
INFO inbound/socks[socks-in]: tcp server started at 127.0.0.1:10808
INFO inbound/http[http-in]: tcp server started at 127.0.0.1:10809
INFO sing-box started (0.00s)

然后另开一个终端,两个入站都测:

1
2
curl -x http://127.0.0.1:10809  -s -o /dev/null -w '%{http_code}\n' https://example.com
curl -x socks5h://127.0.0.1:10808 -s -o /dev/null -w '%{http_code}\n' https://example.com

两个都返回 200 说明入站、路由与出站串通了。日志里对应的连接长这样:

1
2
3
INFO [1013616736 0ms] inbound/socks[socks-in]: inbound connection from 127.0.0.1:53406
INFO [1013616736 2ms] inbound/socks[socks-in]: inbound connection to example.com:443
INFO [1013616736 2ms] outbound/direct[direct]: outbound connection to example.com:443

5. 配置系统服务

如果你用第 3.1 节的官方包安装,systemd 服务已经随包装好,直接用即可:

1
2
sudo systemctl enable --now sing-box
sudo systemctl status sing-box

日志按官方文档的写法看:

1
2
sudo journalctl -u sing-box --output cat -e     # 最近的日志
sudo journalctl -u sing-box --output cat -f # 跟踪输出

如果用的是手动下载的二进制,需要自己写 unit。下面是官方 deb 包内置的单元(/usr/lib/systemd/system/sing-box.service),只把可执行文件路径改成手动安装的位置 /usr/local/bin/sing-box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Unit]
Description=sing-box service
Documentation=https://sing-box.sagernet.org
After=network.target nss-lookup.target network-online.target

[Service]
User=sing-box
StateDirectory=sing-box
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10s
LimitNOFILE=infinity

[Install]
WantedBy=multi-user.target
1
2
3
sudo useradd --system --no-create-home sing-box   # 单元里指定了 User=sing-box
sudo systemctl daemon-reload
sudo systemctl enable --now sing-box

说明几点:

  • -D /var/lib/sing-box 是工作目录(缓存等运行期文件),StateDirectory= 会让 systemd 自动建好并处理权限
  • -C /etc/sing-box 表示加载该目录下所有 .json;只用一个文件时也可以写成 run -c /etc/sing-box/config.json
  • 官方包还带一个模板单元 sing-box@.servicesystemctl start sing-box@home 会加载 /etc/sing-box/home.json,适合一台机器跑多份配置
  • 那几项 CapabilityBoundingSet 是给 TUN / 透明代理预留的;纯 SOCKS/HTTP 入站用不上,但保留它们不影响运行

6. 测试代理连接

6.1 终端临时使用

1
2
3
4
5
export http_proxy=http://127.0.0.1:10809
export https_proxy=http://127.0.0.1:10809
export socks_proxy=socks5://127.0.0.1:10808

curl -x http://127.0.0.1:10809 https://ipinfo.io

6.2 永久写入 shell 配置

1
2
echo 'export http_proxy=http://127.0.0.1:10809' >> ~/.bashrc
echo 'export https_proxy=http://127.0.0.1:10809' >> ~/.bashrc

zsh 用户写 ~/.zshrc。注意环境变量只对交互式 shell 与显式继承的程序生效,守护进程(包括 Docker)不会读到它们,需要单独配置——见下一节。

7. 让 Docker 走代理

Docker 守护进程要单独告诉它代理地址,否则 docker pull 不会经过 sing-box:

1
2
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo vim /etc/systemd/system/docker.service.d/proxy.conf
1
2
3
4
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:10809/"
Environment="HTTPS_PROXY=http://127.0.0.1:10809/"
Environment="NO_PROXY=localhost,127.0.0.1,127.0.0.0/8,docker-registry.example.com,.corp"

NO_PROXY 里记得放两样东西:内网网段(避免容器间调用被绕进代理),以及你自己的私有 registry 域名。漏掉后者时,docker pull 会拿代理去访问一个只能内网到达的地址,报连接超时。

1
2
3
4
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show docker --property=Environment # 确认变量已生效
docker pull hello-world

上面这段只解决守护进程拉镜像这一层。还有两层它管不到,需要另外配:

容器里跑的程序(比如前面那个下载服务)不继承守护进程的代理,要在运行时注入:

1
docker run -e HTTP_PROXY=http://172.17.0.1:10809 -e HTTPS_PROXY=http://172.17.0.1:10809 ...

172.17.0.1 是默认 bridge 网络的网关地址,用 ip -4 addr show docker0 确认;自定义网络下形如 172.18.0.1,用 docker network inspect <网络名>Gateway 字段。注意 sing-box 的入站默认只监听 127.0.0.1,容器连不上,要把它绑到网关上:

1
2
3
4
5
6
{
"type": "http",
"tag": "http-in",
"listen": "172.17.0.1",
"listen_port": 10809
}

绑非回环地址之后务必用防火墙把来源限定在 docker 网段,否则同网段的机器都能拿它当代理用。

代理不可用时的排查顺序:先确认 sing-box 在跑(systemctl status sing-box),再看 docker pull 报的是连接超时还是 407 之类的鉴权错误。代理挂了而代理配置还在的话,所有镜像拉取都会失败,这是最先表现出来的症状。

docker build 的 RUN 步骤apt installpip install 这些)走的是客户端配置,写 ~/.docker/config.json

1
2
3
4
5
6
7
8
9
{
"proxies": {
"default": {
"httpProxy": "http://127.0.0.1:10809",
"httpsProxy": "http://127.0.0.1:10809",
"noProxy": "localhost,127.0.0.1,172.17.0.0/16"
}
}
}

两种方式等价;官方现在更推荐把守护进程代理写进 /etc/docker/daemon.jsonproxies 键,两处都配时 daemon.json 优先。

8. 故障排除

8.1 常见问题

服务启动失败:先看报错文本,字段被移除类的错误会直接点明版本:

1
2
sing-box check -c /etc/sing-box/config.json
sudo journalctl -u sing-box -n 50 --output cat

端口没有监听netstat 属于 net-tools,Ubuntu 24.04 默认不装,用 ss

1
sudo ss -tlnp | grep sing-box

测试代理端口是否可用telnet 同样默认未安装,用 curl 即可(nc 需要 sudo apt install netcat-openbsd):

1
curl -x socks5h://127.0.0.1:10808 -s -o /dev/null -w '%{http_code}\n' https://example.com

Docker 仍不走代理:确认 unit 覆盖生效(systemctl show docker --property=Environment),并注意 NO_PROXY 里是否误包含了目标域名。

8.2 安全建议

  1. 入站只监听 127.0.0.1,需要给局域网使用时用防火墙限定来源 IP
  2. 定期升级:sudo apt upgrade sing-box(官方源安装时);手动安装的到 Releases 页取新包
  3. 开启日志轮转,避免 journalctl 无限增长
  4. 配置文件含密钥,sudo chmod 600 /etc/sing-box/config.json

9. 管理命令

1
2
3
4
5
6
7
8
9
sudo systemctl start sing-box      # 启动
sudo systemctl stop sing-box # 停止
sudo systemctl restart sing-box # 重启(改完配置)
sudo systemctl status sing-box # 状态
sudo journalctl -u sing-box --output cat -f # 跟踪日志

# 配置备份与回滚
sudo cp /etc/sing-box/config.json /etc/sing-box/config.json.bak
sudo cp /etc/sing-box/config.json.bak /etc/sing-box/config.json && sudo systemctl restart sing-box

总结

  • 优先用官方 APT 源安装:自带 systemd 服务,升级跟着 apt
  • 配置按当前文档写:sniff 用路由动作、内网判断用 ip_is_privateinbounds[].sniffgeoip 都已被移除
  • sing-box check 静默通过才算语法正确,但必须再手动 run 一次并用 curl -x 验证,否则不知道出站是否真的通
  • Docker 守护进程不读 shell 的环境变量,拉镜像要单独写 docker.service.d/proxy.conf(或 /etc/docker/daemon.jsonproxies),并且 NO_PROXY 要包含内网网段与私有 registry
  • 容器内程序与 docker build 的 RUN 步骤读的是客户端配置 ~/.docker/config.jsonproxies,与守护进程那层互不相通

参考链接

系列索引:网络与自建服务