Docker 部署 SFTPGo:把家用服务器变成安全文件交换站,外网临时收发不用开 NAS 后台

SFTPGo 文件交换站封面:家庭服务器通过 cpolar HTTPS 安全临时收发文件,不暴露 NAS 后台

家里有一台小主机、办公室有一台闲置 mini PC,平时用来跑 Docker、备份照片、同步资料。偶尔遇到一个很现实的需求:外部同事要临时传一个大文件,或者你要给对方发一个安装包、设计稿、日志压缩包。

很多人的第一反应是:把 NAS 后台映射出去,或者临时开一个公网端口。

我不建议这么做。

NAS 后台里通常有存储管理、用户管理、套件管理、系统设置,一旦暴露到公网,风险和收益不成比例。更稳妥的做法是:单独部署一个文件交换服务,只给外部人员一个低权限账号,只能访问一个独立目录,用完就关账号、关隧道。

这篇就用 Docker 部署 SFTPGo,把它变成一个临时、安全、可控的文件交换站;需要外网访问时,再用 cpolar 临时映射 SFTPGo 的 WebClient 入口,而不是把整块 NAS 或管理后台暴露出去。

这套方案适合什么场景

适合:

  • 家用服务器、办公室小主机、迷你主机上临时收发文件;
  • 给外部同事开一个临时上传/下载账号;
  • 每个账号绑定独立目录,避免看到其他文件;
  • 不想让对方安装 SFTP 客户端,只想通过网页上传下载;
  • 不想开放 NAS 管理后台,只暴露必要的文件交换入口。

不适合:

  • 长期公开网盘;
  • 存放高度敏感资料;
  • 给陌生人开放匿名上传;
  • 把它当成完整 NAS 管理系统替代品。

SFTPGo 是一个文件传输服务器,支持 SFTP、FTP/S、WebDAV、HTTP WebClient 等能力。本文重点用它的 WebClient:外部同事打开网页,输入账号密码,就能上传或下载指定目录里的文件。

注意:SFTPGo 不等同于 NAS 后台。它更像一个“文件交换层”,适合做临时账号、目录隔离、权限控制;不要把它和群晖、威联通、TrueNAS 这类管理后台混在一起暴露。

整体流程

SFTPGo 与 cpolar 外网文件交换流程图:浏览器访问 WebClient 8081,管理端 8080 仅本机访问

本文会做 6 件事:

  1. 在服务器上准备 SFTPGo 数据目录;
  2. 用 Docker Compose 启动 SFTPGo;
  3. 初始化管理员账号;
  4. 创建一个低权限文件交换用户;
  5. 用 WebClient 在本地验证上传和下载;
  6. 用 cpolar 临时映射 WebClient,让外部同事访问测试。

本文示例约定:

  • SFTPGo 管理入口:http://127.0.0.1:8080
  • SFTPGo WebClient 入口:http://127.0.0.1:8081
  • 本地文件目录:/srv/sftpgo/exchange
  • Docker 项目目录:/opt/sftpgo
  • 临时用户:partner01

如果你的服务器没有 /srv/opt 写入权限,可以换成当前用户目录,例如 ~/sftpgo。路径换了以后,下面命令里的路径也要一起换。

第一步:准备本地目录

先在服务器上创建项目目录、配置目录和文件交换目录:

sudo mkdir -p /opt/sftpgo
sudo mkdir -p /srv/sftpgo/config
sudo mkdir -p /srv/sftpgo/exchange/partner01
sudo chown -R 1000:1000 /srv/sftpgo
cd /opt/sftpgo

这里的 1000:1000 是 SFTPGo Docker 镜像默认使用的普通用户 UID/GID。这样容器内的 SFTPGo 进程可以正常读写挂载目录。

目录含义:

/opt/sftpgo                 # Docker Compose 文件目录
/srv/sftpgo/config          # SFTPGo 配置和内置数据
/srv/sftpgo/exchange        # 对外文件交换根目录
/srv/sftpgo/exchange/partner01  # partner01 用户的独立目录

这一步很关键。后面给外部同事开的账号,只绑定 /srv/sftpgo/exchange/partner01,不要绑定整块 NAS 目录,也不要绑定你的照片、备份、Docker 配置目录。

第二步:编写 Docker Compose 文件

/opt/sftpgo 下创建 docker-compose.yml

cd /opt/sftpgo
sudo tee docker-compose.yml > /dev/null <<'EOF'
services:
  sftpgo:
    image: drakkan/sftpgo:latest
    container_name: sftpgo
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
      - "127.0.0.1:8081:8081"
    environment:
      SFTPGO_HTTPD__BINDINGS__0__PORT: "8080"
      SFTPGO_HTTPD__BINDINGS__0__ADDRESS: "0.0.0.0"
      SFTPGO_HTTPD__BINDINGS__0__ENABLE_WEB_ADMIN: "true"
      SFTPGO_HTTPD__BINDINGS__0__ENABLE_WEB_CLIENT: "true"
      SFTPGO_HTTPD__BINDINGS__1__PORT: "8081"
      SFTPGO_HTTPD__BINDINGS__1__ADDRESS: "0.0.0.0"
      SFTPGO_HTTPD__BINDINGS__1__ENABLE_WEB_ADMIN: "false"
      SFTPGO_HTTPD__BINDINGS__1__ENABLE_WEB_CLIENT: "true"
    volumes:
      - /srv/sftpgo/config:/var/lib/sftpgo
      - /srv/sftpgo/exchange:/srv/sftpgo/exchange
EOF

这个 Compose 文件里有两个 Web 端口:

  • 8080:本机管理入口,用来创建用户、设置权限;
  • 8081:只启用 WebClient,不启用 Web 管理后台,用来临时映射给外部同事。

并且两个端口都只绑定在 127.0.0.1

ports:
  - "127.0.0.1:8080:8080"
  - "127.0.0.1:8081:8081"

这表示局域网里的其他机器不能直接访问这两个端口。需要外网临时访问时,我们只用 cpolar 映射 8081,不映射 8080

第三步:启动 SFTPGo

执行:

cd /opt/sftpgo
sudo docker compose up -d

查看容器状态:

sudo docker compose ps

正常会看到 sftpgo 处于运行状态。

再看日志:

sudo docker logs --tail=80 sftpgo

本机测试 Web 入口:

curl -I http://127.0.0.1:8080
curl -I http://127.0.0.1:8081

能返回 HTTP 响应头,就说明 Web 服务已经起来了。

第四步:初始化管理员账号

在服务器本机浏览器打开:

http://127.0.0.1:8080

第一次进入 SFTPGo Web Admin,会要求创建管理员账号。

建议这样设置:

  • 管理员用户名不要用 admin
  • 密码至少 16 位,包含大小写字母、数字、符号;
  • 管理员账号只自己使用,不发给外部同事;
  • 管理入口 8080 不做公网映射。

初始化完成后,用管理员账号登录 Web Admin。

第五步:创建临时文件交换用户

进入 SFTPGo Web Admin 后,创建用户:

Users -> Add user

示例配置:

Username: partner01
Password: 使用随机强密码
Status: Active
Filesystem provider: Local filesystem
Home dir: /srv/sftpgo/exchange/partner01

权限建议按需求给,不要默认全开。

如果对方只需要下载文件:

Permissions: list, download

如果对方需要上传文件:

Permissions: list, upload, overwrite, create_dirs

如果对方只是临时上传,不建议给删除权限。这样即使对方误操作,也不会把目录里的文件删掉。

创建完成后,回到服务器确认目录权限:

sudo chown -R 1000:1000 /srv/sftpgo/exchange/partner01
sudo chmod -R u+rwX,g-rwx,o-rwx /srv/sftpgo/exchange/partner01

chmod 这条命令的含义是:目录所有者可读写进入,组和其他用户无权限。它不是 WebClient 权限的替代品,而是本地文件系统层面的兜底。

第六步:本地验证 WebClient 上传下载

在本机打开 WebClient:

http://127.0.0.1:8081

用刚创建的用户登录:

Username: partner01
Password: 你给 partner01 设置的密码

先做下载测试。

在服务器里放一个测试文件:

echo "hello from sftpgo" | sudo tee /srv/sftpgo/exchange/partner01/readme.txt > /dev/null
sudo chown 1000:1000 /srv/sftpgo/exchange/partner01/readme.txt

刷新 WebClient,应该能看到 readme.txt,点击可以下载。

再做上传测试:

  1. 在 WebClient 里点击上传;
  2. 上传一个小文件,例如 test.txt
  3. 回到服务器检查文件是否落到目录里:
sudo ls -lah /srv/sftpgo/exchange/partner01

如果文件能上传、能下载,这个临时文件交换账号就已经可用了。

第七步:用 cpolar 临时映射 WebClient

到这里,SFTPGo 只在本机可访问。外部同事还打不开。

临时外网访问可以用 cpolar,把本地 8081 映射成一个公网 HTTPS 地址。重点是:只映射 WebClient 端口 8081,不要映射管理端口 8080,更不要映射 NAS 后台。

如果服务器已经安装并登录 cpolar,直接执行:

cpolar http 8081

命令启动后,终端会显示一个公网访问地址,形式类似:

https://xxxx.cpolar.top

把这个地址发给外部同事,让对方使用 partner01 的账号密码登录。

如果你更习惯用 cpolar Web UI,可以在服务器本机打开:

http://127.0.0.1:9200

然后创建 HTTP 隧道:

协议:HTTP
本地地址:127.0.0.1
本地端口:8081

创建后,在 cpolar 的隧道列表里复制公网地址。

这里不要填 80808080 是管理入口,里面有用户管理和系统配置,不应该给外部人员访问。

第八步:外部同事访问测试

给外部同事发送三样东西:

访问地址:https://xxxx.cpolar.top
用户名:partner01
密码:单独通过另一种渠道发送
说明:只用于本次文件收发,用完会关闭

建议不要把地址、用户名、密码放在同一条消息里。比如地址发 IM,密码电话或另一个聊天工具发。

让对方测试:

  1. 打开 cpolar 公网地址;
  2. 进入 SFTPGo WebClient 登录页;
  3. 输入 partner01 和密码;
  4. 上传一个小文件;
  5. 下载你提前放好的 readme.txt
  6. 你在服务器上确认文件是否出现。

服务器端检查:

sudo ls -lah /srv/sftpgo/exchange/partner01

测试完成后,如果只是一次性收发文件,立刻做两件事:

  1. 在 SFTPGo Web Admin 里禁用 partner01
  2. 停止 cpolar 隧道。

如果是前台运行的 cpolar http 8081,在终端按:

Ctrl + C

如果是在 cpolar Web UI 创建的隧道,就在 Web UI 里停止对应隧道。

安全边界:这几条不要省

SFTPGo 文件交换安全边界图:只暴露 WebClient,使用强密码、临时账号、独立目录并用完关闭

这套方案的安全感,来自“只暴露必要入口”和“只给必要权限”。不要因为它能跑通,就把边界放大。

1. 不要映射整块 NAS

不要把 SFTPGo 用户的 Home dir 设置成:

/volume1
/mnt/nas
/srv
/home
/

应该给每个外部人员单独建目录,例如:

/srv/sftpgo/exchange/partner01
/srv/sftpgo/exchange/customer-a
/srv/sftpgo/exchange/project-x-upload

2. 不要开放管理员后台

本文把管理入口放在 8080,WebClient 放在 8081。cpolar 只映射:

cpolar http 8081

不要执行:

cpolar http 8080

3. 临时账号用完就禁用

文件收发结束后,在 SFTPGo Web Admin 里把用户改成禁用状态。下次需要再启用,或者重新建一个新账号。

4. 密码要强,最好单次使用

不要用:

123456
password
company2026
partner01

建议用密码管理器生成 16 位以上随机密码。

5. 不要传高度敏感文件

临时文件交换站适合传普通协作文件、安装包、日志、素材。涉及身份证、合同原件、财务数据、客户隐私、密钥文件时,不要直接用这种临时公网方式裸传。

6. 用完关闭隧道

cpolar 的价值在于“按需临时打开”。文件收发结束后就关闭隧道,不要把临时入口长期挂在公网。

常见问题排查

1. 容器启动失败

先看状态:

cd /opt/sftpgo
sudo docker compose ps

再看日志:

sudo docker logs --tail=120 sftpgo

常见原因:

  • 80808081 已被占用;
  • Compose 文件缩进错误;
  • /srv/sftpgo/config 没有写入权限;
  • Docker 服务没有启动。

检查端口占用:

sudo lsof -iTCP:8080 -sTCP:LISTEN
sudo lsof -iTCP:8081 -sTCP:LISTEN

如果端口被占用,把 Compose 里的宿主机端口换掉,例如把 8081 改成 18081,cpolar 也跟着映射新端口。

修改后重启:

cd /opt/sftpgo
sudo docker compose down
sudo docker compose up -d

2. Web 页面打不开

先在服务器本机测试:

curl -I http://127.0.0.1:8080
curl -I http://127.0.0.1:8081

如果本机都打不开,问题在 SFTPGo 或 Docker,不在 cpolar。

继续检查:

sudo docker ps --filter name=sftpgo
sudo docker logs --tail=80 sftpgo

如果本机能打开,但局域网其他机器打不开,这是正常的。本文故意把端口绑定在 127.0.0.1,避免局域网直接访问。

3. 登录失败

检查 4 件事:

  • 用户名是否写错;
  • 密码是否复制时带了空格;
  • 用户状态是否 Active;
  • 是否用管理员账号登录了 WebClient,或用普通用户登录了 Web Admin。

本文外部同事应该访问的是:

http://127.0.0.1:8081

公网时访问的是 cpolar 映射出来的 8081 地址。

4. 目录权限不对

如果 WebClient 登录后看不到文件,或者上传时报错,先看目录权限:

sudo ls -ld /srv/sftpgo/exchange/partner01
sudo ls -lah /srv/sftpgo/exchange/partner01

重新设置目录归属:

sudo chown -R 1000:1000 /srv/sftpgo/exchange/partner01
sudo chmod -R u+rwX,g-rwx,o-rwx /srv/sftpgo/exchange/partner01

然后重新登录 WebClient 测试。

5. 外网地址打不开

先确认本地 WebClient 能打开:

curl -I http://127.0.0.1:8081

再确认 cpolar 映射的是 8081

cpolar http 8081

如果你用的是 cpolar Web UI,检查隧道配置:

协议:HTTP
本地地址:127.0.0.1
本地端口:8081

不要填错成 8080,也不要填成容器内部 IP。

如果公网地址之前能打开,后来打不开,重新启动一次隧道并复制新的公网地址。免费随机地址会变化,不要继续使用旧地址。

6. 上传失败

上传失败通常有三类原因。

第一类:SFTPGo 用户权限不够。

在用户权限里确认至少包含:

list, upload

如果需要覆盖同名文件,再加:

overwrite

如果需要创建目录,再加:

create_dirs

第二类:本地目录不可写。

执行:

sudo chown -R 1000:1000 /srv/sftpgo/exchange/partner01
sudo chmod -R u+rwX,g-rwx,o-rwx /srv/sftpgo/exchange/partner01

第三类:文件太大或网络中断。

先用 1MB 以下的小文件测试。如果小文件能上传,大文件失败,再检查浏览器、网络稳定性和服务器磁盘空间:

df -h /srv/sftpgo/exchange

7. 文件权限看起来异常

通过 WebClient 上传的文件属于容器内运行用户,对应宿主机上通常显示为 UID 1000。

查看:

sudo ls -lah /srv/sftpgo/exchange/partner01

如果你还要让宿主机上的其他服务读取这些文件,可以把后续处理脚本也放在同一用户权限下运行,或者在处理前调整属主。不要为了省事直接给整个目录 777

收尾:一次临时收发的标准动作

一次外部文件交换结束后,我建议按这个顺序收尾:

1. 确认文件已经收齐或发完
2. 在 SFTPGo Web Admin 禁用临时用户
3. 停止 cpolar 隧道
4. 把文件从 exchange 目录转移到内部长期存储
5. 清空临时目录

清空临时目录前先确认路径:

sudo find /srv/sftpgo/exchange/partner01 -maxdepth 1 -type f -print

确认无误后再删除文件:

sudo find /srv/sftpgo/exchange/partner01 -maxdepth 1 -type f -delete

不要对不熟悉的目录执行 rm -rf,尤其不要在 NAS 挂载目录里随手复制删除命令。

小结

这套方案的核心不是“把文件服务暴露到公网”,而是把暴露范围压到最小:

  • SFTPGo 负责文件交换;
  • 每个外部人员一个低权限账号;
  • 每个账号一个独立目录;
  • WebClient 负责临时上传下载;
  • cpolar 只在需要时映射 WebClient;
  • 管理后台和 NAS 后台都不对外开放。

如果只是临时给同事收发文件,这比直接开放 NAS 后台安全得多,也比临时搭一个共享目录更可控。

你可以按本文先跑一遍本地流程,再开 cpolar 做外网测试。卡住的话,留言说清楚你卡在哪一步:Docker 启动、目录权限、账号权限、外网访问,还是上传失败。我会按你的报错和截图帮你逐项排查。

更多推荐