그래서 이번 글은 가장 흔한 설정 실수 3개를 먼저 후보로 두고, 각각을 로그/설정으로 확인한 뒤 해결하는 방식으로 정리했습니다.
우선 공통으로, “실패가 어디에서 나는지”를 로그에서 먼저 잡아두면 시행착오가 확 줄어듭니다.
시작: 증상 재현 + 로그 확인(공통 준비)
클라이언트에서 업로드가 실패하는 정확한 에러 메시지를 먼저 확보하세요. 예: Permission denied, Failure, Write failed, broken pipe 등.
- sshd 로그: /var/log/auth.log
- systemd 사용 시: journalctl -u ssh -f (또는 배포판에 따라 journalctl -u sshd -f)
- SFTP 세션 관련 키워드: subsystem request for sftp, internal-sftp, chroot, refused, permission denied
로그에서 사용자가 어떤 경로로 들어갔는지(홈 디렉터리/Chroot)와 어떤 동작에서 막혔는지(디렉터리 생성/파일 생성/rename)가 찍히는지부터 봅니다.
이제 원인 후보 3개로 나눠 점검합니다.
원인 후보 1) Chroot 디렉터리 권한/소유자 규칙 위반(로그인은 되는데 업로드만 실패)
SFTP를 안전하게 쓰려고 ChrootDirectory를 설정해 둔 경우, “들어가는 루트 디렉터리”는 보안 규칙이 엄격합니다. 대표적으로 chroot 경로(그리고 상위 경로)가 root 소유이고, group/other가 쓰기 불가여야 합니다. 이 규칙을 어기면 로그인은 되거나, 혹은 접속 직후 튕기거나, 업로드만 실패하는 형태로 나타날 수 있습니다.
확인(로그/설정)
- /etc/ssh/sshd_config에서 Chroot 관련 설정 확인: ChrootDirectory, Match User, Match Group
- /var/log/auth.log에서 chroot/permission 관련 문구 확인
- ChrootDirectory로 지정한 경로와 그 상위 경로의 소유자/권한 확인
해결(안전한 디렉터리 구조로 재정리)
- Chroot 루트(예: /sftp, /home/sftpuser 등)는 root:root 소유 + 쓰기 권한 제거
- 사용자가 쓸 실제 업로드 폴더는 chroot 안쪽에 별도로 만들고 그 폴더에만 사용자 쓰기 권한 부여(예: /sftp/USERNAME/upload)
핵심은 “chroot로 잡아둔 루트”를 사용자가 쓰지 못하게 하고, 실제 쓰기 폴더를 한 단계 더 안쪽에 두는 겁니다. 그 다음, 설정 검증 후 ssh 데몬을 재시작합니다.
- sshd 설정 검증: 배포판에 따라 sshd -t 또는 /usr/sbin/sshd -t
- 재시작: systemctl restart ssh (환경에 따라 sshd)
원인 후보 2) Subsystem sftp가 내부 SFTP로 고정되지 않음(내부/외부 경로 꼬임)
SFTP는 sshd의 Subsystem 설정에 따라 동작이 달라집니다. Debian에서 흔한 실수는, internal-sftp 대신 외부 바이너리 경로를 지정해 두었는데(또는 예전 설정이 남아있는데) 실제 바이너리가 없거나 권한/경로가 맞지 않아 “접속은 되지만 파일 작업에서 이상 동작”이 발생하는 경우입니다.
확인(설정)
- /etc/ssh/sshd_config에서 Subsystem sftp 라인을 확인
- Match 블록 안에서 Subsystem을 따로 덮어쓰는 설정이 있는지 확인
- 로그에 subsystem request for sftp 뒤로 에러가 이어지는지 확인
해결(권장: internal-sftp로 단순화)
- Subsystem sftp internal-sftp 형태로 단순하게 유지
- Chroot를 쓰는 경우, 보통 ForceCommand internal-sftp 조합이 안정적입니다(정책에 따라 필요 시)
설정을 바꿨다면 반드시 sshd -t로 문법 검증 후 재시작하고, 클라이언트에서 업로드/폴더 생성/rename까지 한 번에 테스트해 보세요. “로그인 성공”만으로는 검증이 끝나지 않습니다.
원인 후보 3) 방화벽/포트 오해(Cloudflare 설정과 섞여 판단이 흔들리는 케이스)
Cloudflare를 쓰는 환경에서 가장 자주 생기는 오해는 이겁니다: “Cloudflare 뒤로 들어오니까 SFTP도 Cloudflare를 탄다.” 그런데 일반적인 구성에서는 SFTP(SSH, 22/tcp)는 Cloudflare 프록시 대상이 아닙니다(Cloudflare Spectrum 같은 별도 기능을 쓰지 않는 한). 즉, SFTP는 클라이언트 → 서버로 직접 들어오며, 따라서 서버의 Security Group/방화벽(UFW, iptables)에서 22 포트를 허용하지 않거나, 회사/공유기/ISP에서 22를 막으면 접속/전송이 불안정해질 수 있습니다.
확인(체크리스트)
- DNS에서 SFTP 접속 대상이 정말 서버 IP를 가리키는지(웹용 레코드와 혼동하지 않는지)
- Cloudflare에서 해당 레코드가 프록시(주황 구름)인지 여부 확인: SFTP는 보통 DNS only로 두는 편이 안전
- 서버 방화벽에서 22/tcp 허용 여부 확인(UFW 사용 시 규칙 확인)
- 서버 sshd가 실제로 0.0.0.0:22 또는 의도한 IP/포트에 리슨 중인지 확인
해결(원칙: 웹과 SSH를 분리해서 생각)
- SFTP용 접속은 가능하면 별도 호스트명(예: sftp.example.com)을 만들고 DNS only로 운영
- 보안 정책상 22 대신 다른 포트를 쓰는 경우, 포트 변경과 방화벽 허용이 함께 되어 있는지 재점검
- 접속이 “가끔만” 끊기면, 중간 장비(사내망, 공유기)에서 SSH 세션을 끊는 정책이 있는지도 의심
이 파트는 Cloudflare 자체 문제라기보다, Cloudflare를 쓰는 순간 “접속 경로”에 대한 가정이 틀어져서(프록시로 착각) 점검이 늦어지는 문제가 더 큽니다.
정리 체크: 10분 안에 보는 빠른 점검 순서
- 1) /var/log/auth.log 또는 journalctl에서 업로드 실패 시점 로그 확보
- 2) ChrootDirectory 사용 여부 확인 → chroot 루트 경로가 root 소유/쓰기 금지인지 확인
- 3) Subsystem sftp가 internal-sftp인지 확인(중복/Match 덮어쓰기 포함)
- 4) Cloudflare 프록시 여부와 무관하게, 서버 방화벽에서 22/tcp(또는 SSH 포트) 허용 확인
- 5) sshd -t로 설정 검증 후 systemctl restart ssh
- 6) “로그인”만 말고 업로드/폴더 생성/rename까지 동작 테스트
마무리
SFTP는 “인증/세션”과 “파일 시스템 권한/Chroot 규칙”이 동시에 맞아야 정상 동작합니다. 로그를 먼저 보고, Chroot/Subsystem/방화벽(Cloudflare 오해 포함) 3가지만 차례대로 확인해도 대부분 빠르게 해결됩니다.
그래도 애매하게 남으면, 실패 순간의 auth.log 한 줄(개인정보 가리고)과 sshd_config의 Match 블록만 따로 떼어 다시 점검하는 게 가장 효율적입니다.