ssh 접속이 안 될 때 — 호스트 키 협상 실패와 키 파일 권한, OS별로 정리

in #kr8 days ago (edited)

41FABE56-C659-4F57-B262-9BE5247DD741.png

ssh 접속이 안 될 때 — 호스트 키 협상 실패와 키 파일 권한, OS별로 정리

안녕하세요 가야태자 @talkit 입니다.

이번에 wTerm2를 만들면서 ssh 관련 이슈 두 가지가 있었는데, 해당 이슈는 실제 ssh를 사용할 때도 발생할 수 있는 문제여서 한번 글로 써야겠다는 생각을 했습니다.

ssh를 사용할 때 호스트 키 미스 매치 문제와, ssh 키 파일의 OS별 해결책을 한번 글로 적어 봅니다.

해당 내용은 아래와 같습니다.


왜 이 두 가지인가

제가 만들고 있는 wTerm2는 윈도우용 터미널입니다. SSH와 AWS SSM 접속을 프로필로 등록해 두고 클릭 한 번으로 붙는 게 목적입니다. 그래서 개발하는 내내 접속이 안 되는 상황을 계속 만났습니다.

그런데 파고 들어가 보니 둘 다 제 프로그램 버그가 아니었습니다. 콘솔에서 ssh 를 직접 쳐도 똑같이 실패합니다. 즉 터미널 프로그램을 만들든 안 만들든, ssh를 쓰는 사람이면 언젠가 만나는 문제입니다.

  • 문제 1no matching host key type found 로 즉시 끊김
  • 문제 2 — 키 파일을 지정했는데 Permission denied (publickey)

두 문제 다 오류 메시지가 이미 답을 절반 담고 있는데, 그 문장이 낯설어서 엉뚱한 데를 파게 됩니다. 저도 그랬습니다.


문제 1 — no matching host key type found

증상

구형 서버에 붙으려 하면 인증 창이 뜨기도 전에 끊깁니다.

Unable to negotiate with x.x.x.x port 22: no matching host key type found.
Their offer: ssh-rsa,ssh-dss

제가 처음에 한 질문이 이거였습니다.

키 문제인데 키 파일은 안 쓰고 아이디·비밀번호만 쓰는데?

이게 정확히 함정입니다. "key" 라는 단어를 보고 내 개인키를 의심하게 되는데, 여기서 말하는 key는 서버의 신분증입니다.

오류 문구를 끝까지 읽으면 절반이 풀립니다

이 짧은 메시지에 단서가 셋 있습니다.

단어
Unable to negotiate협상 단계 — 아직 로그인 시도조차 안 했습니다
host key type내 키가 아니라 서버의 호스트 키
Their offer:서버가 "나는 이것들을 쓸 수 있다" 고 내민 목록

즉 이 오류는 이렇게 읽으면 됩니다.

서버가 A·B를 쓸 수 있다는데, 내 ssh는 A도 B도 안 받는다. 그래서 대화를 시작할 수 없다.

아이디·비밀번호는 아직 등장하지도 않았습니다. 그래서 비밀번호를 아무리 확인해도 소용이 없습니다.

왜 갑자기 안 되게 됐나

ssh-rsa 라는 이름이 오해를 부릅니다. RSA 키를 뜻하는 게 아니라, SHA-1 해시로 서명하는 방식을 뜻합니다.

SHA-1은 2017년에 실제 충돌이 만들어지면서 깨졌습니다. 그래서 OpenSSH 8.8(2021년 9월)부터 ssh-rsa 를 기본 비활성화했습니다. ssh-dss(DSA)는 그보다 훨씬 전에 꺼졌고, OpenSSH 10부터는 아예 빌드에서 빠졌습니다.

정리하면 이렇습니다.

서버오래돼서 ssh-rsa·ssh-dss 만 제공
내 PCOpenSSH가 최신이라 그 둘을 거부
결과협상 실패

서버가 안 바뀌었는데 내 PC가 업데이트되면서 갑자기 안 되는 것이라, "어제까지 됐는데?" 가 됩니다. 윈도우 업데이트로 OpenSSH가 올라간 경우가 특히 그렇습니다.

진단 — -Q-G 는 다릅니다

여기서 사람들이 자주 헷갈리는 게 있습니다. 저도 헷갈렸습니다.

ssh -Q 는 "내 ssh가 지원할 수 있는 것" 을 보여줍니다.

ssh -Q HostKeyAlgorithms

ssh -G 는 "지금 실제로 켜져 있는 것" 을 보여줍니다.

ssh -G 서버주소 | grep -i hostkeyalgorithms

제 맥(OpenSSH 10.2)에서 재봤습니다.

ssh-rsa
ssh -Q (지원 가능)있음
ssh -G (기본 활성)없음
ssh-dss-Q 에도 없음 (OpenSSH 10에서 제거)

-Q 에 보인다고 되는 게 아닙니다. 지원은 하지만 꺼놨다는 뜻입니다. 그래서 옵션으로 다시 켜면 됩니다.

반대로 -Q 에도 없으면 옵션으로도 못 켭니다. ssh-dss 가 그 경우라, OpenSSH 10을 쓰신다면 +ssh-dss 를 넣어도 소용이 없습니다. 그때는 서버를 고치거나 구형 클라이언트를 써야 합니다.

내 버전부터 확인하세요.

ssh -V

해결 1 — 한 번만 붙어보기

먼저 명령줄에서 확인합니다.

ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa user@호스트

+ 를 반드시 붙이세요. 이게 이 글에서 제일 중요한 한 글자입니다.

쓴 것결과
HostKeyAlgorithms=ssh-rsa기본 목록을 교체 — 이제 최신 서버에 못 붙습니다
HostKeyAlgorithms=+ssh-rsa기본 목록에 추가 — 최신 서버도 그대로 됩니다
HostKeyAlgorithms=-ssh-rsa목록에서 제거
HostKeyAlgorithms=^ssh-rsa목록 맨 앞으로 (우선순위만 올림)

인터넷에서 복사한 명령에 + 가 빠진 경우가 정말 많습니다. 그러면 그 설정을 넣은 뒤로 다른 서버들이 안 붙기 시작하는데, 원인을 연결 짓기가 어렵습니다.

PubkeyAcceptedAlgorithms 는 OpenSSH 8.5부터의 이름입니다. 그보다 낮은 버전이면 PubkeyAcceptedKeyTypes 를 쓰세요. ssh -V 로 확인하시면 됩니다.

해결 2 — 설정 파일에 넣기 (권장)

매번 옵션을 치는 건 현실적이지 않습니다. 호스트별로 설정 파일에 적어 두세요.

파일 위치는 OS마다 이렇습니다.

OS경로
리눅스 · macOS~/.ssh/config
WindowsC:\Users\사용자명\.ssh\config
Windows (PowerShell 표기)$env:USERPROFILE\.ssh\config
WSLWSL 안의 ~/.ssh/config (윈도우 것과 별개입니다)

내용은 이렇게 씁니다.

Host oldserver
    HostName x.x.x.x
    User myaccount
    Port 22
    HostKeyAlgorithms +ssh-rsa
    PubkeyAcceptedAlgorithms +ssh-rsa

이렇게 해두면 그냥 이렇게 붙습니다.

ssh oldserver

Host 블록으로 나누는 게 핵심입니다. 전역으로 걸지 말고 그 서버에만 거세요. 그러면 나머지 서버는 계속 최신 규칙으로 안전하게 붙습니다.

Windows에서 .ssh 폴더가 없으면 이렇게 만듭니다.

New-Item -ItemType Directory -Force "$env:USERPROFILE\.ssh"
notepad "$env:USERPROFILE\.ssh\config"

메모장으로 저장할 때 파일 이름을 "config" 처럼 따옴표로 감싸세요. 안 그러면 config.txt 로 저장돼서 ssh가 못 읽습니다. 흔한 함정입니다.

호스트 키 말고 다른 데서 막히기도 합니다

구형 서버는 보통 여러 군데가 같이 낡았습니다. 오류 문구에 따라 이렇게 나뉩니다.

오류 문구붙일 옵션
no matching host key typeHostKeyAlgorithms=+...
no matching key exchange methodKexAlgorithms=+diffie-hellman-group14-sha1
no matching cipherCiphers=+aes128-cbc
no matching MACMACs=+hmac-sha1
키 인증만 거부PubkeyAcceptedAlgorithms=+ssh-rsa

각각 무엇을 쓸 수 있는지는 이렇게 봅니다.

ssh -Q kex        # 키 교환
ssh -Q cipher     # 암호화
ssh -Q mac        # 무결성
ssh -Q key        # 키 종류

제 프로그램에서는 이 목록을 하드코딩하지 않고 ssh -Q 로 물어봅니다. 하드코딩하면 OpenSSH 버전이 올라갈 때 낡고, 무엇보다 그 PC가 지원하지 않는 값을 골라도 안 붙습니다.

그리고 화면에 뜬 Their offer: ... 를 읽어서, 선택 창에서 서버가 제안한 것에 표시를 붙입니다. 방금 그 오류를 본 사용자에게 같은 정보를 다시 보여주면 무엇을 고를지 바로 압니다.

근본 해결은 서버 쪽입니다

옵션은 우회입니다. 서버를 손댈 수 있으면 이게 낫습니다.

방법 1 — ed25519 호스트 키 추가 (가장 간단합니다)

sudo ssh-keygen -A
sudo systemctl restart sshd

ssh-keygen -A없는 종류의 호스트 키만 새로 만듭니다. 기존 키를 덮어쓰지 않으므로, 이미 접속하던 사람들의 known_hosts 가 깨지지 않습니다.

/etc/ssh/sshd_config 에 해당 줄이 주석 처리돼 있으면 함께 풀어야 합니다.

HostKey /etc/ssh/ssh_host_ed25519_key

방법 2 — OpenSSH 업그레이드. 근본적이지만 운영 중인 서버라면 신중해야 합니다.

바꾸기 전에 접속 세션 하나를 열어두세요. sshd 설정을 잘못 건드리면 새 접속이 안 되는데, 이미 열려 있는 세션은 유지됩니다. 그 세션으로 되돌릴 수 있습니다.

무엇을 포기하는지 알고 쓰세요

이 옵션은 호스트 키 서명에 SHA-1을 허용하는 것입니다.

호스트 키는 "지금 붙은 상대가 정말 그 서버인가" 를 증명하는 수단입니다. 그 서명 방식이 약해지면 중간자 공격(MITM) 방어력이 떨어집니다. root 로 붙는 서버라면 더 그렇습니다.

그래서 저는 프로그램 안내와 문서에 이걸 함께 적었습니다.

접속만 되게 해놓고 보안 저하를 말하지 않으면, 사용자가 판단할 기회를 잃습니다.

현실적으로는 이렇게 나눕니다.

상황판단
사내망의 곧 교체될 장비옵션으로 넘어가도 됩니다
인터넷에 열린 운영 서버서버를 고치는 게 맞습니다
잘 모르겠다일단 옵션으로 붙되, 서버 교체 일정을 잡으세요

문제 2 — 키 파일을 지정했는데 Permission denied

증상

제가 실제로 받은 오류입니다.

$ ssh -p 22 -i C:\dev\oraclevm2.ppk [email protected]
Bad permissions. Try removing permissions for user: BUILTIN\Users on file C:/dev/oraclevm2.ppk
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
Load key "C:\dev\oraclevm2.ppk": bad permissions
[email protected]: Permission denied (publickey).

여기서 문제가 두 층으로 쌓여 있었습니다.

  1. .ppkssh.exe 가 읽지 못합니다 — PuTTY 전용 형식입니다
  2. 파일 권한이 너무 열려 있습니다 — 윈도우 ACL 상속

그리고 순서가 중요합니다. 권한을 먼저 고쳐도 형식 때문에 실패합니다. 저는 실제로 권한을 고치고 "왜 아직도 안 되지?" 를 했습니다.

층 1 — .ppk 는 OpenSSH가 못 읽습니다

PuTTY는 자체 형식(.ppk)을 씁니다. ssh 는 OpenSSH 형식만 읽습니다. 오라클 클라우드나 예전 사내 문서에서 받은 키가 .ppk 인 경우가 흔합니다.

변환 방법 — 윈도우 (PuTTYgen)

  1. PuTTYgen 실행
  2. Load.ppk 파일 선택 (파일 형식을 "All Files" 로 바꿔야 보입니다)
  3. 메뉴 Conversions → Export OpenSSH key
  4. .pem 이나 확장자 없이 저장

변환 방법 — 맥 · 리눅스 (명령줄)

# 우분투/데비안: sudo apt install putty-tools
# 맥:          brew install putty
puttygen key.ppk -O private-openssh -o key.pem
chmod 600 key.pem

바뀌었는지 확인하는 방법은 간단합니다. 파일을 열어서 첫 줄을 보세요.

첫 줄형식
PuTTY-User-Key-File-3:PuTTY — 변환해야 합니다
-----BEGIN OPENSSH PRIVATE KEY-----OpenSSH (최신)
-----BEGIN RSA PRIVATE KEY-----OpenSSH가 읽는 PEM

층 2 — 권한 규칙이 OS마다 전혀 다릅니다

제가 클로드에게 물어본 게 이거였습니다.

601 정도면 되나? 윈도우는 어떤 권한이어야 하나?

규칙이 서로 완전히 다릅니다.

리눅스 · macOS

OpenSSH의 검사는 딱 한 줄입니다.

(권한 & 0o077) != 0  이면 거부

그룹·기타 비트가 하나라도 있으면 안 됩니다. 읽기든 쓰기든 실행이든 상관없습니다.

권한결과이유
600통과소유자만 읽기·쓰기
400통과소유자만 읽기
601거부기타 실행 비트 하나 때문에
640거부그룹 읽기
644거부그룹·기타 읽기
660거부그룹 읽기·쓰기

601 이 거부라는 게 포인트입니다. "실행 권한 하나쯤이야" 가 아닙니다.

chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh

폴더도 봅니다. ~/.ssh755 면 상황에 따라 걸립니다. 700 으로 두세요.

한 가지 주의드립니다. 제가 이 글을 쓰면서 맥(OpenSSH 10.2)에서 재봤는데, ssh -i644 인 키를 그냥 읽었고, ssh-add 는 거부했습니다. 반면 윈도우에서는 ssh -i 가 확실히 거부했습니다(위 오류가 그것입니다).

버전과 플랫폼에 따라 어디서 걸리는지가 다릅니다. "내 맥에서는 되는데" 가 서버나 윈도우에서 막히는 전형적인 이유입니다. 그냥 항상 600 으로 두시는 게 답입니다.

Windows

윈도우에는 숫자 권한이라는 개념이 자체가 없습니다. chmod 600 은 의미가 없고, WSL에서 친 chmod 는 윈도우 쪽 파일에 제대로 먹지 않습니다.

윈도우 OpenSSH는 DACL(접근 제어 목록) 을 봅니다. 규칙은 이렇습니다.

소유자 · SYSTEM · Administrators 외의 주체가 목록에 있으면 거부

흔히 걸리는 주체가 이 셋입니다.

  • BUILTIN\Users
  • Everyone
  • Authenticated Users

지금 상태를 보려면:

icacls "C:\path\to\key.pem"

고치는 명령은 두 줄입니다. 순서가 중요합니다.

icacls "C:\path\to\key.pem" /inheritance:r
icacls "C:\path\to\key.pem" /grant:r "$env:USERNAME:(R)"
명령하는 일
/inheritance:r상위 폴더에서 물려받은 항목을 끊어냅니다
/grant:r "사용자:(R)"나에게만 읽기 권한을 줍니다 (:r 은 기존 항목 대체)

상속을 먼저 끊어야 합니다. 순서가 바뀌면 상속 항목이 그대로 남아서 다시 거부됩니다. 제 프로그램도 이 순서로 실행합니다.

진짜 원인은 파일을 어디에 뒀느냐였습니다

제 경우 키가 C:\dev\ 에 있었습니다. C:\ 드라이브 루트의 느슨한 ACL을 그대로 물려받는 자리입니다. 거기 두면 거의 항상 걸립니다.

키를 %USERPROFILE%\.ssh\ 로 옮기자 곧바로 접속됐습니다. 그 폴더는 처음부터 소유자 전용으로 설정돼 있어서 상속 문제가 안 생깁니다.

New-Item -ItemType Directory -Force "$env:USERPROFILE\.ssh"
Move-Item "C:\dev\mykey.pem" "$env:USERPROFILE\.ssh\"

우회 수단보다 재발하지 않는 배치가 낫습니다. 권한 조이기 버튼을 누르는 것보다, 애초에 걸리지 않는 폴더에 두는 게 답이었습니다.

WSL을 쓰신다면 하나 더

WSL에서 /mnt/c/... 아래의 윈도우 파일은 기본적으로 777 로 보입니다. 그래서 ssh가 무조건 거부합니다. chmod 를 쳐도 반영이 안 됩니다.

# 안 됩니다
chmod 600 /mnt/c/Users/me/key.pem

해결은 키를 WSL 안쪽으로 복사하는 것입니다.

mkdir -p ~/.ssh && chmod 700 ~/.ssh
cp /mnt/c/Users/me/key.pem ~/.ssh/
chmod 600 ~/.ssh/key.pem

/etc/wsl.conf 에 메타데이터 옵션을 켜는 방법도 있지만, 복사가 훨씬 간단하고 사고가 적습니다.

macOS 하나 더 — 키체인

맥에서는 암호 걸린 키를 키체인에 맡길 수 있습니다.

ssh-add --apple-use-keychain ~/.ssh/id_ed25519

그리고 ~/.ssh/config 에 이렇게 넣어두면 재부팅 후에도 자동으로 불러옵니다.

Host *
    UseKeychain yes
    AddKeysToAgent yes

예전 문서에는 -K 로 나옵니다. 지금은 --apple-use-keychain 이 정식 이름입니다.

오류를 읽는 순서

Permission denied (publickey) 는 원인이 여럿입니다. 이 순서로 좁히면 빠릅니다.

ssh -vvv -i key.pem user@호스트

로그에서 이렇게 나뉩니다.

로그에 보이는 것원인
bad permissions / UNPROTECTED PRIVATE KEY권한
Load key ...: invalid format형식 (.ppk 이거나 깨짐)
Offering public key 뒤 서버가 거절서버의 authorized_keys 문제
no mutual signature algorithm알고리즘 — 문제 1 쪽
키를 아예 시도조차 안 함IdentitiesOnly=yes 를 붙여 보세요

마지막 줄이 은근히 많습니다. ssh-agent에 키가 여러 개 있으면 엉뚱한 것부터 시도하다가 서버가 횟수 초과로 끊습니다.

ssh -o IdentitiesOnly=yes -i key.pem user@호스트

정리 — 상황별 한 줄

증상해야 할 것
no matching host key typessh -o HostKeyAlgorithms=+ssh-rsa 로 확인 → ~/.ssh/config 에 호스트별로
-Q 에는 있는데 안 됨지원은 하지만 꺼져 있는 것. 옵션으로 켜면 됩니다
-Q 에도 없음옵션으로 못 켭니다. 서버를 고치세요
UNPROTECTED PRIVATE KEY (맥·리눅스)chmod 600 키파일, chmod 700 ~/.ssh
BUILTIN\Users (윈도우)icacls /inheritance:r/grant:r "사용자:(R)"
WSL에서 /mnt/c 의 키WSL 안으로 복사한 뒤 chmod 600
invalid format.ppk 입니다. PuTTYgen으로 변환하세요
계속 재발함키를 ~/.ssh 또는 %USERPROFILE%\.ssh 로 옮기세요

제가 배운 것

오류 문구를 끝까지 읽으면 절반이 풀립니다. host key typeTheir offer 두 단어가 "인증 전 단계" 와 "서버가 내민 목록" 을 이미 알려주고 있었습니다. 저는 key 만 보고 제 개인키를 의심했습니다.

우회 옵션을 줄 때는 무엇을 포기하는지 함께 적어야 합니다. 접속만 되게 해놓고 보안 저하를 말하지 않으면 사용자가 판단할 기회를 잃습니다.

목록은 시스템에 물어보는 편이 낫습니다. ssh -Q 한 줄이 하드코딩된 표보다 정확하고 낡지 않습니다. 그리고 -Q-G 는 다른 질문에 답한다는 것도요.

우회 수단보다 재발하지 않는 배치를 안내하는 게 낫습니다. 권한 조이기 버튼을 만드는 것보다 "~/.ssh 에 두세요" 가 나은 답이었습니다.

그리고 프로그램을 만드는 입장에서 하나 더 배웠습니다. 고치는 수단은 문제가 보이는 자리에 있어야 합니다. 처음에 권한 조이기 버튼을 접속 프로필 편집 창에 뒀는데, 오류는 터미널 화면에 뜹니다. 제가 "편집 창에 버튼이 있습니다" 라고 안내했지만, 애초에 오류 옆에 있어야 했습니다. 지금은 접속 실패한 화면 안에 버튼이 있습니다.

프로세스가 코드 255 로 종료되었습니다   [다시 실행] [닫기]
개인키 권한이 너무 열려 있습니다        [키 권한 조이기]

읽어주셔서 감사합니다.


이 글의 명령과 동작은 2026년 8월 기준 OpenSSH 10.2에서 직접 확인한 것입니다.
오류 메시지는 실제로 받은 것이고, 서버 주소는 제 것입니다.

Sort:  

Upvoted! Thank you for supporting witness @jswit.

최근 오늘의 스팀마을 서비스구축 코딩하면서 Ai와 몇일간 싸워봐서 그런지 위 메시지가 이제 낯설지가 않네요.

그나저나 코딩에 빠지면서 느끼는것은
프로그래머 엔지니어들이 무척이나 존경스럽습니다.

그 수많은 에러를 수정하고 회피하고.
정상화는 그 노력 등등등..

AI와 싸우시면 안되고 친해지셔야죠 ^^
살살 달래 가면서 작업 하십시오 ^^