SFTPGo 部署教程:独立账号、文件上传与目录权限
部署 SFTPGo,为文件交接建立独立用户和目录,限制上传、下载和删除权限,备份用户文件及服务状态。
给文件接收者单独的账号和目录
给别人传一批文件,最省事的做法常常是把服务器账号发过去。这会同时交出更多权限:对方可能看到无关目录,账号也很难在交接完成后彻底收回。SFTPGo 适合把文件交接从系统登录中分离,给每个接收者一个独立用户,只开放需要的目录和操作。
需要独立文件交付服务器,可查看雨云配置,推荐码 `KuZhuJi`;磁盘与带宽按文件量和传输需求选择。
这次从本地文件存储、SFTP 上传和 Web 管理开始。FTP、WebDAV、对象存储和自动化事件都可以以后再接。先验证一个普通用户能上传、能下载、不能访问别人的文件,再考虑让这项服务接收真正的业务资料。
管理员与传文件的用户是不同身份
管理员负责创建账号、权限、配额和目录映射,普通用户负责文件操作。不要让接收资料的人使用管理员账号,也不要把 SFTPGo 用户和 VPS 上的系统 SSH 用户混在一起。
网页管理只通过私人通道访问,文件用户则使用单独的 SFTP 端口。服务器自己的 SSH 管理端口保持原用途,两个服务不要占用同一个端口。
如果只是偶尔交接,也可以全程通过 SSH 转发开放给自己,再由自己代为上传;需要直接给他人使用时,才安排 SFTP 公网入口和相应访问限制。敏感资料交接完成后要有清理规则,文件管理工具不会替你决定保存多久。
两个挂载目录都要保留
官方普通镜像默认使用 UID/GID `1000`。`/srv/sftpgo` 放用户文件及备份等内容,`/var/lib/sftpgo` 保存应用工作目录中的配置数据、主机密钥等持久内容。只挂载前者,重建时可能丢失服务身份和其他必要状态。
mkdir -p ~/services/sftpgo/data ~/services/sftpgo/home
cd ~/services/sftpgo
sudo chown -R 1000:1000 data home保存为 `compose.yaml`:
services:
sftpgo:
image: drakkan/sftpgo:v2.7.6
ports:
- "127.0.0.1:8086:8080"
- "127.0.0.1:2022:2022"
volumes:
- ./data:/srv/sftpgo
- ./home:/var/lib/sftpgo
environment:
SFTPGO_GRACE_TIME: "32"
stop_grace_period: 40s
restart: unless-stopped这里使用官方文档列出的版本标签;安装前检查该版本的支持及更新情况,维护时按发行说明更新。普通镜像与 distroless 等变体的默认数据提供程序和可用工具不同,不能换了标签还认为行为完全一样。
`SFTPGO_GRACE_TIME` 给已有连接留出关闭时间,Docker 的等待时间设得略长于它。它有助于正常关停,但不代表断电或强制终止时正在传输的文件一定完整。接收重要文件仍应在上传完成后核对大小或哈希。
docker compose config --quiet
docker compose up -d
docker compose logs --tail=80 sftpgo首次管理只走私人入口
同时转发网页和 SFTP 测试端口:
ssh -L 18086:127.0.0.1:8086 -L 12022:127.0.0.1:2022 user@your-server本机打开 `http://127.0.0.1:18086/web/admin`,创建首个管理员。后台创建一个测试用户,例如 `handoff`,将本地存储的主目录放在 `/srv/sftpgo/data/handoff`,并设置只满足本次交接的权限。
不要直接把宿主机根目录或整个站点目录挂给普通用户。需要共享资料时准备独立的交接目录,明确谁可以上传、下载、删除、改名和创建子目录。上传者只需要交资料时,可以限制额外操作;涉及只上传不读取的场景,逐项验证实际客户端行为后再使用。
用普通客户端验证目录边界
用测试用户连到本地转发端口,不要使用管理员身份测试传输。首次出现主机指纹提示时,通过可信方式核对服务身份后确认;恢复或重建时保存主机密钥,可以减少无意变更指纹的问题。
sftp -P 12022 handoff@127.0.0.1进入交互界面后执行上传与下载:
pwd
ls
put sample.txt
get sample.txt received.txt测试前在本机准备 `sample.txt`。下载后比较内容,或者用哈希检查传输结果。再尝试进入另一个用户的目录,并确认无法访问;权限边界需要从普通用户看到的路径判断,而不是看管理员设置页上的目录名称。
如果操作失败,分清应用权限和宿主文件权限。应用允许上传,但 UID `1000` 无法写入挂载目录,也会失败。反过来,宿主目录可写并不代表普通用户应该有删除权限。先检查日志和目录属主,别把权限统一改为 `777`。
开放公网只放实际需要的协议
确认测试正常后,确有直接访问需求时,可以把 SFTP 映射改为 `2022:2022`。网页管理的 8086 仍绑定回环地址。需要 WebClient 时再为网页入口单独配置 HTTPS,并检查管理员与普通用户入口的访问范围。
Docker 发布端口的流量路径可能不同于宿主机普通服务,设置防火墙后从另一台机器实际测试允许和拒绝的来源。不要只看某个防火墙命令显示成功便判断端口已经限制。云平台安全组和宿主机规则也应一致。
账号凭据由接收者本人保存;适合采用公钥认证的用户,核对并登记其公钥。离开或交接结束时禁用账号并撤销相关密钥,检查共享链接、活动连接及自动化凭据是否还需要保留。不要为了下一次方便,把所有临时账号长期放着。
配额与保留时间一起确定
先为用户安排合理的存储范围,定期检查实际目录占用。应用配额统计、外部工具直接写入的文件和底层磁盘剩余空间,不一定始终同步;有人绕过应用往目录里放文件后,应按当前版本的管理能力核对统计。
交接约定最好写清文件命名、上传完成标志和删除时间。例如接收方确认后再清理,而不是按修改时间一刀切删除;一份仍在传输的大文件可能具有较早的时间信息。需要自动化清理时,先在副本上验证规则,不把第一次运行直接对准正式目录。
重要交接可以由发送方提供文件哈希,并由接收方核对。哈希能帮助识别内容是否一致,不负责判断文件来源是否可信。可执行文件和脚本照常按业务要求审查,别因为它通过了私人 SFTP 入口就直接运行。
备份用户文件与服务状态
后台导出的用户与管理数据,不能默认包含用户上传的文件。完整恢复需要文件内容、SQLite 状态、配置和主机密钥;本例停机后一起归档。
以下命令针对本文的 v2.7.6、SQLite 和两个本地挂载目录。备份前让用户结束传输,确认没有第二个容器或宿主程序同时写入这些目录。外部数据库、对象存储和额外挂载的资料,需要另列备份范围。 把下方备份代码保存为 backup-sftpgo.sh,在 VPS 上用 bash backup-sftpgo.sh 执行。
#!/usr/bin/env bash
set -euo pipefail
umask 077
set -o noclobber
cd "$HOME/services/sftpgo"
test -d data
test ! -L data
test -d home
test ! -L home
test -f compose.yaml
test ! -L compose.yaml
sudo test -s home/sftpgo.db
backup_dir="$HOME/sftpgo-backups"
install -d -m 700 "$backup_dir"
stamp=$(date -u +%Y%m%dT%H%M%SZ)
archive_name="sftpgo-$stamp.tar"
archive="$backup_dir/$archive_name"
test ! -e "$archive"
test ! -e "$archive.partial"
test ! -e "$archive.sha256"
members=(data home compose.yaml)
for file in .env sftpgo.json; do
if test -e "$file"; then
test -f "$file"
test ! -L "$file"
members+=("$file")
fi
done
docker compose config --quiet
cid=$(docker compose ps -q sftpgo)
[[ "$cid" =~ ^[0-9a-f]{64}$ ]]
test "$(docker inspect --format '{{.State.Running}}' "$cid")" = true
if ! docker compose stop sftpgo; then
echo '停止未确认成功:检查原容器状态,先不要复制数据库。' >&2
exit 1
fi
test "$(docker inspect --format '{{.State.Running}}' "$cid")" = false
test "$(docker inspect --format '{{.State.ExitCode}}' "$cid")" = 0
if sudo tar --numeric-owner -cpf - -- "${members[@]}" > "$archive.partial" && test -s "$archive.partial" && tar -tf "$archive.partial" > /dev/null; then
mv -n -- "$archive.partial" "$archive"
test ! -e "$archive.partial"
(cd "$backup_dir" && sha256sum "$archive_name" > "$archive_name.sha256")
else
echo '归档失败,保留 partial;原目录未改动,恢复原服务。' >&2
docker compose start sftpgo
exit 1
fi
docker compose start sftpgo
printf '归档:%s\n校验文件:%s.sha256\n' "$archive" "$archive"
归档放在服务目录之外,目录权限为 700,文件由 umask 077 限制。它可能包含管理员数据、密码哈希、主机私钥及 .env;校验和只能核对传输是否一致,异机保存前还要加密。若打包失败,示例保留 partial 并尝试启动原服务;停止、状态检查、改名或校验等其他步骤报错时,先检查同一原容器,再在原目录执行 docker compose start sftpgo。不要把 partial 当作可恢复备份。
恢复用原实例相同的镜像版本,在独立空目录中进行,不覆盖正在使用的 data/home。先把归档及同名 .sha256 校验文件放回备份目录,再替换实际归档名: 把下段代码保存为 restore-sftpgo.sh,在 VPS 上用 bash restore-sftpgo.sh 执行;脚本结束后,进入 $HOME/services/sftpgo-restore-check 目录,再继续下方的权限核对和可选修正。
#!/usr/bin/env bash
set -euo pipefail
umask 077
set -o noclobber
backup_dir="$HOME/sftpgo-backups"
archive_name='sftpgo-替换为实际UTC时间.tar'
cd "$backup_dir"
sha256sum -c "$archive_name.sha256"
restore_dir="$HOME/services/sftpgo-restore-check"
test ! -L "$restore_dir"
if test -e "$restore_dir"; then
test -d "$restore_dir"
test ! -L "$restore_dir"
existing_entries=$(find "$restore_dir" -mindepth 1 -maxdepth 1 -print -quit)
test -z "$existing_entries"
else
install -d -m 700 "$restore_dir"
fi
sudo tar --numeric-owner --same-owner -xpf "$backup_dir/$archive_name" -C "$restore_dir"
cd "$restore_dir"
sudo test -s home/sftpgo.db
test -f compose.yaml
sudo find data home -printf '%U:%G %m %p\n'
启动前核对 data/home 的数字属主与模式,尤其是数据库、配置文件和 SSH 主机私钥。按本文 UID/GID 1000 的配置,如果提取后出现错误属主,可只修正这两个恢复目录中属主不符的普通文件和目录;不要对原实例执行,也不要用 chmod 777 代替检查。私钥或自定义配置原为 600 时,恢复后仍应为 600。
(
set -euo pipefail
restore_dir="$HOME/services/sftpgo-restore-check"
test -d "$restore_dir"
test ! -L "$restore_dir"
cd -- "$restore_dir" || exit 1
test -d data
test ! -L data
test -d home
test ! -L home
# 仅适用于本教程原配置:data/home 预期全部属于 1000:1000。
# 先核对上方输出;使用过其他 UID/GID 或外部挂载时,不执行此修正。
sudo find data home -xdev \( -type f -o -type d \) \( ! -uid 1000 -o ! -gid 1000 \) -exec chown --no-dereference 1000:1000 -- {} +
sudo find data home -printf '%U:%G %m %p\n'
)
保存下面的独立测试配置为 compose.restore.yaml。它沿用本文版本,换用另一组回环端口,并关闭自动重启。自定义了配置文件、环境变量或数据路径的实例,应按归档中的原配置补齐;不要在恢复演练时顺便升级。
services:
sftpgo:
image: drakkan/sftpgo:v2.7.6
ports:
- "127.0.0.1:18087:8080"
- "127.0.0.1:12023:2022"
volumes:
- ./data:/srv/sftpgo
- ./home:/var/lib/sftpgo
environment:
SFTPGO_GRACE_TIME: "32"
stop_grace_period: 40s
restart: "no"
下面的启动命令在 VPS 的恢复目录中执行:
cd "$HOME/services/sftpgo-restore-check"
docker compose -p sftpgo-restore-check -f compose.restore.yaml config --quiet
docker compose -p sftpgo-restore-check -f compose.restore.yaml up -d
随后在自己的电脑另开一个终端建立转发,保持此 SSH 会话;不要在 VPS 上运行这一条:
ssh -L 18087:127.0.0.1:18087 -L 12023:127.0.0.1:12023 user@your-server
在本机打开 http://127.0.0.1:18087/web/admin,用原管理员登录,检查原用户及其目录权限;再用 sftp -P 12023 原用户名@127.0.0.1 下载旧文件并比对哈希。核对原主机指纹,实际上传、下载一个新文件,已创建第二个测试用户时,再确认普通用户不能进入另一用户的目录。检查配额后停止测试实例,保留归档,不要把测试端口直接开放到公网。
本次恢复演练使用 Docker Desktop 的 ARM64 Linux 环境、v2.7.6 普通镜像和 SQLite,将完整冷归档恢复到两个新建的 Linux 数据卷。恢复前核对了全部归档成员的内容、数字属主和模式;恢复后验证旧管理员、原用户、主机密钥、旧文件及新的 SFTP 读写,跨用户目录访问也被拒绝。上面的 Linux VPS Compose 与 sudo 命令未在真实 VPS 上逐行执行;外部数据库、对象存储、版本升级和其他架构不在这次演练范围内。
