Debian SSH 与 SFTP 分离:独立端口、Chroot 隔离与 internal-sftp
1. 服务介绍
SSH 提供远程登录和加密传输能力,SFTP 是 SSH 的文件传输子系统。将普通 SSH 管理入口和 SFTP 文件交换入口分离,可以让管理员继续使用标准 SSH 端口,同时为文件用户启用独立端口、Chroot 目录和 internal-sftp,禁止其获得 Shell、端口转发和 TTY 权限。
本文以 Debian 系统为例,创建一个独立的 sshd 实例监听 SFTP 端口 54321。普通 SSH 服务继续使用系统默认配置,SFTP 实例使用独立配置文件和 systemd 服务管理。SFTP 用户只能访问自己的 Chroot 目录中的上传目录,不能浏览服务器真实根目录,也不能通过 SSH 执行命令。
2. 准备运行环境
• 操作系统:Debian 系 Linux;需要安装 openssh-server 和 openssh-client。
• 操作权限:root 或具备 sudo 权限的管理员账号。
• 管理 SSH:系统默认 SSH 服务继续监听 TCP 22;不要让两个 sshd 实例抢占同一端口。
• SFTP 端口:本文使用 TCP 54321,必须同步配置防火墙、安全组和客户端。
• 用户:准备专用 SFTP 用户,例如 sftpuser;该用户不应拥有交互式 Shell。
• 目录:Chroot 根目录必须由 root 拥有且不能被 SFTP 用户写入;在其下创建由用户拥有的 upload 子目录。
• 认证:优先使用公钥认证;不要把密码写进命令、脚本或文章。
• 操作建议:修改独立配置前先备份;先执行 sshd -t -f 语法检查,再启动新实例。
3. 相关知识
• SFTP 不等于 FTP。它运行在 SSH 加密通道中,不使用 FTP 的 TCP 21、主动模式或被动模式端口。
• internal-sftp 由 OpenSSH 内部提供,不需要单独安装 sftp-server 二进制,也不需要启动 vsftpd。
• ChrootDirectory 会改变 SFTP 用户看到的根目录。Chroot 根目录及每一级父目录必须由 root 拥有且不能被普通用户写入。
• ForceCommand internal-sftp 强制用户进入 SFTP,阻止用户通过 SSH 执行 Shell 命令。
• PermitTTY no、AllowTcpForwarding no、X11Forwarding no 可进一步关闭终端、端口转发和 X11 通道。
• 独立 sshd 使用独立配置文件、PID 文件和 systemd 单元;主 SSH 服务 reload 不等于 SFTP 实例 reload。
• sftp -P 使用大写 -P 指定端口;普通 ssh -p 使用小写 -p,两者参数不能混淆。
4. 实验步骤
4.1 安装 OpenSSH 并确认主服务
bash
apt update
apt -y install openssh-server openssh-client
systemctl enable --now ssh
systemctl --no-pager --full status ssh
ss -lntp | grep -E ':22\b'
备份主配置和后续独立配置路径:
bash
cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)
touch /etc/ssh/sshd_sftp_config
chmod 600 /etc/ssh/sshd_sftp_config


4.2 创建 SFTP 用户和 Chroot 目录
创建专用组、用户和目录。用户 Shell 使用 nologin,即使用户误通过普通 SSH 端口连接,也不能获得交互式 Shell:
bash
groupadd --system sftpusers
useradd -g sftpusers -d /upload -s /usr/sbin/nologin sftpuser
install -d -o root -g root -m 755 /srv/sftp/sftpuser
install -d -o sftpuser -g sftpusers -m 750 /srv/sftp/sftpuser/upload
id sftpuser
namei -l /srv/sftp/sftpuser/upload
如果使用公钥认证,将公钥保存到 Chroot 外部的 root 管理目录,避免把 .ssh 放在用户可写目录:
bash
install -d -o root -g root -m 755 /etc/ssh/authorized_keys
install -o root -g root -m 600 /tmp/sftpuser.pub /etc/ssh/authorized_keys/sftpuser
/tmp/sftpuser.pub 只是临时文件示例,使用后应删除:
bash
rm -f /tmp/sftpuser.pub


4.3 创建独立 sshd 配置
创建 /etc/ssh/sshd_sftp_config:
conf
Port 54321
ListenAddress 0.0.0.0
PidFile /run/sshd-sftp.pid
UsePAM yes
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
Subsystem sftp internal-sftp
Match Group sftpusers
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp -d /upload
PermitTTY no
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
配置要点:
• Port 54321 与主 SSH 的 22 分离;防火墙只向需要的来源开放。
• AuthorizedKeysFile 使用 Chroot 外部绝对路径,避免密钥文件受用户目录权限影响。
• ChrootDirectory 指向 root 拥有的目录;用户只能写入 upload 子目录。
• ForceCommand internal-sftp -d /upload 登录后直接进入上传目录,不启动 Shell。
• PasswordAuthentication no 只允许公钥登录。若实验必须临时使用密码,验证完成后立即关闭。
检查独立配置:
bash
sshd -t -f /etc/ssh/sshd_sftp_config
sshd -T -f /etc/ssh/sshd_sftp_config | egrep 'port|chrootdirectory|forcecommand|authorizedkeysfile|passwordauthentication'
4.4 创建 systemd 服务
创建 /etc/systemd/system/ssh-sftp.service:
ini
[Unit]
Description=Dedicated OpenSSH SFTP service
After=network.target
[Service]
Type=simple
ExecStart=/usr/sbin/sshd -D -e -f /etc/ssh/sshd_sftp_config
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
加载并启动独立实例:
bash
systemctl daemon-reload
systemctl enable --now ssh-sftp
systemctl --no-pager --full status ssh-sftp
ss -lntp | grep -E ':54321\b'
防火墙、安全组只允许实际 SFTP 来源访问 54321。不要因为启用 SFTP 就放行所有公网地址。
4.5 客户端连接测试
使用大写 -P 连接 SFTP:
bash
sftp -P 54321 -i ~/.ssh/id_ed25519 sftpuser@203.0.113.10
在 SFTP 交互界面执行:
text
pwd
ls
put ./test.txt
ls -l
get test.txt ./download-test.txt
bye
使用 SSH 命令连接同一端口时,不应得到普通 Shell:
bash
ssh -p 54321 -i ~/.ssh/id_ed25519 sftpuser@203.0.113.10
客户端可以进入 SFTP 说明传输链路正常;如果 SSH 被 ForceCommand internal-sftp 接管,用户不能执行 id、ls / 等 Shell 命令。
5. 验证结果
5.1 配置和服务状态
bash
sshd -t -f /etc/ssh/sshd_sftp_config
systemctl is-active ssh ssh-sftp
systemctl --failed
journalctl -u ssh-sftp -n 80 --no-pager
预期结果:独立配置无语法错误;主 SSH 和 SFTP 实例均为 active;日志无持续启动失败。
5.2 端口监听
bash
ss -lntp | grep -E ':(22|54321)\b'
预期结果:22 由主 SSH 服务监听,54321 由独立 SFTP sshd 监听。若两个实例监听同一端口,独立服务会启动失败。
5.3 SFTP 隔离验证
bash
sftp -P 54321 -i ~/.ssh/id_ed25519 sftpuser@203.0.113.10
进入后验证:
• pwd 显示 /upload 或配置的起始目录。
• 可以上传和下载 upload 内文件。
• 不能切换到服务器真实根目录。
• 不能通过 SSH 执行 Shell 命令。
• 用户不能访问其他 SFTP 用户目录。
服务端查看目录和权限:
bash
find /srv/sftp -maxdepth 3 -printf '%M %u %g %p\n'
namei -l /srv/sftp/sftpuser/upload
5.4 日志验证
bash
journalctl -u ssh-sftp --since '10 minutes ago' --no-pager
tail -n 80 /var/log/auth.log
登录成功、上传失败、目录权限错误和密钥认证失败都应能从日志找到线索。不要只看客户端一句 Connection closed 就判断服务异常。
6. 常见问题
6.1 `ssh-sftp` 启动失败
先检查端口冲突、PID 文件和配置语法:
bash
sshd -t -f /etc/ssh/sshd_sftp_config
ss -lntp | grep -E ':54321\b'
journalctl -u ssh-sftp -n 100 --no-pager
6.2 Chroot 报权限错误
Chroot 根目录和父目录必须由 root 拥有且不能被组或其他用户写入;可写目录放在 Chroot 内部:
bash
stat -c '%A %a %U %G %n' /srv /srv/sftp /srv/sftp/sftpuser /srv/sftp/sftpuser/upload
6.3 SFTP 用户进入后不能上传
检查 upload 目录属主、属组和权限;不要把整个 Chroot 根目录改成用户属主:
bash
chown sftpuser:sftpusers /srv/sftp/sftpuser/upload
chmod 750 /srv/sftp/sftpuser/upload
6.4 公钥认证失败
确认独立配置使用的密钥路径、文件属主和权限:
bash
ls -l /etc/ssh/authorized_keys/sftpuser
sshd -T -f /etc/ssh/sshd_sftp_config | grep authorizedkeysfile
journalctl -u ssh-sftp -n 100 --no-pager
6.5 用户获得了 Shell
检查 ForceCommand internal-sftp、Match Group sftpusers 的缩进和用户组归属:
bash
id sftpuser
sshd -T -C user=sftpuser,host=203.0.113.10,addr=192.0.2.20 \
-f /etc/ssh/sshd_sftp_config | egrep 'forcecommand|chrootdirectory|permittty|allowtcpforwarding'
6.6 客户端误用 FTP 命令
该服务是 SFTP,不是 FTP/FTPS。使用 sftp -P 54321 或支持 SFTP 的客户端;不要用 ftp、lftp ftp:// 或 TCP 21 测试。