SSH: Received disconnect ... Too many authentication failures
안녕하세요, BlueByte입니다. ssh deploy@bastion을 실행했는데 비밀번호를 묻지도 않고 Too many authentication failures로 연결이 끊깁니다. 쓰려던 키는 디스크에 있고 서버에도 등록돼 있는데, 그게 소용이 없습니다. agent가 다른 키를 먼저 여러 개 내밀었고, 서버는 그 하나하나를 시도로 세었으며, 내 키 차례가 오기 전에 연결을 끊은 것입니다. 오늘은 양쪽이 각각 무엇을 남기는지, 내민 키마다 시도가 소모되는 이유, 어떤 키가 나가는지 눈으로 보는 법, 원인별 해결, 그리고 이 일이 반복되지 않도록 신원 목록을 짧게 유지하는 법까지 하나씩 짚어보겠습니다.
끊긴 연결이 클라이언트와 서버에 각각 남기는 것
클라이언트는 서버가 보낸 disconnect 메시지를 받은 그대로 찍습니다:
Received disconnect from 203.0.113.10 port 22:2: Too many authentication failures
Disconnected from 203.0.113.10 port 22OpenSSH는 이 줄을 Received disconnect from %s port %d:%u: %.400s 형식으로 만듭니다 — 호스트, 포트, disconnect 사유 코드, 그리고 서버가 보낸 문구 순입니다. 가운데 2는 서버가 이 disconnect에 실어 보내는 프로토콜 오류 사유 코드라서, 여기서 추가로 읽어낼 의미는 없습니다.
더 쓸모 있는 쪽은 서버이고, 설정을 손대기 전에 먼저 꺼내 볼 값어치가 있습니다:
sudo journalctl -t sshd --since "10 min ago" | grep -i "maximum authentication"sshd[4412]: error: maximum authentication attempts exceeded for deploy from 198.51.100.24 port 51922 ssh2 [preauth]OpenSSH는 이 줄을 auth_maxtries_exceeded()에서 maximum authentication attempts exceeded for %s%.100s from %.200s port %d ssh2 형식으로 찍고, 계정이 존재하지 않으면 사용자 이름 앞에 invalid user 를 붙입니다. 이 접두사는 키 개수 문제인지 사용자 이름 오타인지 가르는 가장 빠른 단서이고, 확인 비용은 명령 한 줄입니다. 위 명령이 유닛 이름 대신 sshd syslog 식별자로 거르는 이유도 여기 있습니다. Debian 계열은 유닛을 ssh로, Red Hat 계열은 sshd로 부르므로 식별자로 맞추면 양쪽에서 그대로 동작합니다.
agent에 든 키 하나하나가 시도를 소모하는 이유
예산을 정하는 곳은 sshd_config입니다. 매뉴얼은 MaxAuthTries를 연결당 허용되는 최대 인증 시도 횟수로 설명하고, 실패 횟수가 이 값의 절반에 도달하면 이후 실패가 로그에 남는다고 적으며, 기본값을 6으로 제시합니다.
사람들이 놀라는 지점은 "무엇이 세어지는가"입니다. 공개키 인증은 협상입니다. 클라이언트가 공개키를 내밀면 서버가 authorized_keys와 대조해 예·아니오를 답하고, 그다음에야 클라이언트가 개인키 소유를 증명합니다. 서버가 거절한 제안 하나하나가 실패한 시도입니다. agent에 키가 여섯 개면 시도 여섯 번이고, 일곱 번째 — 성공했을 그 키 — 는 보내지지도 않습니다.
기본값 두 개가 이 목록을 예상보다 길게 만듭니다. IdentitiesOnly의 기본값은 no이고, ssh_config 매뉴얼은 이 상태에서 ssh-agent나 PKCS11Provider, SecurityKeyProvider가 설정된 것보다 많은 신원을 내밀 수 있다고 설명합니다. 그리고 IdentityFile은 누적됩니다. 매뉴얼은 IdentityFile 지시자를 여러 번 쓰면 시도할 신원 목록에 더해진다고 적으면서, 이것이 첫 값이 이기는 다른 설정 지시자들과 다른 동작이라고 따로 짚어 둡니다.
여분의 신원이 흘러드는 경로
- 키가 쌓인 agent. 셸 프로필의
AddKeysToAgent,~/.ssh안의 키를 전부 올리는 데스크톱 키링, 혹은 여러 직장의 키가 모인 노트북. - 겹쳐 쓴
IdentityFile줄. 키 세 개를 나열한Host *블록은 기본 파일에 더해 그 셋을 모든 연결에 얹습니다. - 기본 파일 목록. 설정이 전혀 없으면
ssh는~/.ssh/id_rsa,~/.ssh/id_ecdsa,~/.ssh/id_ecdsa_sk,~/.ssh/id_ed25519,~/.ssh/id_ed25519_sk,~/.ssh/id_mldsa44_ed25519를 시도합니다 — 정확한 구성은 버전에 따라 달라집니다. - 낮춰 놓은 서버 예산. 하드닝 기준선은
MaxAuthTries 3을 흔히 설정하며, 내 쪽은 아무것도 바뀌지 않았는데 여유가 절반으로 줄어듭니다. - 틀린 사용자 이름. 그 키들을 갖고 있지 않은 계정에서는 모든 키가 실패하므로, 아무리 적게 내밀어도 상한에 닿습니다. 서버 로그의
invalid user접두사가 이 경우를 지목합니다.
키 개수를 세고, 실제로 나가는 제안을 보기
먼저 agent가 무엇을 들고 있는지 물어봅니다:
ssh-add -l256 SHA256:9Xk2... alice@laptop (ED25519)
3072 SHA256:7Qp1... alice@old-job (RSA)
256 SHA256:2Fm8... alice@personal (ED25519)그다음 실제 연결이 무엇을 보내는지 지켜봅니다. 질문에 곧장 답을 주는 명령이 이것입니다:
ssh -v deploy@bastion 2>&1 | grep -E "Offering|Will attempt|Authentications that can continue"debug1: Will attempt key: alice@laptop ED25519 SHA256:9Xk2... agent
debug1: Offering public key: alice@laptop ED25519 SHA256:9Xk2... agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: alice@old-job RSA SHA256:7Qp1... agent
debug1: Offering public key: alice@personal ED25519 SHA256:2Fm8... agentOffering 줄을 세어 보십시오. 제안 세 번이나 여섯 번에서 멈추고 연결이 끊긴다면 답이 나온 것이고, 내 키에는 아무 문제가 없습니다. 접속하지 않고 ssh가 훑을 목록 전체를 보려면 확정된 설정을 출력하게 하면 됩니다:
ssh -G deploy@bastion | grep -E "^identityfile|^identitiesonly|^user "ssh -G는 그 대상에 대한 모든 Host·Match 블록을 해석하므로, 내가 썼다고 생각하는 것이 아니라 실제로 쓰일 값을 보여줍니다.
서버가 세는 상한 확인하기
그 호스트를 관리한다면 파일이 아니라 데몬이 실제로 확정한 값을 읽으십시오:
sudo sshd -T | grep -iE "maxauthtries|logingracetime|pubkeyauthentication"maxauthtries 3
logingracetime 120
pubkeyauthentication yessshd -T는 include와 드롭인 파일까지 펼쳐 줍니다. 요즘 배포판 대부분이 /etc/ssh/sshd_config.d/ 조각을 쓰고 하드닝 롤이 거기에 값을 적어 넣기 때문에 이 차이가 실제로 중요합니다.
클라이언트에서, 키 하나와 Host 블록 하나로 고치기
즉시 푸는 방법은 플래그 한 쌍입니다 — agent의 여분 제안을 무시하고 신원 하나만 보내라는 뜻입니다:
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519_bastion deploy@bastion전역이 아니라 호스트별로 굳혀 두십시오. agent는 다른 곳에서는 실제로 쓸모가 있기 때문입니다:
Host bastion
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_ed25519_bastion
IdentitiesOnly yes구체적인 Host 블록은 Host * 블록보다 위에 두십시오. ssh_config 매뉴얼의 순서 규칙은 분명합니다. 별도 언급이 없는 한 각 설정 지시자는 처음 지정된 값이 쓰입니다. 파일 맨 위의 Host * 블록은 그 아래에 쓴 블록을 이깁니다.
나머지는 작은 조치 둘이 덮습니다. ssh-add -d ~/.ssh/id_rsa_old는 낡은 키 하나를 빼고, ssh-add -D는 agent를 통째로 비운 뒤 필요한 것만 다시 올리게 합니다. 그리고 Host 블록의 IdentityAgent none은 다른 곳의 SSH_AUTH_SOCK을 건드리지 않은 채 그 대상에서만 agent를 무시합니다. 서버에서 MaxAuthTries를 올리는 것도 가능하지만 맞는 답인 경우는 드뭅니다. 나만 볼 수 있는 목록을 우회하려고, 무차별 대입 시도가 쓰는 바로 그 창을 함께 넓히는 셈이기 때문입니다.
실제 사례: bastion, 키 아홉 개, 그리고 MaxAuthTries 3
한 엔지니어가 백업으로 노트북을 복구했고 키링이 agent에 키 아홉 개를 올립니다. 다른 건 다 되는데 운영 bastion만 즉시 끊깁니다. ssh-add -l은 아홉 항목을 보여주고, ssh -v는 Offering public key 줄이 정확히 세 번 나온 뒤 끊기는 것을 보여줍니다. bastion의 sshd -T는 maxauthtries 3을 보고하는데, 이는 인터넷에 노출된 호스트에만 적용되는 하드닝 롤이 설정한 값입니다 — 다른 서버가 멀쩡한 이유가 이것입니다. 엔지니어는 IdentitiesOnly yes와 올바른 IdentityFile을 담은 네 줄짜리 Host bastion 블록을 추가합니다. 다음 ssh -v는 제안 한 번과 로그인 성공을 보여주고, 서버는 아무것도 바뀌지 않았습니다.
고쳐졌는지 확인하고, 목록을 짧게 유지하기
-v로 다시 실행해 세어 봅니다:
ssh -v deploy@bastion 2>&1 | grep -cE "Offering public key"
ssh -o BatchMode=yes deploy@bastion true && echo "auth ok"제안 한 번과 auth ok — 신원이 고정된 상태의 모습입니다. 의지하는 Host 블록마다 IdentitiesOnly yes를 붙이고, 키 하나를 여기저기 돌려쓰는 대신 대상마다 키를 따로 두고, 가끔 ssh-add -l로 agent가 다시 차오르는지 살피면 그 상태가 유지됩니다. 이 호스트들을 스크립트로 다룬다면 BatchMode=yes가 멈춰 선 프롬프트를 즉시 실패로 바꿔 주므로, 신원이 깨졌을 때 멈춘 작업이 아니라 실패한 작업으로 드러납니다.
Permission denied (publickey)와 다른 점
Permission denied (publickey)는 서버가 끝까지 들어줬다는 뜻입니다. 내가 내밀 수 있는 키를 전부 보았고 authorized_keys에 맞는 것이 없었으며, 시도가 아니라 신원이 동난 것입니다. 그쪽 해결은 서버에 있습니다 — 올바른 공개키가 올바른 파일에, 올바른 소유권과 권한으로 들어가야 합니다. Too many authentication failures는 서버가 목록 중간에서 끊었다는 뜻이라, 정작 중요한 키는 보내지지도 않았을 수 있습니다. ssh -v의 Offering 개수가 둘을 명령 한 줄로 갈라 줍니다.
Host key verification failed는 아예 다른 단계입니다. 그 검사는 인증보다 먼저 돌고 서버가 known_hosts를 근거로 자기 신원을 나에게 증명하는 일이라, 그 오류가 날 때는 내 키가 아직 하나도 나가지 않은 상태입니다.
다음에 아무것도 묻지 않고 연결이 죽으면 이 순서를 거꾸로 따라가 보세요. 서버 로그 줄에서 invalid user 접두사를 확인하고, ssh -v의 Offering 줄을 세고, 그 수를 sshd -T 값과 견줘 본 다음, 신원을 고정하면 됩니다.
관련 질문
비밀번호를 입력하지도 않았는데 왜 이 오류가 나나요?
공개키 제안 자체가 인증 시도이기 때문입니다. 클라이언트는 갖고 있는 공개키를 하나씩 내밀고, 서버는 authorized_keys와 대조해 모르는 것을 거절하며, 그 거절 하나하나가 MaxAuthTries에서 차감됩니다. 기본값이 6이므로 agent에 키가 일곱 개만 있어도 대화형 인증 방식에 닿기 전에 예산이 바닥납니다. 프롬프트 없이 연결이 끊기는 이유가 이것입니다.
서버에서 MaxAuthTries를 올리면 되지 않나요?
올릴 수는 있지만 대개 잘못 잡은 손잡이입니다. 이 값은 연결 하나가 시도할 수 있는 횟수의 상한이라, 올리면 원치 않는 클라이언트를 포함한 모든 클라이언트에게 그 창이 넓어지고, 정작 agent가 내미는 키 목록에는 아무 영향이 없습니다. 클라이언트에서 신원을 고정하면 원인을 고치면서 서버의 방어 태세는 그대로 둘 수 있습니다. 여러 팀이 시도를 소모하는 다단계 AuthenticationMethods 체인을 실제로 써야 하는 내부 호스트라면 올리는 선택도 설명이 됩니다.
IdentitiesOnly=yes를 넣었는데도 엉뚱한 키를 계속 내밉니다.
파일 대신 확정된 설정을 ssh -G host로 확인하십시오. 그 대상에 대한 모든 Host·Match 블록을 해석해 보여줍니다. 거의 모든 사례는 규칙 두 개로 설명됩니다. 지시자는 처음 지정된 값이 이기므로 내 블록 위에 놓인 Host * 블록이 덮어씁니다. 그리고 IdentityFile은 대체가 아니라 목록에 더해지는 문서화된 예외라서, IdentitiesOnly를 켜도 남아 있는 IdentityFile 줄은 여전히 기여합니다.
서버 로그에 maximum authentication attempts exceeded for invalid user deploy라고 찍힙니다.
그 접두사는 해당 호스트에 그 계정이 없다는 뜻이라, 애초에 맞을 수 있는 키가 없었고 상한에 닿는 것이 예정된 결과였습니다. OpenSSH는 사용자 이름이 유효하지 않을 때 메시지에 invalid user를 끼워 넣습니다. 키나 agent를 손대기 전에 사용자 이름부터 고치십시오. ssh는 지정이 없으면 로컬 사용자 이름으로 접속하므로 Host 블록의 User 줄을 함께 보십시오.
제 대화형 ssh는 되는데 Ansible이나 CI 작업만 이 오류가 납니다.
두 실행이 같은 신원 목록을 보는 경우는 거의 없습니다. CI 러너에는 agent가 포워딩돼 들어와 있거나 HOME이 달라 설정 파일 자체가 다를 수 있고, 제 셸에는 방금 추가한 Host 블록이 있습니다. 그 사용자로 ssh -G를 돌려 해석된 설정을 찍어 재현한 뒤, 자동화 전용 ssh config나 ansible_ssh_common_args에 IdentitiesOnly=yes와 명시적 IdentityFile을 넣어 agent가 무엇을 들고 있든 무관하게 만드십시오.
참고 자료
- OpenBSD manual pages — sshd_config(5) (MaxAuthTries: maximum authentication attempts permitted per connection, additional failures logged past half the value, default 6; LoginGraceTime; PubkeyAuthentication)
- OpenBSD manual pages — ssh_config(5) (IdentitiesOnly and its no default, the default IdentityFile list, multiple IdentityFile directives adding to the list, IdentityAgent, and the first-value-wins rule for directives)
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
같은 카테고리 다른 글
systemd: Start request repeated too quickly
systemd는 StartLimitIntervalSec(기본 10초) 안에 StartLimitBurst(기본 5회)보다 많이 시작된 유닛의 시작을 거부하며, Restart=도 이 제한에 포함됩니다. 기본 RestartSec 100ms로는 크래시하는 서비스가 1초도 안 되어 다섯 번을 소진합니다. 저널에서 진짜 크래시를 찾아 고치고, reset-failed를 실행한 뒤, RestartSec로 재시작에 여유를 주세요.
Active Directory: 복제가 error 1722(The RPC server is unavailable)로 실패
RPC는 하위 계층이 연결에 실패했을 때 1722(0x6ba, RPC_S_SERVER_UNAVAILABLE)를 보고합니다. 따라서 진짜 원인은 RPC 자신이 아니라 DNS, 차단된 포트, 두 도메인 컨트롤러 중 한쪽의 호스트 설정입니다. repadmin이 어느 파트너가 실패하는지 알려주고, dcdiag /test:dns가 이름 해석을 배제하며, Test-NetConnection과 동적 포트 범위가 방화벽 문제를 가릅니다. 가장 흔한 실수는 TCP 135만 허용하고 49152–65535는 막아 둔 규칙입니다.
Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license
120일 RD Licensing 유예 기간이 끝났는데 세션 호스트가 쓸 수 있는 라이선스 서버가 없어 세션을 거부하는 상황입니다. GetGracePeriodDays 의 DaysLeft 가 0 이고 SpecifiedLSList 가 비어 있으면 몇 초 만에 확인됩니다. 해결은 활성화된 라이선스 서버와, 호스트 버전을 감당할 만큼 새 CAL 입니다. 2019 CAL 은 2022 세션 호스트를 서비스하지 못합니다. 여기에 배포 또는 Licensing 정책 설정과 두 서버 사이의 RPC 포트 개방이 따라옵니다.
Windows 11: NAS 공유 폴더를 열 때 "조직의 보안 정책이 인증되지 않은 게스트 액세스를 차단" (0x80070035)
Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro, Windows Server 2019 이후의 SMB 클라이언트는 기본적으로 게스트 로그온을 거부하고, Windows 11 24H2 Enterprise/Pro/Education 은 게스트 세션이 할 수 없는 SMB 서명까지 요구합니다. 그래서 게스트 접근만 제공하는 NAS 공유는 'block unauthenticated guest access' 대화상자, Error code 0x80070035, 또는 System error 3227320323 으로 실패하고, SmbClient/Security 로그에 Event ID 31017 'Rejected an insecure guest logon' 이 남습니다. Microsoft 가 권하는 해결은 NAS 에 실제 계정을 만들고 펌웨어에서 서명을 지원하게 하는 것입니다. Set-SmbClientConfiguration -EnableInsecureGuestLogons $true(24H2 는 -RequireSecuritySignature $false 까지)는 비상구이며, 그 클라이언트의 서명과 암호화를 포기하는 대가가 있습니다.
Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — 누가 쥐고 있고 어떻게 기다리는지
다른 패키지 관리자, 대개 부팅 때 persistent systemd 타이머로 발화한 Ubuntu 의 unattended-upgrades 가 여러분의 apt-get 이 도는 동안 dpkg 프런트엔드 락을 쥐고 있는 것입니다. Ubuntu 는 apt 바이너리에만 binary::apt::DPkg::Lock::Timeout "120" 을 실어 두어서 apt-get 은 즉시 포기하고 apt 는 기다립니다. 메시지의 PID 를 읽고, 실행이 끝나게 두거나 apt-get 에 -o DPkg::Lock::Timeout=<초> 를 주고, dpkg --configure -a 는 실제로 중단된 실행 뒤에만 쓰고, 락 파일은 절대 지우지 마세요. 보유자가 종료하면 커널이 푸는 fcntl 락입니다.
macOS: xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools)
macOS의 /usr/bin에 있는 git·make·clang 같은 개발 명령은 활성 developer 디렉터리로 넘겨주는 shim이고, xcrun은 xcode-select가 가리키는 디렉터리에 도구가 없다고 보고하는 것입니다 — 대개 macOS 메이저 업그레이드가 /Library/Developer/CommandLineTools를 비워 두었거나, Xcode가 옮겨지거나 삭제된 경우입니다. xcode-select -p와 패키지 영수증을 확인한 뒤 xcode-select --install로 Command Line Tools를 다시 설치하거나, 실제로 있는 Xcode를 xcode-select로 가리키면 됩니다.