SSH 오류 메시지는 일부러 무뚝뚝합니다. 프로토콜은 낯선 이에게 왜 돌려보냈는지 알려 주지 않습니다. 보안으로는 좋고 사용성으로는 나쁩니다. 눈에 보이는 메시지는 실제 문제에서 두 단계쯤 떨어져 있을 때가 많습니다. 이 글은 실제로 마주치는 오류 열세 가지, 각각이 진짜 뜻하는 바, 그리고 해결로 가는 가장 빠른 길을 짚는 현장 안내서입니다.
시작 전에 도구 하나: ssh -v user@host(-vvv까지 붙일 수 있습니다 — 그 출력 읽는 법(영어)). 아래의 거의 모든 수수께끼는 핸드셰이크의 어느 단계에서 죽는지 보는 순간 더 이상 수수께끼가 아니게 됩니다.
오류 한눈에 보기
| 오류 | 의미 | 첫 조치 |
|---|---|---|
Connection refused | 호스트는 응답했으나 그 포트에 수신 중인 것이 없음 | sshd가 떠 있고 접속한 포트를 쓰는지 확인 |
Connection timed out | 경로에서 패킷이 사라짐 | 클라우드 방화벽 / 보안 그룹 확인 |
Permission denied (publickey) | 제시한 키가 하나도 수락되지 않음 | 사용자, 그다음 키, 그다음 authorized_keys 확인 |
Permission denied (프롬프트 없음) | 비밀번호 인증이 꺼짐; 키로 해결해야 함 | 키를 쓰거나, 공급사 콘솔로 접속 |
Host key verification failed | 신뢰한 뒤 서버 키가 바뀜 | 별도 경로로 확인한 뒤 ssh-keygen -R |
Too many authentication failures | 에이전트가 키를 너무 많이 제시함 | -i와 함께 -o IdentitiesOnly=yes |
UNPROTECTED PRIVATE KEY FILE | 키 파일을 남이 읽을 수 있음 | 개인 키에 chmod 600 |
Broken pipe | 유휴 연결이 끊김 | ServerAliveInterval 추가 |
no matching key exchange method | 레거시 서버, 최신 클라이언트 | 그 호스트에만 알고리즘 하나 다시 허용 |
Connection reset by peer | SSH 인사 전에 TCP가 세게 닫힘 | fail2ban / MaxStartups 확인 |
Bad configuration option | 설정 줄을 쓰기엔 클라이언트가 너무 오래됨 | ssh 업그레이드, 또는 해당 줄 가드 |
did not receive identification string | 무언가 연결했으나 배너를 보내지 않음 | 대개 스캐너; 서버 쪽에서 무시 |
Unspecified GSS failure | Kerberos 시도가 실패한 뒤 폴백됨 | 로그인이 결국 되면 무시 |
1. Connection refused
머신에는 닿지만, 두드린 포트에 수신 중인 것이 없습니다. 이건 거의 내 탓이 아닙니다:
- sshd가 돌고 있지 않음 — 콘솔에서
systemctl status sshd(VPS 공급사는 바로 이런 순간을 위한 웹 콘솔이 있습니다); - 비표준 포트에서 수신 중 —
ssh -p 2222를 해 보거나/etc/ssh/sshd_config의Port를 확인하세요; - 방화벽이 포트를 (드롭이 아니라) 적극적으로 거부하고 있음.
서버를 방금 다시 만들었다면 방화벽입니다. 어제까지 됐다면 sshd가 죽었거나 포트가 바뀐 것입니다.
전체 해결: connection refused vs timed out 해부.
2. Connection timed out
refused와 다릅니다. 내 패킷이 사라지고 있습니다. 호스트가 죽었거나, IP가 틀렸거나, 아니면 — 클라우드 머신에서 가장 흔하게 — 보안 그룹 / 네트워크 ACL이 포트 22를 조용히 드롭합니다. 공급사의 방화벽 규칙을 먼저 확인하고, 그다음 아웃바운드 22를 막는 네트워크에 있는지 확인하세요(호텔과 사무실 게스트 Wi-Fi가 이걸 즐겨 합니다. 포트 443 기반 접속이나 다른 네트워크로 확인해 보세요).
전체 해결: 같은 refused vs timed out 안내에 timed-out 갈래가 단계별로 있습니다.
3. Permission denied (publickey)
역대 챔피언입니다. 서버는 키 인증만 받고, 제시한 키를 하나도 받지 않았습니다. 가능성 높은 순서대로:
- 잘못된 사용자. Ubuntu 이미지는
ubuntu, Amazon Linux는ec2-user, 대부분의 맨 VPS는root입니다. 같은 키라도 사용자명이 틀리면 정확히 이 오류가 납니다. 클라우드 이미지는 저마다 로그인 사용자를 고르는데, 그게 EC2/VPS 첫 로그인(영어)의 절반입니다. - 내 키가 제시되지 않음.
ssh -v는 시도한 모든 키를 보여 줍니다. 내 키가 목록에 없으면 명시적으로 가리키세요:ssh -i ~/.ssh/id_ed25519 user@host. - 키가 서버에 없음. 공개 키 쪽이
~user/.ssh/authorized_keys에 한 줄로 있어야 합니다. root가 아니라 그 사용자의 것이어야 합니다. - 서버 쪽 권한. 홈 디렉터리,
~/.ssh(700), 또는 파일 자체(600)가 그룹 쓰기 가능이면 sshd는authorized_keys를 인정하지 않습니다. ‘키를 복사했는데도 안 돼요’의 전형입니다.
전체 해결: Permission denied (publickey), 빠르고 체계적인 해결(영어)이 모든 원인을 순서대로 처리합니다.
4. 비밀번호 프롬프트를 기대했는데 나오는 Permission denied
비밀번호 인증을 원했는데 묻지도 않았다면, 서버에 PasswordAuthentication no가 걸려 있는 것입니다. 대부분의 클라우드 이미지가 그렇게 출고되고, 그게 올바른 기본값입니다. 공급사 콘솔이나 기존 키로 들어간 다음, 비밀번호를 다시 켜는 대신 새 키를 제대로 추가하세요.
5. Host key verification failed / REMOTE HOST IDENTIFICATION HAS CHANGED
ASCII 해골 기운이 감도는 무서운 배너입니다. 서버가 전에 신뢰한 것과 다른 키를 제시했습니다. 설명은 둘입니다. 머신이 정당하게 재구축/재설치됐거나(압도적으로 흔한 경우), 나와 서버 사이의 무언가가 서버인 척하고 있거나(경고가 존재하는 바로 그 경우)입니다.
서버가 재구축됐다는 걸 아는 경우에만 — 오직 그때만 — ssh-keygen -R hostname이 낡은 항목을 지우고, 다음 연결에서 다시 신뢰합니다. 그 제거를 작업 흐름에 무턱대고 스크립트로 넣지 마세요. 경고가 진짜인 그 한 번이, 받을 수 있는 유일한 경고입니다.
전체 해결: Host key verification failed, 올바르게 해결하는 법(영어).
6. Too many authentication failures
에이전트가 신나게 키 여섯 개를 제시했고, 서버의 MaxAuthTries가 그만하라고 한 겁니다. 클라이언트에게 의도한 키만 제시하라고 하세요:
ssh -o IdentitiesOnly=yes -i ~/.ssh/the_right_key user@host
…아니면 ~/.ssh/config에 호스트별로 영구화하세요. 키를 많이 들고 다닌다면, Host * 아래의 IdentitiesOnly yes가 이 오류에서 영영 벗어나게 해 줍니다.
7. WARNING: UNPROTECTED PRIVATE KEY FILE
개인 키를 남이 읽을 수 있어서 클라이언트가 사용을 거부합니다. chmod 600 ~/.ssh/id_ed25519. Windows에서는 머신 간에 키를 복사하거나 클라우드 드라이브에서 빼낸 뒤 나타납니다. 파일의 보안 속성에서 고치거나, 키를 동기화 폴더에 아예 두지 마세요.
8. client_loop: send disconnect: Broken pipe (한눈파는 사이 죽는 세션)
나와 서버 사이의 NAT 라우터나 공격적인 방화벽이 유휴 연결을 끊은 것입니다. 클라이언트 쪽 keepalive로 해결합니다:
Host *
ServerAliveInterval 30
ServerAliveCountMax 4
침묵을 2분까지 견디고, 30초마다 패킷을 보내 NAT 테이블이 나를 잊지 않게 합니다. 세션이 유휴가 아니라 부하 중에 죽는다면 그건 다른 종류의 문제입니다. keepalive가 아니라 MTU(특히 VPN)를 보세요.
9. no matching key exchange method found / no matching cipher
오래된 무언가와 대화 중입니다. 레거시 암호에 멈춰 있는 라우터, 스위치, 어플라이언스입니다. 최신 OpenSSH는 그 알고리즘들을 그럴 만한 이유로 제거했습니다. 그 호스트 하나에만 다시 허용하고, 절대 전역으로 하지 마세요:
Host ancient-switch
KexAlgorithms +diffie-hellman-group14-sha1
Ciphers +aes256-cbc
오류 메시지가 서버가 제시한 알고리즘을 알려 줍니다. 그중 가장 덜 나쁜 것을 추가하세요.
10. ssh_exchange_identification: read: Connection reset by peer
TCP 연결이 열리자마자, SSH가 인사도 하기 전에 세게 닫혔습니다. 흔한 용의자: fail2ban이나 DenyHosts가 내 IP를 차단했거나(콘솔에서 /etc/hosts.deny와 fail2ban의 jail을 확인), sshd가 과부하거나(MaxStartups가 포트를 두드리는 스캐너에 소진됨), 로드 밸런서 헬스 체크가 연결을 먹고 있는 것입니다. fail2ban이고 내 서버라면, 해결은 콘솔 로그인과 fail2ban-client unban <your-ip>입니다. 그런 다음 SSH를 스캐너 레이더 밖으로 옮기거나 jail을 조여 다음 주의 애꿎은 피해자가 되지 않게 하세요.
11. Bad configuration option: pubkeyacceptedalgorithms
서버 문제가 아닙니다. 로컬 ssh가 설정 파일을 쓰기엔 너무 오래된 것입니다. PubkeyAcceptedAlgorithms는 OpenSSH 8.5에서 등장했습니다(PubkeyAcceptedKeyTypes를 대체했습니다). 그 줄을 오래된 클라이언트 — 특정 연식의 순정 macOS ssh, 오래된 배포판, 미니멀 컨테이너 이미지 — 에 먹이면 파일 자체를 파싱하길 거부합니다. 벗어나는 길 세 가지: 클라이언트를 업그레이드하기(ssh -V가 현재 위치를 알려 줍니다), 최신 버전이 여전히 별칭으로 받아 주는 옛 표기 PubkeyAcceptedKeyTypes를 쓰기, 또는 능력 있는 클라이언트만 읽도록 그 줄을 가드하기:
Match all
# only OpenSSH >= 8.5 gets here via Include ordering
— 실제로 가장 간단한 가드는 버전에 민감한 옵션을 Include로 끌어오는 별도 파일에 두는 것입니다. 그러면 같은 dotfiles를 쓰는 오래된 클라이언트는 그냥 건너뜁니다. 같은 모양의 오류(Bad configuration option: <something>)는 늘 같은 뜻입니다. 그 단어를 이 바이너리가 모른다는 것입니다. 옵션 철자를 먼저, 바이너리 버전을 그다음에 확인하세요.
12. did not receive identification string / invalid ssh identification string
둘 다 SSH의 가장 첫 단계, 어떤 암호화도 하기 전의 배너 교환에 있습니다. did not receive identification string from <ip>는 무언가 포트 22로 TCP 연결을 열었지만 SSH-2.0-... 줄을 끝내 보내지 않았을 때 sshd가 서버 쪽에 기록합니다. 포트 스캐너, 로드 밸런서 헬스 체크, 모니터링 프로브, 또는 포기한 클라이언트입니다. 서버 로그에서는 거의 항상 결함이 아니라 배경 잡음입니다. 단, 내 클라이언트가 연결에 실패하는 경우는 예외인데, 그때는 경로의 무언가(프록시, TCP 래퍼, IDS)가 배너를 먹고 있는 것입니다.
kex_exchange_identification: ... invalid ssh identification string는 그 거울상입니다. 무언가가 응답은 했는데 SSH가 아닌 데이터로 했습니다. 흔한 원인, 순서대로: ssh를 엉뚱한 포트로 겨눈 경우 — HTTP 서버, 데이터베이스, HTML을 돌려주는 캡티브 포털; 경로의 미들박스가 연결을 가로채 서버 대신 응답하는 경우; 또는 ProxyCommand/ProxyJump 설정 오류로 SSH 스트림이 아닌 것이 클라이언트로 흘러드는 경우 — 배너가 와야 할 자리에 프록시의 오류 페이지가 도착한 것입니다. 정말로 sshd와 대화 중인지 확인하세요. nc <host> 22는 SSH-2.0-OpenSSH... 배너를 즉시 출력해야 합니다. HTML이나 아무것도 출력하지 않는다면, 내 키가 아니라 포트(또는 호스트, 또는 프록시 홉)가 틀린 것입니다.
13. Unspecified GSS failure / debug1: unspecified GSS failure. Minor code may provide more information
Kerberos 잡음입니다. OpenSSH는 기본적으로 키보다 먼저 GSSAPI(Kerberos) 인증을 시도하고, Kerberos 영역에 있지 않으면 — 대부분의 사람이 그렇습니다 — 실패해 이걸 디버그 수준으로 기록한 뒤 키 인증으로 폴백해 정상적으로 연결합니다. 연결이 결국 성공한다면 이 줄은 무해하니 무시하세요. 실제로 Kerberos SSO에 의존하는데 인증이 실패하는 경우에만 문제가 되며, 그때는 진짜 티켓/영역 문제입니다(klist, 시계 오차, DNS의 호스트 SPN을 확인하세요). Kerberos가 아닌 구성에서 잡음을 멈추려면 설정에 GSSAPIAuthentication no를 추가하세요.
해결을 오래가게 만들기
이 중 절반은 일회성 해결이고, 나머지 절반은 다시 돌아오지 않도록 ~/.ssh/config에 한 줄 넣을 가치가 있습니다. keepalive, IdentitiesOnly, 호스트별 포트와 신원이 그렇습니다. 설정이 기억을 이깁니다.
기억을 이기는 또 하나는 상태를 대신 지켜 주는 클라이언트입니다. Termalin은 호스트별 설정 — 포트, 사용자, 키, keepalive — 을 호스트 자체에 함께 저장하고, 첫 연결에서 서버를 검증하며 지문이 바뀌면 큰 소리로 경고하고, 상태 표시줄에 머신의 활력 징후를 실시간으로 보여 줍니다. 그래서 ‘그 머신이 떠 있기나 한가?‘가 더 이상 진단 단계가 아니게 됩니다. Free 요금제는 호스트 수 제한이 없습니다. 받아서 이 오류 몇 개를 영구히 은퇴시키세요.