FTP 是企业里"老但稳定"的文件传输方案——服务器间脚本同步、备份归档、跨地域传输都还在用。本篇把 vsftpd 与 pure-ftpd 的 Docker 化部署、主被动模式原理、Nginx stream 四层代理、SSL 加密整理清楚。
阅读对象:运维 / 部署工程师、需要搭建内部文件服务器的技术团队
覆盖范围:FTP 主动 / 被动模式原理 + vsftpd 完整部署 + pure-ftpd 完整部署 + 虚拟用户 + SSL/TLS 加密 + Nginx stream 反代 + 常见客户端测试
一、FTP 协议基础
1.1 两个端口
| 端口 | 作用 |
|---|
| 21 | 控制端口(命令) |
| 20 | 数据端口(仅主动模式) |
主动模式(PORT):服务器从 20 端口主动连客户端告知的端口。
被动模式(PASV):服务器告知客户端自己的地址 + 端口,等客户端来连。
实战推荐被动模式——客户端不用开端口,服务器在公网就行。
1.2 主动 vs 被动模式
| 维度 | 主动模式 | 被动模式 |
|---|
| 数据连接 | 服务器 → 客户端 | 客户端 → 服务器 |
| 客户端要求 | 必须有公网 IP | 任何网络 |
| 防火墙 | 客户端要放行随机端口 | 服务器要放行 PASV 端口段 |
| 适用 | 内网 | 公网推荐 |
核心难点:FTP 协议需要 2 个连接,NAT / 防火墙容易把数据连接搞断——必须正确配置。
二、vsftpd 部署
vsftpd(Very Secure FTPD)是 Linux 默认 FTP 服务端,最稳定。
2.1 拉取镜像
1
| docker pull fauria/vsftpd
|
2.2 启动容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 测试端口占用
lsof -i:21100-21110
# 启动
docker run -d --name vsftpd --restart=always \
-p 20:20 \
-p 21:21 \
-p 21100-21110:21100-21110 \
-v /home/docker/vsftpd/data:/home/vsftpd \
-v /home/docker/vsftpd/log:/var/log \
-e FTP_USER=www \
-e FTP_PASS={{REDACTED}} \
-e PASV_ADDRESS=203.0.113.10 \
-e PASV_MIN_PORT=21100 \
-e PASV_MAX_PORT=21110 \
-e REVERSE_LOOKUP_ENABLE=NO \
fauria/vsftpd
|
关键参数:
PASV_ADDRESS=203.0.113.10:服务器公网 IP——必须设对,否则客户端连不上PASV_MIN_PORT / MAX_PORT:PASV 模式端口段——防火墙必须放行REVERSE_LOOKUP_ENABLE=NO:强烈推荐——避免 DNS 反向解析卡登录
2.3 完整环境变量
| 变量 | 默认 | 说明 |
|---|
FTP_USER | admin | 默认用户 |
FTP_PASS | 随机 16 位 | 不指定就随机 |
PASV_ADDRESS | Docker host IP | 配服务器公网 IP |
PASV_ADDR_RESOLVE | NO | 主机名解析 |
PASV_ENABLE | YES | 启用被动模式 |
PASV_MIN_PORT | 21100 | PASV 起始端口 |
PASV_MAX_PORT | 21110 | PASV 结束端口 |
REVERSE_LOOKUP_ENABLE | YES | 设为 NO 加快登录 |
PASV_PROMISCUOUS | NO | 关闭被动模式安全检查 |
PORT_PROMISCUOUS | NO | 关闭主动模式安全检查 |
2.4 手动创建用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 进入容器
docker exec -i -t vsftpd bash
# 创建用户目录
mkdir /home/vsftpd/myuser
# 写虚拟用户
echo -e "myuser\nmypass" >> /etc/vsftpd/virtual_users.txt
# 转换数据库
/usr/bin/db_load -T -t hash -f /etc/vsftpd/virtual_users.txt /etc/vsftpd/virtual_users.db
# 退出并重启
exit
docker restart vsftpd
|
2.5 生成 SSL 证书
1
2
3
4
5
| # 创建自签证书(生产用 Let's Encrypt)
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /etc/vsftpd/vsftpd.pem \
-out /etc/vsftpd/vsftpd.pem
|
挂载到容器:
1
| -v /home/docker/vsftpd/ssl/vsftpd.pem:/etc/vsftpd/vsftpd.pem:ro
|
在 vsftpd.conf 加:
1
2
3
4
5
6
7
8
| ssl_enable=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=NO
ssl_ciphers=HIGH
rsa_cert_file=/etc/vsftpd/vsftpd.pem
rsa_private_key_file=/etc/vsftpd/vsftpd.pem
|
客户端必须选"显式 TLS"(FTPS),不是 SFTP(SSH 文件传输)。
三、pure-ftpd 部署
pure-ftpd 是另一个流行的 FTP 服务端,虚拟用户管理更灵活。
3.1 拉取镜像
1
| docker pull stilliard/pure-ftpd
|
3.2 启动容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| docker run -d --name ftp --restart=always \
-p 20-21:20-21 \
-p 30000-30009:30000-30009 \
-v /home/ftp/conf:/etc/pure-ftpd/passwd \
-v /home/ftp/data:/home/ftpusers/www \
-v /home/ftp/data:/home/ftp \
-e "PUBLICHOST=localhost" \
stilliard/pure-ftpd \
/bin/bash -c "useradd ftp; \
usermod -d /home/ftp ftp; \
/run.sh -c 100 -C 10 \
-l puredb:/etc/pure-ftpd/pureftpd.pdb \
-j -i -R \
-P 10.0.0.1 -p 30000:30009"
|
3.3 参数详解
| 参数 | 含义 |
|---|
-c 100 | 最大客户端数 100 |
-C 10 | 每 IP 最多 10 个连接 |
-l puredb:... | 虚拟用户文件 |
-j | 自动创建家目录 |
-i | 匿名用户不允许上传 |
-R | 不允许 chmod |
-P 10.0.0.1 | PASV 模式告知客户端的 IP |
-p 30000:30009 | PASV 端口段 |
3.4 虚拟用户管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 进入容器
docker exec -it ftp /bin/bash
# 添加用户 www(密码 {{REDACTED}})
pure-pw useradd www \
-f /etc/pure-ftpd/passwd/pureftpd.passwd \
-m \
-u ftpuser \
-d /home/ftpusers/www
# 修改家目录权限
chmod -R 777 /home/ftp/data/
# 删除用户
pure-pw userdel www -f /etc/pure-ftpd/passwd/pureftpd.passwd -m
# 查看用户列表
pure-pw list -f /etc/pure-ftpd/passwd/pureftpd.passwd
|
3.5 验证测试
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 在宿主机创建文件
touch /home/ftp/data/test.txt
# 2. 浏览器访问(匿名)
ftp://<SERVER_IP>:21
# 应该能看到 test.txt(无需密码)
# 3. FTP 工具连接
# 用户名 www,密码 {{REDACTED}}
# 看到 test.txt,能下载能上传能删除
# 4. 主被动模式测试
# 在客户端(FileZilla)切换主动 / 被动模式,都能正常上传下载
|
3.6 清理
1
2
| docker rm -f ftp
rm -rf /home/ftp
|
四、Nginx stream 四层代理
FTP 服务器在内网,要暴露到公网?Nginx stream 模块做四层代理。
4.1 完整 stream 配置
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
| stream {
# FTP 控制端口
upstream ftp_control {
server 10.0.0.1:21;
}
server {
listen 21;
proxy_pass ftp_control;
proxy_connect_timeout 1s;
proxy_timeout 10m;
}
# FTP 主动模式数据端口(20)
upstream ftp_data {
server 10.0.0.1:20;
}
server {
listen 20;
proxy_pass ftp_data;
proxy_connect_timeout 1s;
proxy_timeout 10m;
}
# PASV 端口段
server {
listen 21100-21110;
proxy_pass 10.0.0.1:$server_port; # $server_port 是动态端口
}
}
|
关键点:
proxy_pass 10.0.0.1:$server_port:动态端口转发——客户端连 21100 转到 21100,21101 转到 21101proxy_timeout 10m:10 分钟无数据不切断——大文件传输必备proxy_upload_rate / proxy_download_rate:限速(生产推荐)
1
2
3
4
5
6
| server {
listen 21100;
proxy_pass 10.0.0.1:21100;
proxy_upload_rate 10240k; # 上传限速 10MB/s
proxy_download_rate 20480k; # 下载限速 20MB/s
}
|
4.2 SSL / FTPS 代理
FTPS(FTP over TLS)的四层代理较复杂——TLS 加密流无法被 Nginx 解密再加密。
推荐方案:
- 用 HAProxy(原生支持 SSL 透传)
- 或直接在 FTP 服务器端处理 TLS,Nginx 只代理明文
五、客户端使用
5.1 命令行
1
2
3
4
5
6
7
8
| # 上传 / 下载测试
curl -u www:{{REDACTED}} ftp://203.0.113.10/k8s/test.sh | bash
# 列出
curl -u www:{{REDACTED}} ftp://203.0.113.10/
# 主动模式
curl -u www:{{REDACTED}} --ftp-port - ftp://203.0.113.10/file
|
5.2 FileZilla
跨平台 FTP 客户端,首选:
1
2
3
4
| 主机:203.0.113.10
用户名:www
密码:{{REDACTED}}
端口:21
|
关键设置:
- 传输模式:被动(推荐)
- 加密:显式的 TLS(FTPS)
5.3 WinSCP(Windows)
适合自动化脚本(批处理):
1
2
3
4
5
| @echo off
"C:\Program Files\WinSCP\WinSCP.com" ^
/command "open ftp://www:{{REDACTED}}@203.0.113.10/" ^
"put C:\local\file.zip /remote/file.zip" ^
"exit"
|
5.4 lftp(Linux 推荐)
1
2
3
4
5
| # 批量上传
lftp -u www,{{REDACTED}} -e "mirror -R /local/path /remote/path" 203.0.113.10
# 加密传输
lftp -u www,{{REDACTED}} -e "set ftp:ssl-force true; set ftp:ssl-protect-data true; ls" 203.0.113.10
|
六、典型坑位
6.1 登录成功但列不出文件
症状:PASV 模式登录后卡住,服务器等待客户端连数据端口。
原因:
PASV_ADDRESS 没设对- 防火墙没放行 PASV 端口段
- Nginx 反代没配端口段转发
修复:
1
2
3
4
5
| # 1. 确认 PASV_ADDRESS 是公网 IP(不是 127.0.0.1)
echo $PASV_ADDRESS
# 2. 防火墙放行
iptables -A INPUT -p tcp --dport 21100:21110 -j ACCEPT
# 3. Nginx stream 加端口段
|
6.2 主动模式连不上
症状:PORT 模式报 “Connection refused”。
原因:服务器 20 端口主动连客户端,客户端在 NAT / 防火墙后。
修复:改用 PASV 模式,别折腾主动模式。
6.3 上传文件 0 字节
症状:客户端显示上传成功,服务器文件是 0 字节。
原因:PASV 端口被防火墙 drop 掉了。
修复:
1
2
3
4
5
| # 1. tcpdump 看数据连接
tcpdump -i any port 21100-21110
# 2. 防火墙状态
iptables -L -n | grep 21100
|
6.4 vsftpd 启动报 “500 OOPS”
修复:
1
2
3
4
5
6
7
| # 容器内检查 vsftpd.conf
docker exec vsftpd cat /etc/vsftpd/vsftpd.conf | grep -v "^#" | grep -v "^$"
# 常见错误:listen_ipv6=YES + listen=YES 冲突
# 修法:二选一
echo "listen=YES" >> /etc/vsftpd/vsftpd.conf
echo "listen_ipv6=NO" >> /etc/vsftpd/vsftpd.conf
|
七、vsftpd vs pure-ftpd 选型
| 维度 | vsftpd | pure-ftpd |
|---|
| 性能 | 高 | 中等 |
| 稳定性 | 极高 | 高 |
| 配置 | 简洁 | 灵活 |
| 虚拟用户 | 手动 db_load | pure-pw 命令行 |
| TLS / SSL | 支持 | 支持 |
| Docker 镜像 | 官方 vsftpd / fauria | stilliard |
| 适合 | 生产 / 简单场景 | 多用户 / 复杂权限 |
简单场景选 vsftpd(稳定为先),多用户管理选 pure-ftpd(命令行友好)。
八、生产安全清单
8.1 强制 TLS
vsftpd:
1
2
3
4
5
6
7
8
| ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=NO
|
pure-ftpd:
1
2
| echo "TLS 1" > /etc/pure-ftpd/conf/TLS
# 把证书放到 /etc/ssl/private/pure-ftpd.pem
|
8.2 限制用户目录
1
2
3
| # vsftpd
chroot_local_user=YES
allow_writeable_chroot=YES
|
1
2
| # pure-ftpd
echo "YES" > /etc/pure-ftpd/conf/ChrootEveryone
|
8.3 限速
1
2
3
| # vsftpd
local_max_rate=1024000 # 1MB/s
anon_max_rate=512000 # 500KB/s
|
8.4 禁用匿名
8.5 fail2ban 防爆破
1
2
3
4
5
6
7
8
| # /etc/fail2ban/jail.local
[vsftpd]
enabled = true
port = ftp,ftp-data,ftps,ftps-data
filter = vsftpd
logpath = /var/log/vsftpd.log
maxretry = 5
bantime = 3600
|
九、监控与日志
9.1 vsftpd 日志
1
2
3
4
5
6
7
| # 容器内
docker exec vsftpd tail -f /var/log/vsftpd.log
# 输出示例
Mon May 21 03:00:00 2021 [pid 1] CONNECT: Client "1.2.3.4"
Mon May 21 03:00:01 2021 [pid 1] [www] OK LOGIN: Client "1.2.3.4"
Mon May 21 03:00:05 2021 [pid 1] [www] OK UPLOAD: Client "1.2.3.4", "/file.zip", 12345 bytes, 1.2 MB/s
|
9.2 pure-ftpd 日志
1
| docker exec ftp tail -f /var/log/pure-ftpd/pureftpd.log
|
9.3 接入 Loki
1
2
3
4
| logging:
driver: loki
options:
loki-url: "http://internal.example.com:3100/loki/api/v1/push"
|
十、卸载清理
1
2
3
4
5
6
7
| # vsftpd
docker rm -f vsftpd
rm -rf /home/docker/vsftpd
# pure-ftpd
docker rm -f ftp
rm -rf /home/ftp
|
十一、2024+ 视角补充
本文写于 2021-03,2024-2026 这几年里"FTP 该不该用"的共识有明显变化:
FTP 整体趋势:FTP 协议在 2020s 越来越被视为遗留技术——所有主流浏览器(Chrome、Firefox、Edge、Safari)自 2020-2021 起就在逐步淘汰 FTP 协议。RFC 959 设计于 1985,默认明文传输是致命伤(用户名/密码/文件内容全裸奔)。2024+ 的新项目,优先选 SFTP / HTTPS / MinIO / S3 兼容对象存储:
| 场景 | 2021 主流 | 2024+ 推荐 | 理由 |
|---|
| 内部脚本同步 | FTP/FTPS | SFTP(SSH 自带) | SSH 默认安全,无需额外服务 |
| 跨地域文件交换 | FTP 服务器 | MinIO / S3 兼容对象存储 | 签名 URL、版本控制、跨域复制 |
| 浏览器下载 | FTP URL | HTTPS + 静态站点 | 浏览器已不再支持 ftp:// 链接 |
| 客户端工具上传 | FileZilla/FTP | rclone + S3 兼容 | 增量同步、加密、断点续传 |
| 老设备/老协议对接 | FTP | FTP/FTPS(保留) | 无替代品时仍可用 |
vsftpd / pure-ftpd 项目现状:
- vsftpd 仍维护,但 2024 年发布节奏明显放缓(最后大版本 3.0.5 在 2021)。新特性很少,主要是 CVE 修复
- pure-ftpd 状态类似。社区分裂(部分分支用 Rust 重写,但未成熟)
- ProFTPD 仍活跃(2024-2025 有 1.3.8+ 发布),但市场份额不大
- 新部署不推荐用这俩老牌项目,能用 SFTP 就用 SFTP
SFTP 容器化(vsftpd 替代):
1
2
3
4
5
6
7
8
| # atmoz/sftp 是 2024+ 最流行的自托管 SFTP 镜像
docker run -d \
--name sftp \
-p 2222:22 \
-v /home/sftp/users.conf:/etc/sftp/users.conf:ro \
-v /home/sftp/data:/home \
atmoz/sftp \
www:{{REDACTED}}:1001:1001:upload
|
users.conf 一行一个用户:用户名:密码:UID:GID:家目录——比 vsftpd 虚拟用户简单。
FTP 仍必要的场景(2024 仍存在):
- 老的工业设备(数控机床、医疗仪器)只支持 FTP 上报数据
- 老的 ERP / 财务系统只支持 FTP 文件交换
- 与海外客户的 B2B 集成(对方系统只懂 FTP)
- 备份归档脚本(老脚本不愿重写)
这些场景的加固清单:
- 必须 FTPS(FTP over TLS),不能明文
- 必须 fail2ban
- 必须 chroot + 限制用户家目录
- 必须限速(防 DoS)
- 定期审计:看
/var/log/vsftpd.log 谁连了、传了什么 - 计划退出:列入"2026 技术债清单",逐年替换
生产实践补充(2024 更新):
- HAProxy 替代 Nginx stream 做 FTPS 四层代理(Nginx stream 透传 TLS 麻烦)
- CrowdSec 替代 fail2ban:社区共享 IP 黑名单,更新更频繁(2024 主流)
- Caddy + sftp-bridge:Caddy 2024+ 原生支持 SFTP 代理,可以少一层 vsftpd
十二、最佳实践清单
- PASV 模式 + 公网 IP:避免主动模式 NAT 问题
PASV_ADDRESS 必填:客户端才知道连哪里- PASV 端口段防火墙放行:21100-21110(或自定义)
- Nginx stream 四层代理:内网 FTP 暴露公网
- 强制 TLS / SSL:FTPS 而非明文 FTP
- chroot 限制用户家目录:防止越权访问
- 限速:避免大文件占满带宽
- 禁用匿名:除非业务需要
- fail2ban / CrowdSec 防爆破:5 次失败封 1 小时
- 定期清理日志:vsftpd.log 不会自动 rotate
- 新项目优先 SFTP / S3:FTP 仅作为兼容老系统的备选
下一步