Syncthing 实现 Ubuntu 到 Ubuntu/Alpine 的单向安全同步

从零部署 Syncthing,用 syncthing:syncthing 管理 /srv/backup,实现 Ubuntu 源端到 Ubuntu、Debian 或 Alpine 备份端的单向同步。

这篇记录一个从零部署 Syncthing 的备份场景:A 机器是 Ubuntu 或 Debian,作为源端;B 机器可以是 Ubuntu、Debian 或 Alpine,作为备份端;目标是把 A 机器上的 /srv/backup 单向同步到 B 机器,不让备份端的改动反向影响源端。

如果前面还有一台业务服务器只负责打包并上传备份,生产链路可以拆成三段:

业务服务器 A -> SSH 上传 tar.gz -> Syncthing 源端 B:/srv/backup -> Syncthing 单向同步 -> 最终备份端 C:/srv/backup

这种情况下,A 不必运行 Syncthing;A 只需要把完整备份包原子地放入 B 的 /srv/backup。真正负责同步状态、设备连接和最终落盘的是 B 与 C 上的 Syncthing。

Syncthing 是一个持续文件同步工具,可以在两台或多台机器之间实时同步文件。它没有中心服务器,数据只保存在自己的设备上;设备之间的通信使用 TLS 加密,每台设备也通过独立证书身份识别。Web 管理面板只建议通过本机监听和 SSH 隧道访问,不要直接暴露到公网。

适用场景

  • 源端目录:/srv/backup
  • 备份端目录:/srv/backup
  • 目录属主:syncthing:syncthing
  • 同步模式:源端 仅发送,备份端 仅接收
  • 运行用户:专门创建的 syncthing 普通服务用户
  • 管理方式:通过 SSH 隧道访问 Syncthing Web GUI

实际生产里也可以把源端同步端口改成非默认端口,例如 B 机监听 29029/tcp29029/udp,C 机继续监听默认 22000。Syncthing 设备地址保留 dynamic 时会通过发现服务交换真实地址;手动指定地址时才需要写成 tcp://公网IP:端口

本文从安装开始就把服务、配置目录和同步目录都交给 syncthing:syncthing,符合 Syncthing 对运行用户的建议。

一、Ubuntu 源端配置

1. 安装 Syncthing

sudo apt update
sudo apt install syncthing -y

创建服务用户、配置目录和同步目录:

sudo useradd --system --home /var/lib/syncthing --create-home --shell /usr/sbin/nologin syncthing
sudo install -d -o syncthing -g syncthing /var/lib/syncthing
sudo install -d -o syncthing -g syncthing /var/lib/syncthing/.config
sudo install -d -o syncthing -g syncthing /var/lib/syncthing/.local/state/syncthing
sudo install -d -o syncthing -g syncthing /srv/backup

生成 Syncthing 配置:

sudo -u syncthing syncthing -generate="/var/lib/syncthing/.config/syncthing"

2. 启动 systemd 服务

Ubuntu 和 Debian 的软件包通常提供 syncthing@.service 模板。这里使用 syncthing@syncthing.service,表示以 syncthing 用户运行:

sudo systemctl daemon-reload
sudo systemctl enable --now syncthing@syncthing.service
sudo systemctl status syncthing@syncthing.service --no-pager

确认进程用户:

ps -eo pid,user,args | grep '[s]yncthing'

正常应该看到 USERsyncthing,命令类似:

syncthing /usr/bin/syncthing serve --no-browser --no-restart --logflags=0

查看监听端口:

ss -tunlp | grep syncthing

常见监听包括:

tcp   LISTEN 0 4096 127.0.0.1:8384 0.0.0.0:* users:(("syncthing",pid=1234,fd=14))
tcp   LISTEN 0 4096 *:22000          *:*       users:(("syncthing",pid=1234,fd=11))
udp   UNCONN 0 0    *:22000          *:*       users:(("syncthing",pid=1234,fd=13))
udp   UNCONN 0 0    0.0.0.0:21027    0.0.0.0:* users:(("syncthing",pid=1234,fd=19))

其中:

  • 127.0.0.1:8384 是 Web 管理面板。
  • 22000/tcp22000/udp 是设备同步端口。
  • 21027/udp 主要用于局域网发现。

如果你在 Syncthing 高级设置里把监听地址改成了自定义端口,例如:

tcp://0.0.0.0:29029
quic://0.0.0.0:29029

那系统防火墙、云安全组和另一端手动地址都要使用 29029,不要继续照抄 22000

3. 通过 SSH 隧道访问源端面板

Web GUI 只监听本机时,从自己的电脑建立隧道:

ssh -N -L 8384:127.0.0.1:8384 user@你的Ubuntu服务器IP

然后在本地浏览器打开:

http://127.0.0.1:8384

4. 放行同步通信端口

如果 Ubuntu 使用 UFW,只放行同步端口:

sudo ufw allow 22000/tcp
sudo ufw allow 22000/udp
sudo ufw reload

不要把 8384 当成同步端口开放。8384 是管理面板端口;设备之间传输文件默认使用 22000/tcp22000/udp,或者由 Syncthing 的发现和中继机制协商。

二、Ubuntu/Debian 备份端配置

如果备份端也是 Ubuntu 或 Debian,安装方式和源端一致,只是后面在 Web GUI 里把文件夹类型设为 仅接收

sudo apt update
sudo apt install syncthing -y

sudo useradd --system --home /var/lib/syncthing --create-home --shell /usr/sbin/nologin syncthing
sudo install -d -o syncthing -g syncthing /var/lib/syncthing
sudo install -d -o syncthing -g syncthing /var/lib/syncthing/.config
sudo install -d -o syncthing -g syncthing /var/lib/syncthing/.local/state/syncthing
sudo install -d -o syncthing -g syncthing /srv/backup

sudo -u syncthing syncthing -generate="/var/lib/syncthing/.config/syncthing"

sudo systemctl daemon-reload
sudo systemctl enable --now syncthing@syncthing.service
sudo systemctl status syncthing@syncthing.service --no-pager

通过 SSH 隧道访问备份端面板时,为了避免和源端本地 8384 冲突,可以映射到本地 8385

ssh -N -L 8385:127.0.0.1:8384 user@你的Ubuntu接收端IP

浏览器打开:

http://127.0.0.1:8385

如果使用 UFW,同样只放行同步端口:

sudo ufw allow 22000/tcp
sudo ufw allow 22000/udp
sudo ufw reload

三、Alpine 备份端配置

1. 安装 Syncthing

apk update
apk add syncthing

创建服务用户、配置目录和同步目录:

adduser -D -H -s /sbin/nologin syncthing
install -d -o syncthing -g syncthing /var/lib/syncthing
install -d -o syncthing -g syncthing /var/lib/syncthing/.config
install -d -o syncthing -g syncthing /srv/backup

生成 Syncthing 配置:

su -s /bin/sh syncthing -c 'syncthing -generate="/var/lib/syncthing/.config/syncthing"'

2. 配置 OpenRC 服务

Alpine 使用 OpenRC。创建 /etc/init.d/syncthing

cat << 'EOF' > /etc/init.d/syncthing
#!/sbin/openrc-run

name="syncthing"
command="/usr/bin/syncthing"
command_user="syncthing:syncthing"
command_args="-home=/var/lib/syncthing/.config/syncthing -gui-address=127.0.0.1:8384 -no-browser -no-restart"
command_background="yes"
pidfile="/run/syncthing.pid"

depend() {
    need net
}
EOF

启动并加入默认运行级别:

chmod +x /etc/init.d/syncthing
rc-update add syncthing default
rc-service syncthing start
rc-service syncthing status

确认进程用户和监听地址:

ps -eo pid,user,args | grep '[s]yncthing'
ss -tunlp | grep syncthing

Web GUI 应该只监听 127.0.0.1:8384。访问 Alpine 面板时建立隧道:

ssh -N -L 8385:127.0.0.1:8384 user@你的Alpine服务器IP

然后浏览器打开:

http://127.0.0.1:8385

如果 Alpine 侧启用了防火墙,只放行同步通信端口即可:

22000/tcp
22000/udp

四、建立设备信任

分别打开两台机器的 Syncthing Web GUI。

在 B 机器,也就是 Ubuntu、Debian 或 Alpine 备份端:

  1. 点击 操作
  2. 点击 显示 ID
  3. 复制设备 ID。

回到 A 机器,也就是 Ubuntu 源端:

  1. 点击 添加远程设备
  2. 粘贴备份端的设备 ID。
  3. 保持 地址 为默认的 dynamic
  4. 保存。

然后回到备份端面板,等待连接请求弹出,点击 添加设备 接受 Ubuntu 源端。

远程设备的 地址 字段最容易填错。不要填 0.0.0.0:8384127.0.0.1:8384http://...:8384;这些都是 Web GUI 管理端口,不是设备同步地址。常规场景保留 dynamic 即可;如果要手动指定直连地址,应写成 tcp://公网IP:22000 这类同步端口地址。

如果两台机器都在公网 VPS 上,通常会自动发现并连接。如果连接不上,优先检查:

  • 两端同步通信端口是否放行。
  • 云厂商安全组是否也放行了对应端口。
  • 两端时间是否准确,证书认证对系统时间比较敏感。
  • 远程设备地址是否仍为 dynamic,或者是否写成了正确的 tcp://...:22000

五、配置单向同步目录

1. 源端设置为仅发送

在源端 Syncthing 面板中:

  1. 点击 添加文件夹
  2. 文件夹路径 填写 /srv/backup
  3. 共享 选项卡中勾选备份端。
  4. 高级 选项卡中,将 文件夹类型 设置为 仅发送 (Send Only)
  5. 保存。

仅发送 的意思是:源端是权威端。源端新增、修改、删除会同步到对端,但对端本地改动不会被当成正常变更反向覆盖源端。

2. 备份端设置为仅接收

在备份端 Syncthing 面板中:

  1. 等待源端发来的共享目录提示。
  2. 点击接受。
  3. 本地路径填写 /srv/backup
  4. 高级 选项卡中,将 文件夹类型 设置为 仅接收 (Receive Only)
  5. 保存。

仅接收 的意思是:备份端只接收源端下发的数据。如果有人在备份目录里手动改文件,Syncthing 会把它标记为本地变更,而不是推回源端。

如果备份端目录里本来就手工放入了同名文件,仅接收 可能会显示本地变更。确认文件来自源端且内容一致后,可以在备份端文件夹菜单中执行 还原本地更改,让备份端重新接受源端状态。

六、验证同步

在源端创建测试文件:

date | sudo -u syncthing tee /srv/backup/syncthing-test.txt >/dev/null

观察两端 Web GUI,等待状态变成 最新。然后在备份端检查:

cat /srv/backup/syncthing-test.txt
ls -lh /srv/backup/syncthing-test.txt

文件属主应为 syncthing:syncthing。再测试删除同步:

sudo -u syncthing rm /srv/backup/syncthing-test.txt

如果源端删除文件,备份端也会跟着删除。这个行为适合“镜像式备份”,但不适合“防误删归档”。如果担心源端误删同步到备份端,应额外在备份端配置快照、版本控制或定时归档。

1. 对比两端目录大小和文件数

当 Syncthing 面板显示远程设备和文件夹都是 最新,但当前速率是 0 B/s 时,不一定是没有传输成功,也可能是已经同步完成、没有新数据需要传。

可以分别在源端和备份端执行:

du -sh /srv/backup
find /srv/backup -type f | wc -l

如果文件数有差异,再进一步对比文件相对路径和字节数:

find /srv/backup -type f -printf '%P %s\n' | sort

两端都执行一次。如果源端的业务文件在备份端都存在,大小也一致,Syncthing 面板又显示 最新,就可以认为同步已经完成。

Alpine 默认的 BusyBox find 不支持 -printf。在 Alpine 备份端可以改用:

find /srv/backup -type f -exec ls -ln {} \; | sort

或者安装 GNU findutils:

apk add findutils

2. 小磁盘 VPS 的空间保护

Syncthing 会检查配置和数据库所在磁盘的可用空间。小盘 VPS 如果只剩几 GB,可能看到类似错误:

insufficient space on disk for database

优先清理磁盘空间。如果确认剩余空间足够支撑 Syncthing 数据库,也可以在 Web GUI 中调整:

操作 -> 设置 -> 高级 -> 选项 -> 最小主目录可用磁盘空间

把它从默认值调低,例如 1%。这只是放宽 Syncthing 的保护阈值,不会增加实际磁盘空间。

七、内网备份端的 RouterOS 端口映射与直连排查

如果备份端在家里或办公室内网,例如备份端 IP 是 10.20.20.8,而源端是公网 VPS,那么只在备份端系统里放行 22000 还不够。外网源端想通过 IPv4 直连内网备份端,前端路由器也要把 Syncthing 同步端口 NAT 到备份端。

如果两端已经通过公网 IPv6 直连,或者 Syncthing 面板里远程设备已经显示 TCP WAN / QUIC WAN 且状态为 已连接,可以不做 RouterOS IPv4 端口映射。端口映射主要是给“没有可用公网 IPv6、又不想走 relay”的 IPv4 场景准备的。

下面以 RouterOS 为例:

  • WAN 接口:pppoe-out1
  • 备份端内网 IP:10.20.20.8
  • Syncthing 同步端口:22000/tcp22000/udp

1. RouterOS 添加 dstnat 端口映射

/ip firewall nat
add chain=dstnat in-interface=pppoe-out1 protocol=tcp dst-port=22000 action=dst-nat to-addresses=10.20.20.8 to-ports=22000 comment="Syncthing TCP to 10.20.20.8"
add chain=dstnat in-interface=pppoe-out1 protocol=udp dst-port=22000 action=dst-nat to-addresses=10.20.20.8 to-ports=22000 comment="Syncthing UDP to 10.20.20.8"

如果你的 RouterOS 使用 dst-address-type=local 统一匹配本机公网地址,也可以按现有规则风格写成:

/ip firewall nat
add chain=dstnat dst-address-type=local protocol=tcp dst-port=22000 action=dst-nat to-addresses=10.20.20.8 to-ports=22000 comment="Syncthing TCP to 10.20.20.8"
add chain=dstnat dst-address-type=local protocol=udp dst-port=22000 action=dst-nat to-addresses=10.20.20.8 to-ports=22000 comment="Syncthing UDP to 10.20.20.8"

两种写法选一种即可,不要重复建太多同类规则。

2. RouterOS 放行 forward

如果防火墙 forward 链最后有 drop all other forward,就需要确保端口转发流量在 drop 前被放行。

可以单独放行 Syncthing:

/ip firewall filter
add chain=forward in-interface=pppoe-out1 protocol=tcp dst-address=10.20.20.8 dst-port=22000 action=accept comment="Allow Syncthing TCP to 10.20.20.8"
add chain=forward in-interface=pppoe-out1 protocol=udp dst-address=10.20.20.8 dst-port=22000 action=accept comment="Allow Syncthing UDP to 10.20.20.8"

如果已有类似下面这种通用规则,并且 pppoe-out1 已经在 WAN interface list 里,那么单独的 Syncthing filter 规则不是必须的:

/ip firewall filter
add chain=forward action=accept connection-nat-state=dstnat in-interface-list=WAN comment="Allow DSTNAT Port Forwarding"

规则顺序很重要:放行规则必须在最后的 drop 规则前面。

3. 检查 RouterOS 规则

打印 NAT 和 filter 规则:

/ip firewall nat print detail without-paging
/ip firewall filter print detail without-paging

检查 Syncthing 规则是否有命中计数:

/ip firewall nat print stats where comment~"Syncthing"
/ip firewall filter print stats where comment~"Syncthing"

如果外部源端正在尝试连接,packetsbytes 应该会增长。只要 NAT 和 filter 计数在增长,说明 RouterOS 端口转发基本已经生效。

还要确认 WAN 口拿到的是公网 IPv4:

/ip address print where interface=pppoe-out1

如果这里显示的是 10.x.x.x100.64.x.x172.16.x.x172.31.x.x192.168.x.x 这类地址,说明上游可能是内网或 CGNAT。此时 RouterOS 本机端口映射无法从公网直接生效,需要改用公网 IPv6、WireGuard、Tailscale、ZeroTier,或者继续使用 Syncthing relay。

4. 指定直连地址

如果备份端所在网络有公网 IPv4,并且 RouterOS 已经映射到 10.20.20.8:22000,可以在源端 Syncthing 里编辑备份端设备:

高级 -> 地址

dynamic 改成:

tcp://你的公网IP:22000

保存后暂停/恢复该远程设备,或者重启源端 Syncthing:

sudo systemctl restart syncthing@syncthing.service

回到 Syncthing 主界面,点开远程设备详情,正常直连时连接类型应从:

中继广域网

变成类似:

TCP WAN

如果仍然显示 中继广域网,说明它还在走 relay。relay 能用,但速度通常很慢,初次同步几个 GiB 时会非常明显。

如果源端使用自定义同步端口,例如监听在 29029,那么对端手动地址应写成:

tcp://源端公网IP:29029

不要把 Web GUI 端口 8384 写进设备地址。8384 只用于浏览器管理面板,不承载文件同步。

5. 在备份端确认是否已经直连

在备份端 10.20.20.8 上查看 Syncthing 连接:

ss -tunp | grep syncthing

如果看到类似下面这类连接,说明外部源端已经通过 RouterOS 打到了备份端的 22000

tcp ESTAB 0 0 10.20.20.8:22000 192.0.2.10:29029 users:(("syncthing",pid=2635,fd=22))

这里的重点是本地端口为 10.20.20.8:22000,对端是源端公网 IP。对端端口不一定是 22000,这很正常。

如果看到的是类似下面这种:

tcp ESTAB 0 0 10.20.20.8:50454 101.132.255.222:22067 users:(("syncthing",pid=2635,fd=11))

这通常是 relay 中继连接,不代表直连已经成功。

八、安全建议

  • Syncthing 服务、配置目录和同步目录统一使用 syncthing:syncthing
  • Web GUI 只监听 127.0.0.1:8384,通过 SSH 隧道访问。
  • 不要把 8384 Web GUI 管理端口直接暴露到公网。
  • 同步端口只开放给必要来源更稳妥,云安全组和系统防火墙都要检查。
  • 源端使用 仅发送,备份端使用 仅接收,不要配置成默认的双向同步。
  • 对重要备份目录,建议在备份端再做快照或版本保留,避免误删被实时同步。