1. 先说清楚scp、sftp、WinSCP的关系为什么禁一个等于禁三个不少朋友一听到“禁用scp、sftp和WinSCP”下意识会以为要装什么安全软件、改防火墙甚至有人直接去把OpenSSH卸载了。这完全是想偏了。这三样东西本质上是同一个链路的不同环节scp和sftp都是基于SSH协议的文件传输方式WinSCP则是Windows下用来连接这两种协议的图形客户端。先捋一下它们各自的位置scp全称Secure Copy基于SSH协议把远程文件复制到本地或者把本地文件推到远程。它在服务端实际是调用了远程主机的scp命令通过SSH通道执行。底层实现早期是走RCP协议后来新版本OpenSSH默认走SFTP协议的内部模式但对外表现仍然是scp。sftpSecure File Transfer Protocol它不是简单的“加密版FTP”而是SSH协议的一个子系统。当客户端发起SFTP连接时SSH服务端会启动/usr/lib/openssh/sftp-server这个子系统程序来处理文件传输。所以SFTP和SSH共用22端口不需要单独开端口。WinSCP一个Windows平台的开源图形化SFTP/SCP客户端。它支持SFTP协议和SCP协议两种模式连接Linux服务器时要么走SFTP子系统要么走SCP命令通道二者必居其一。搞清楚这个关系你就明白禁用思路了只要在服务端把SSH协议里的SFTP子系统和SCP执行通道关掉WinSCP自然也就废了因为它不是独立的服务器组件只是一个客户端工具。真正要动刀的地方只有一个文件/etc/ssh/sshd_config。这篇文章默认环境是Ubuntu 16.04 OpenSSH 7.2p2操作思路对其他版本也通用只是个别配置项在旧版本上可能不存在我会在对应位置特别说明。2. 动手前准备确认环境、备份配置、留好逃生通道老司机都知道改SSH配置最怕的不是配置不对而是改完连不上。Ubuntu 16.04的SSH服务是sshd配置文件在/etc/ssh/sshd_config修改之前有几个准备工作必须先做。2.1 确认当前SSH版本和子系统配置先登录服务器跑几条命令确认状态# 查看OpenSSH版本 ssh -V # 查看sshd当前生效的配置 sudo sshd -T | grep -E subsystem|sftp|permitrootlogin # 查看sshd_config里现有的Subsystem配置 grep -i subsystem /etc/ssh/sshd_config正常情况下你会看到类似这样的输出subsystem sftp /usr/lib/openssh/sftp-server或者在新一点的版本上可能是Subsystem sftp internal-sftp这两个的区别后面会详细讲先记下来你机器上是哪一种。sshd -T这个命令很重要它不会改动任何东西只是把实际生效的配置打出来不用猜配置有没有被include文件覆盖。2.2 备份配置文件这个步骤看起来废话但每次改完SSH出问题的人十有八九没备份。一个cp命令的事别省sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date %F)我建议备份带上日期以后翻记录能知道是哪天改的。2.3 留好两条以上的连接通道改SSH配置之前务必保证当前已经有一个已建立的SSH连接窗口或者配置了密钥登录且测试无误又或者你有机房带外管理、云控制台的VNC访问通道。为什么因为改完重启sshd之后如果配置有误新的连接会被拒绝而原来已建立的连接不受影响——但是一旦你不小心把这个连接也关了又没有带外通道那就只能提交工单让机房帮你重启了。我自己的习惯是先开两个SSH窗口都连着服务器改完配置后先不要断线用第二个窗口测试新连接能正常建立再关第一个窗口。这条习惯帮我避免过至少三次把自己关在门外的丢人事故。2.4 检查配置语法Ubuntu上提供了一个非常实用的检查命令sudo sshd -t这个命令只检查配置语法不实际生效。如果有错误会提示具体行号和原因没输出就是配置没问题。每次改完sshd_config先跑这命令再重启服务。3. 核心实操全局禁用SFTP子系统和SCP命令准备工作做完了进入正题。先讲全局禁用也就是服务器上所有用户都不能走SFTP和SCP。适合安全要求严格的场景比如跳板机、生产环境堡垒机或者不想让开发人员把代码拉出去的封闭环境。3.1 禁用SFTP子系统SFTP依赖的是sshd_config里这一行Subsystem sftp /usr/lib/openssh/sftp-server这行的意思是当客户端请求SFTP子系统时sshd启动/usr/lib/openssh/sftp-server程序来处理。只要注释掉这一行服务端就根本不提供SFTP子系统客户端发起SFTP连接时会直接收到“subsystem request failed on channel 0”的报错。操作方式sudo vim /etc/ssh/sshd_config找到Subsystem那一行行首加#注释掉保存退出#Subsystem sftp /usr/lib/openssh/sftp-server然后检查语法并重启sudo sshd -t sudo systemctl restart sshdUbuntu 16.04上是systemctl没问题的如果是老版本SysVinit用sudo service ssh restart。重启完本地试一下sftp root你的IP输出应该类似root你的IPs password: subsystem request failed on channel 0 Connection closed能出现这行报错说明SFTP子系统已经被切掉了。3.2 禁用SCP命令传输SCP和SFTP是两条不同的通道只注释Subsystem那行SCP依然能用这是最多人踩的坑。原因很简单老版本的OpenSSH包括Ubuntu 16.04自带的OpenSSH 7.2里scp命令使用的是传统的SCP协议它在服务端通过执行远程scp命令来完成文件复制走的不是SFTP子系统。你注释了Subsystem相当于堵死了SFTP这条路但SCP走了另一条路自然不受影响。怎么验证注释完Subsystem后从你的电脑执行scp user你的IP:/etc/hostname /tmp/你会发现文件还是能拉下来一点毛病没有。那怎么禁SCP方案有几种我按推荐程度从高到低讲方案A把用户的shell改成nologinSCP协议要求在服务端能执行scp命令而执行命令的前提是用户有一个能登录的shell。如果用户shell被改成了/sbin/nologin或/bin/falseSSH连接时不分配shellSCP命令也就没法执行。sudo usermod -s /sbin/nologin username改完再试scp会报错subsystem request failed on channel 0不对这是SFTP的报错。SCP的报错一般是这样user你的IP: Permission denied (publickey,password).或者干脆直接显示/bin/bash: scp: No such file or directory原理上用户的登录shell被改成nologin之后SSH服务器在收到SCP请求时会尝试以该用户身份执行远程命令但由于shell是nologin命令执行直接被拒绝。这个方案的好处是一并禁止了该用户的交互式shell登录——如果这个账号本来就是专门做文件传输的那就完全合理坏处是如果你还想让这个用户能正常ssh登录执行命令那这个方案就不适用了得看方案B。方案B在sshd_config里用ForceCommand限制在sshd配置中你可以在Match User块或全局配置中强制指定某个用户执行特定命令。如果把ForceCommand设为/bin/false那么该用户通过SSH发起的任何命令执行请求都会被替换成执行/bin/falseSCP自然就废了。但这里有个关键点ForceCommand要么放在全局段要么放在Match块里。如果你想针对特定用户需要这样写Match User username ForceCommand /bin/false不过这么写有个副作用该用户的所有SSH命令都会被拒绝包括正常的ssh usernamehost登录。如果只是想禁SCP不想禁SSH登录那就不能用/bin/false得换一个思路。方案C在ssh会话中禁用scp命令适合还想保留shell的用户这个方案有点绕但确实是可以实现只禁SCP不禁登录原理是利用ForceCommand与Match结合将所有SSH命令请求强制交给一个包装脚本脚本里判断如果是SCP命令就拒绝如果是交互式shell就放行。不过说实话这个方案在实际生产中用得少因为维护成本高而且很容易误伤。生产环境里SCP禁用的需求要么是全部禁要么是针对专用传输账号禁很少会出现“要保留shell但不能scp”这种需求。真有这种需求我建议直接考虑上一个方案B的变体——把用户的SSH登录方式改成仅允许密钥登录且限制在特定目录。实践下来最稳妥的办法是全局注释Subsystem禁SFTP 针对需要限制的用户改shell为nologin禁SCP和交互登录。如果需求是“所有用户都不能传文件”这两个操作做完就齐活了。3.3 重启服务并验证全部禁用效果配置改完后完整验证顺序我是这么做的# 1. 检查配置语法 sudo sshd -t # 2. 重启sshd sudo systemctl restart sshd # 3. 从本机测试SFTP sftp user你的IP # 4. 从本机测试SCP scp user你的IP:/etc/hostname /tmp/预期结果命令预期输出sftp userIPsubsystem request failed on channel 0连接关闭scp userIP:file localPermission denied或类似报错文件无法传输WinSCP连接选择SFTP协议报错选择SCP协议也报错到这里你对“禁用”这个概念应该有个清晰的认知了禁用的本质不是在客户端删除软件而是在服务端断掉协议通道。WinSCP只是一辆车路没了车再好也开不过去。4. 更精细的控制用Match User做定向禁用只禁特定账号全局禁用适合安全要求高、业务简单的场景。但现实往往很骨感服务器上十几个账号运维要传文件、开发不能传或者只有某几个外包账号不能往出拉数据。这种时候Match User和Match Group就能派上用场了。4.1 Match User的基本语法和限制Match是sshd_config的一个配置指令用来对特定条件应用额外的配置段。基本语法是Match User 用户名1,用户名2 配置项1 配置项2 Match Group 组名 配置项1要注意几个硬性规则Match必须是配置文件的最后一个有效配置段。在第一个Match之后所有后续配置都必须属于某个Match块不能再写全局配置。Match内的配置项会覆盖全局配置。不是所有全局配置项都能在Match里用。比如Subsystem就是全局专属不能放进Match块里。这意味着你没办法用Match针对某个用户单独禁用SFTP子系统——因为子系统是全局加载的。但你可以换个思路允许全局SFTP但让特定用户的SFTP不可用。4.2 针对特定用户禁用SFTP和SCP实际配置可以这样写Subsystem sftp internal-sftp Match User developer,testuser ForceCommand /bin/false这段配置的效果是全局正常提供SFTP子系统其他用户一切照旧。developer和testuser这两个用户在执行任何SSH命令时都会被强制替换成执行/bin/false命令因此SFTP子系统的请求一样会被拒绝。实测效果sftp developer你的IP会直接报subsystem request failed on channel 0因为客户端请求SFTP子系统也是一种“命令执行”而ForceCommand /bin/false把这个命令换掉了。scp同样废掉因为SCP在服务端本质也是执行远程命令。而ssh developerIP登录进去也只会执行/bin/false然后立即退出。所以这个方案副作用很大目标用户的完整SSH登录功能也会被禁。如果你希望这个用户还能正常SSH登录执行命令只是不能传文件那用ForceCommand就不太合适。这时候可以考虑用Match User配合ChrootDirectory把用户限制在一个隔离目录里再配合ForceCommand internal-sftp实现一个只能SFTP不能SCP也不能shell的环境。但这个属于“受限SFTP”的正向配置思路跟本文的反向禁用不完全一样这里按下不表。4.3 只禁SCP不禁SFTP怎么做有些时候你的需求可能是“禁止scp但保留sftp”比如统一要求所有文件传输必须走SFTP以获得审计日志。这种场景配置思路是反过来既然scp在服务端需要执行远程命令那只要让SSH收到的非交互式命令被拒绝而交互式会话正常理论上就能做到。但实际配置中很难光靠sshd_config一行实现。我的做法是配合ForceCommand加一个包装脚本新建/usr/local/bin/allow-shell-only.sh#!/bin/bash # 只允许交互式shell登录和SFTP子系统其余命令包括scp一律拒绝 if [ -n $SSH_ORIGINAL_COMMAND ]; then case $SSH_ORIGINAL_COMMAND in internal-sftp) exec /usr/lib/openssh/sftp-server ;; *) echo scp is disabled on this server. exit 1 ;; esac else exec /bin/bash fi对应配置Match User targetuser ForceCommand /usr/local/bin/allow-shell-only.sh这个脚本做的事情是如果这个SSH连接带了原始命令即非交互式请求就判断是不是SFTP子系统的调用——是就放行不是就拒绝如果没有原始命令即正常的交互式登录就正常进bash。这个方案不完美因为case $SSH_ORIGINAL_COMMAND里匹配internal-sftp这一项在实际环境中可能因为客户端请求内容不同而匹配不上需要现场调试。所以我一般不会在博客里把这种配置吹成标准答案但思路值得参考——面向特殊场景用包装脚本做命令级控制是sshd_config静态配置搞不定时的常见补充手段。5. 验证与效果检查从客户端实测禁用后的表现配完之后验证这一环不能省。我见过有人改完配置以为生效了结果过了俩星期发现用户还在用scp拉文件一查才发现注释Subsystem那行根本没保存。所以这篇文章专门把验证环节独立出来。5.1 服务端自查先确认sshd当前生效的配置sudo sshd -T | grep -i subsystem如果输出为空说明SFTP子系统确实没加载。如果还有输出检查你是不是注释错行、或者有include文件二次加载了配置。再确认对应用户的shell状态grep username /etc/passwd如果最后一列是/usr/sbin/nologin或/bin/false说明SCP通道被断掉了。5.2 客户端实测从你自己的工作电脑上分别测试# 测试SFTP sftp usernameIP # 测试SCP scp usernameIP:/etc/hostname /tmp/ # 测试SSH登录是否正常 ssh usernameIP我这里放一个实际测试的输出节选$ sftp testuser192.168.1.100 The authenticity of host 192.168.1.100 (192.168.1.100) cant be established. ... testuser192.168.1.100s password: subsystem request failed on channel 0 Connection closedSFTP被正确禁用。再看SCP$ scp testuser192.168.1.100:/etc/hostname /tmp/ testuser192.168.1.100s password:然后卡住几秒最终报错或者被拒绝。因为服务端没有可用的shell来执行scp命令连接会很快关闭。5.3 WinSCP客户端的表现WinSCP需要写清楚因为它报错的方式挺有迷惑性。启动WinSCP填好主机名、用户名、密码协议那里如果选的是SFTP连接时会卡在“初始化SFTP协议”阶段然后弹窗提示连接被异常关闭。服务器发送的消息包含无效数据。The server sent an unexpected message.或者更直白一点Cannot initialize SFTP protocol. Is the host running a SFTP server?如果你把协议切到SCP报错会变成Connection has been unexpectedly closed. Server sent command line.或者Error: Connection refused两种报错看着吓人但其实都是好事——说明服务端的协议通道确实已经关闭了。WinSCP不是独立的服务器端组件它是客户端工具。只要服务端的SFTP子系统和SCP命令通道关了WinSCP换什么设置都连不上。注意网上有些教程会让你在WinSCP里改“高级→SSH→认证→尝试‘键盘交互’认证”之类的选项这些对协议禁用没有任何帮助。WinSCP的协议栈是完整实现了SFTP或SCP的标准流程的服务端不响应对应协议客户端怎么配置都白搭。5.4 验证时的几条经验刚重启完sshd建议等2-3秒再测试因为sshd重启后可能需要短暂时间监听端口。用systemctl is-active ssh确认服务是active状态再测。测试期间保持一个已登录的SSH窗口别关。所有验证做完、确认没问题了再关闭旧窗口。如果验证发现问题需要回滚直接恢复备份sudo cp /etc/ssh/sshd_config.bak.$(date %F) /etc/ssh/sshd_config sudo systemctl restart sshd实测恢复后立即生效不需要重启机器。6. 典型问题与排查技巧实录改成套配置遇到问题太正常了我把这几年群里和博客里问得最多的情况整理出来按场景分类说一下排查思路。6.1 注释了Subsystem sftp但scp还是能用这是最常见的误解。前面已经解释过老版OpenSSH的SCP不走SFTP子系统走的是传统SCP协议在服务端调用远程scp命令完成。所以注释Subsystem只影响sftp命令和WinSCP的SFTP模式不影响scp命令和WinSCP的SCP模式。解决办法就是按第3节的方案把目标用户的shell改成nologin或者用MatchForceCommand限制。6.2 sftp报错“received message too long 1416128883”这个报错本身不是“禁用”引起的但它经常出现在禁用/限制配置后的排查过程中。原因是当SSH连接时远程服务器在SSH会话初始化阶段输出了不属于SFTP协议内容的多余文本导致sftp客户端解析时把这段文本当成了SFTP协议包的长度字段数值异常巨大1416128883。最常见触发场景是用户登录shell被改成了某个自定义脚本或者/etc/motd、/etc/issue、.bashrc里写了echo内容。当sftp客户端发起连接时sshd会先给用户分配shell执行相关启动脚本而shell启动脚本里的echo输出混入了SSH传输流中sftp客户端一解析发现数据长度异常直接报错。排查步骤# 1. 检查用户shell getent passwd username # 2. 检查是否修改了全局motd或issue cat /etc/motd cat /etc/issue # 3. 检查该用户家目录下的.bashrc和.profile cat ~username/.bashrc | tail -20如果发现shell是/bin/bash而且.bashrc里有echo输出注释掉echo即可。如果是自定义shell脚本把非协议内容清掉。这个报错的定位思路同样适用于配置ForceCommand后黑屏卡住、连接立即断开等其他SSH疑难杂症。6.3 重启sshd失败连接全部断开改配置时语法写错重启sshd失败会造成已经建立的连接不受影响但新连接无法登录。如果当前已建立的窗口还在可以马上用sudo sshd -t检查错误并修复。常见错误Match块后写了全局配置ForceCommand路径写错权限不对导致/bin/false无法执行修复后重启sudo systemctl restart sshd如果当前窗口也断了就只能靠带外管理。所以备份和保持多窗口这两件事真不是废话。6.4 用Match User限制了用户但用户还能用密钥scp很多人忽略的一点SSH公钥认证如果配置了authorized_keys里的command前缀或者ForceCommand被全局授权覆盖配置可能不按你设想的执行。排查这类问题先做一个调试# 在服务器上查看sshd实际拿到的请求 sudo journalctl -u ssh -f然后在客户端重新发起scp请求观察服务端日志里显示的命令是什么。如果看到的是/usr/lib/openssh/sftp-server或scp -t说明你的ForceCommand没生效或者被覆盖了。此时检查sshd_config里是否还有其他的Match块把该用户匹配进去了——多个Match块后面的会覆盖前面的配置。6.5 禁用后rsync还能走吗rsync有自己的一套传输协议默认不走SFTP子系统。如果用rsync over ssh走的是SSH的远程命令通道和scp同理——如果你只注释了Subsystemrsync完全不受影响如果你把用户的shell改成了nologin那rsync over ssh也会被禁因为服务端无法执行rsync命令。6.6 这个方案是否影响FTP不影响。服务器上如果还跑着vsftpd、proftpd这类传统FTP服务它们和SSH是完全独立的进程和端口默认21端口。本文讲的禁用只针对SSH协议链路内的文件传输能力。如果你需要连FTP一起禁那是防火墙和FTP服务配置的事别混在一起。6.7 配完以后用户反馈“连接被拒绝”优先检查sshd是否还在跑sudo systemctl status ssh如果sshd正常用ss -lntp | grep :22确认端口监听正常。再检查是不是防火墙规则动了。多数时候是改配置时手滑动了全局配置导致原本能连的IP被拒绝。回滚配置是最快的解法不要想着现场一点点改回来。7. 关于禁用方式的一些个人经验补充最后聊点实际运维的体会这些是文档里不会写的。我在生产环境里做这类操作比较推荐“先定向、后全局”的顺序。也就是先在测试机或者非核心业务机上用Match User先把一两个账号禁掉试水观察几天确认没有误伤业务再决定是否推广到全局。直接一上来就全局禁用如果有些老系统刚好在用scp做备份可能第二天就有同事找你喝茶了。另外建议改完配置之后在服务器上留一个配置文件注释说明比如# 2024-05-20 by xxx: 禁用了SFTP/SCP原因安全加固要求操作单号XXX多人维护的服务器最怕这种“无声修改”后面接手的人看着配置一脸懵排查问题时浪费大量时间。这行注释的成本几乎为零价值却很实在。还有一点Ubuntu 16.04已经比较老如果你是在较新版本的Ubuntu20.04/22.04上操作scp命令本身已经默认改用SFTP协议传输了那么你只需要禁用SFTP子系统就会连带影响scp和WinSCP的SFTP模式不需要再改shell——但WinSCP如果选SCP协议老版本行为依旧所以还是要区分版本来看待。整个操作如果拆成一句话总结那就是scp和sftp是服务端的两个门WinSCP是一把钥匙把两个门都锁上钥匙自然就没有用武之地了。希望这篇梳理能帮你少走一些弯路改配置前记得备份改完后记得验证剩下的交给时间。