깃허브 SSH 키 등록 방법과 Permission denied (publickey) 해결: 윈도우·맥·리눅스

이 글의 핵심

깃허브에 SSH 키를 등록하는 과정은 키 생성 → ssh-agent에 등록 → 공개 키를 깃허브에 붙여 넣기 → ssh -T로 확인 → 원격 주소를 SSH로 변경, 다섯 단계입니다. 이 글은 각 단계를 윈도우·macOS·리눅스별로 설명하고, 단계를 건너뛰었을 때 나오는 Permission denied (publickey), Host key verification failed, REMOTE HOST IDENTIFICATION HAS CHANGED 같은 오류 메시지를 원인별로 해결합니다.

git push가 비밀번호를 거부하는 이유

깃허브 저장소를 https://github.com/... 주소로 클론한 뒤 git push를 하면 사용자 이름과 비밀번호를 묻습니다. 여기에 깃허브 로그인 비밀번호를 넣으면 다음과 같은 메시지가 나오며 실패합니다.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/USER/REPO.git/'

깃허브는 2021년 8월 13일부터 Git 작업에 계정 비밀번호를 받지 않습니다. 비밀번호는 유출되면 계정 전체가 넘어가고, 2단계 인증과도 잘 맞지 않기 때문입니다. 대신 쓸 수 있는 방법은 두 가지입니다.

방식원격 주소 형태인증 수단맞는 상황
HTTPS + 토큰https://github.com/USER/REPO.git개인 액세스 토큰(PAT) 또는 Git Credential Manager회사 방화벽이 22번 포트를 막는 환경, 토큰 권한을 저장소 단위로 좁히고 싶을 때
SSH[email protected]:USER/REPO.git내 PC에 있는 SSH 개인 키개인 개발 PC, 여러 저장소를 자주 push하는 경우

HTTPS 방식에서는 비밀번호 자리에 토큰을 넣습니다. 윈도우용 Git for Windows는 Git Credential Manager를 함께 설치하므로, 처음 push할 때 브라우저 로그인 창이 뜨고 그 뒤로는 자격 증명을 기억합니다. 이것만으로 충분하다면 SSH를 꼭 쓸 필요는 없습니다.

SSH를 쓰는 이유는 토큰처럼 만료일을 챙기거나 어딘가에 저장해 둘 필요가 없고, 키가 PC 밖으로 나가지 않는다는 점입니다. 서버에는 공개 키만 올라가고, 개인 키는 내 디스크에만 있습니다. 대신 처음 설정 단계가 HTTPS보다 많고, 그 단계 중 하나라도 빠지면 이 글 후반부에 나오는 오류들을 만나게 됩니다. SSH 프로토콜 자체가 공개 키로 어떻게 인증하는지 궁금하다면 SSH 보안 원격 접속 원리를 먼저 읽어 두면 아래 오류 메시지가 훨씬 잘 읽힙니다.

기준 환경: Windows 11(내장 OpenSSH 클라이언트, Git for Windows 2.4x), macOS Sonoma 이후, Ubuntu 24.04(OpenSSH 9.6). Windows 10도 1809 이후 버전이면 OpenSSH 클라이언트가 기본 포함되어 있어 같은 명령이 동작합니다.

이미 키가 있는지 먼저 확인하기

예전에 다른 서비스 때문에 키를 만들어 둔 적이 있다면 새로 만들 필요가 없습니다. 무심코 같은 이름으로 다시 생성하면 기존 키를 덮어써서, 그 키로 접속하던 서버에 못 들어가게 됩니다.

# macOS, 리눅스, Git Bash
ls -al ~/.ssh
# Windows PowerShell
Get-ChildItem $env:USERPROFILE\.ssh

id_ed25519와 id_ed25519.pub, 또는 id_rsa와 id_rsa.pub 같은 쌍이 보이면 이미 키가 있는 것입니다. 확장자 없는 파일이 개인 키, .pub가 공개 키입니다. 폴더가 없거나 비어 있으면 다음 단계로 넘어갑니다.

ssh-keygen -t ed25519로 키 만들기

세 OS 모두 명령은 같습니다. 윈도우에서는 PowerShell이나 Git Bash 어느 쪽에서 실행해도 됩니다.

ssh-keygen -t ed25519 -C "[email protected]"

-t ed25519는 키 알고리즘입니다. Ed25519는 키가 짧고(공개 키가 한 줄에 들어갈 만큼) 서명 속도가 빠르며, 깃허브가 공식 문서에서 가장 먼저 권하는 형식입니다. -C 뒤의 문자열은 공개 키 끝에 붙는 주석일 뿐 인증에는 쓰이지 않습니다. 나중에 깃허브 설정 화면에서 키가 여러 개 보일 때 어느 기기의 키인지 구분하는 용도라서, 이메일 대신 "laptop-2026"처럼 기기 이름을 넣어도 됩니다.

실행하면 저장 위치와 패스프레이즈를 묻습니다.

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

저장 위치는 Enter로 기본값을 받는 것이 좋습니다. SSH 클라이언트는 id_ed25519, id_rsa, id_ecdsa 같은 기본 이름을 자동으로 찾아 시도하기 때문에, 기본 이름을 쓰면 별도 설정 없이 바로 동작합니다. 다른 이름으로 저장하면 뒤에서 설명할 ~/.ssh/config에 경로를 적어 줘야 합니다.

깃허브가 받는 키 형식

깃허브에 등록할 수 있는 키는 Ed25519, ECDSA(nistp256·384·521), RSA, 그리고 하드웨어 보안 키용인 [email protected], [email protected]입니다. DSA 키는 2022년 3월부터 받지 않습니다.

RSA는 조금 주의가 필요합니다. RSA 키 자체는 여전히 등록할 수 있지만, 서명 방식 가운데 SHA-1을 쓰는 ssh-rsa 서명은 깃허브가 2021년 11월 2일 이후 추가된 RSA 키에 대해 거부합니다. 최신 OpenSSH는 기본적으로 SHA-2 기반인 rsa-sha2-256/rsa-sha2-512 서명을 쓰므로 보통은 문제가 없지만, 오래된 SSH 클라이언트나 라이브러리를 쓰는 도구에서는 RSA 키로 인증이 실패할 수 있습니다. 새로 만든다면 이런 고민이 없는 Ed25519를 고르는 편이 간단합니다. 아주 오래된 시스템과도 같은 키를 써야 해서 RSA가 필요하다면 ssh-keygen -t rsa -b 4096처럼 4096비트로 만듭니다.

패스프레이즈를 넣을까 말까

패스프레이즈는 개인 키 파일 자체를 암호화하는 비밀번호입니다. 비워 두면 키 파일을 손에 넣은 사람은 누구든 그대로 내 깃허브 계정으로 push할 수 있습니다. 노트북 도난, 백업 파일 유출, 악성 패키지가 홈 디렉터리를 훑어 가는 경우가 실제 위협입니다. 패스프레이즈를 걸어 두면 파일만으로는 쓸 수 없습니다.

단점은 매번 입력하는 번거로움인데, 이 문제는 다음 단계의 ssh-agent가 해결합니다. 에이전트가 로그인 세션 동안 복호화된 키를 메모리에 들고 있으므로, 패스프레이즈는 부팅 후 한 번(macOS는 키체인에 저장하면 그마저도 생략) 입력하면 됩니다. 개인 PC라면 패스프레이즈를 넣고 에이전트를 쓰는 조합이 보안과 편의의 균형이 가장 좋습니다. 패스프레이즈 없이 만든 키에 나중에 추가하거나 바꾸려면 ssh-keygen -p -f ~/.ssh/id_ed25519를 쓰면 됩니다. 키를 다시 만들 필요는 없습니다.

OS별로 ssh-agent에 키 등록하기

ssh-agent는 개인 키를 메모리에 올려 두고 SSH 클라이언트가 인증할 때 대신 서명해 주는 백그라운드 프로그램입니다. 패스프레이즈 없는 키를 기본 이름으로 저장했다면 에이전트 없이도 동작하지만, 패스프레이즈를 걸었다면 사실상 필수입니다.

Windows: OpenSSH Authentication Agent 서비스

윈도우의 ssh-agent는 “OpenSSH Authentication Agent”라는 윈도우 서비스로 들어 있고, 기본값이 “사용 안 함”입니다. 그래서 그냥 ssh-add를 실행하면 이런 오류가 납니다.

Error connecting to agent: No such file or directory

관리자 권한 PowerShell에서 서비스를 자동 시작으로 바꾸고 켭니다.

# 관리자 권한 PowerShell
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent

그다음 일반 PowerShell에서 키를 추가합니다.

ssh-add $env:USERPROFILE\.ssh\id_ed25519

윈도우의 이 에이전트는 추가한 키를 레지스트리에 사용자별로 암호화해 저장하기 때문에, 재부팅 후에도 다시 ssh-add를 할 필요가 없습니다.

여기서 윈도우 사용자가 가장 많이 막히는 지점이 있습니다. Git for Windows는 자체 ssh.exe(Git 설치 폴더의 usr\bin\ssh.exe)를 함께 설치하고 기본으로 그것을 씁니다. 이 ssh는 윈도우 서비스로 도는 OpenSSH 에이전트와 통신하지 않습니다. 결과적으로 PowerShell에서 ssh -T [email protected]은 성공하는데 git push만 패스프레이즈를 계속 묻거나 Permission denied (publickey)로 실패하는 일이 생깁니다. Git이 윈도우 내장 OpenSSH를 쓰도록 지정하면 해결됩니다.

git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"

경로 구분자를 슬래시로 쓰는 점에 주의합니다. 반대로 Git Bash 안에서만 작업하고 윈도우 서비스를 쓰지 않을 생각이라면, Git Bash에서 eval "$(ssh-agent -s)"와 ssh-add로 리눅스와 같은 방식을 써도 됩니다. 두 방식을 섞는 것이 문제의 원인이므로 하나를 골라 일관되게 쓰는 것이 중요합니다.

macOS: 키체인과 —apple-use-keychain

macOS는 로그인할 때 ssh-agent가 이미 떠 있습니다. 키를 추가하면서 패스프레이즈를 키체인에 저장하도록 합니다.

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

--apple-use-keychain은 macOS Monterey(12)부터의 옵션 이름이고, 그 이전 버전에서는 같은 기능이 -K였습니다. 인터넷의 오래된 글을 따라 -K를 쓰면 최신 macOS에서는 경고가 나오거나, 하드웨어 보안 키 관련 옵션으로 해석되어 엉뚱하게 동작할 수 있습니다.

재부팅 후에도 키가 자동으로 에이전트에 올라가게 하려면 ~/.ssh/config에 다음을 추가합니다. 파일이 없으면 새로 만듭니다.

Host github.com
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519

UseKeychain은 애플이 패치한 OpenSSH에만 있는 옵션입니다. 같은 config 파일을 dotfiles로 리눅스와 공유하면 리눅스 쪽 ssh가 Bad configuration option: usekeychain이라며 접속을 거부합니다. 공유할 생각이라면 파일 맨 위에 IgnoreUnknown UseKeychain 한 줄을 넣어 두면 양쪽에서 모두 동작합니다.

리눅스: 데스크톱은 대부분 이미 실행 중

GNOME이나 KDE 같은 데스크톱 환경은 로그인 시 키링 또는 ssh-agent를 함께 띄우는 경우가 많습니다. 먼저 에이전트가 있는지 확인합니다.

ssh-add -l

The agent has no identities.가 나오면 에이전트는 있고 키만 없는 상태이므로 ssh-add ~/.ssh/id_ed25519로 추가하면 됩니다. Could not open a connection to your authentication agent.가 나오면 에이전트가 없는 것이므로 직접 띄웁니다.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

eval로 감싸는 이유는 ssh-agent -s가 SSH_AUTH_SOCK 같은 환경 변수 설정 명령을 출력하기 때문입니다. 그냥 ssh-agent만 실행하면 에이전트는 뜨지만 현재 셸이 그 위치를 몰라서 ssh-add가 여전히 연결하지 못합니다. 이렇게 띄운 에이전트는 그 셸 세션에만 유효하므로, 서버처럼 데스크톱이 없는 환경에서 매번 쓰려면 ~/.ssh/config에 AddKeysToAgent yes를 넣거나 keychain 같은 도구를 쓰는 방법이 있습니다.

공개 키를 깃허브에 붙여 넣기

깃허브에 올리는 것은 반드시 .pub로 끝나는 공개 키입니다. 개인 키(확장자 없는 파일)는 어디에도 붙여 넣으면 안 됩니다. 클립보드로 복사하는 명령은 OS마다 다릅니다.

# macOS
pbcopy < ~/.ssh/id_ed25519.pub

# 리눅스 (Wayland는 wl-copy, X11은 xclip -selection clipboard)
cat ~/.ssh/id_ed25519.pub
# Windows PowerShell
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | Set-Clipboard

내용은 ssh-ed25519 AAAAC3Nza... [email protected]처럼 한 줄입니다. 터미널에서 마우스로 긁어 복사하면 줄바꿈이 끼어들거나 앞뒤가 잘려서 깃허브가 “Key is invalid”라고 거부하는 경우가 있으니 가능하면 위 명령을 씁니다.

깃허브에서는 오른쪽 위 프로필 사진 → Settings → SSH and GPG keys → New SSH key로 들어갑니다. Title에는 기기 이름을 적고, Key type은 Authentication Key로 둔 채 Key 칸에 붙여 넣고 저장합니다.

Key type 드롭다운에는 Signing Key도 있는데, 이것은 커밋 서명 전용입니다. 실수로 Signing Key로만 등록하면 화면에는 키가 잘 보이는데 push는 Permission denied (publickey)로 실패합니다. 키 목록에서 키가 “Authentication keys” 아래에 있는지 확인합니다.

GitHub CLI를 이미 쓰고 있다면 gh ssh-key add ~/.ssh/id_ed25519.pub --title "laptop-2026"으로 같은 일을 터미널에서 할 수 있습니다.

ssh -T [email protected]으로 연결 확인하기

저장소에 push해 보기 전에 SSH 연결만 따로 확인합니다. 그래야 문제가 생겼을 때 SSH 단계인지 Git 설정 단계인지 나눌 수 있습니다.

ssh -T [email protected]

처음 접속하면 깃허브 서버의 호스트 키를 믿을지 묻습니다.

The authenticity of host 'github.com (20.200.245.247)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

이 질문은 “지금 접속한 서버가 정말 깃허브인가”를 확인하는 단계입니다. 출력된 지문을 깃허브 공식 문서의 “GitHub’s SSH key fingerprints” 페이지에 있는 값과 비교한 뒤 yes를 입력합니다. 작성 시점 기준으로 공개된 지문은 다음과 같습니다.

SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s (RSA)
SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM (ECDSA)
SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU (ED25519)

yes를 입력하면 이 키가 ~/.ssh/known_hosts에 저장되고 다음부터는 묻지 않습니다. 인증까지 성공하면 다음 메시지가 나옵니다.

Hi USERNAME! You've successfully authenticated, but GitHub does not provide shell access.

“does not provide shell access”는 오류가 아닙니다. 깃허브는 SSH로 셸을 열어 주지 않고 Git 작업만 받기 때문에 항상 이 문장을 붙입니다. 이 명령의 종료 코드는 성공해도 1이라서, 스크립트에서 $?로 성공 여부를 판단하면 안 되고 출력의 Hi USERNAME! 부분을 봐야 합니다. 여기에 나온 USERNAME이 내가 push하려는 계정과 같은지도 확인해 둡니다. 다른 계정 이름이 나오면 뒤의 “계정 두 개” 절에 해당하는 상황입니다.

[email protected]의 git은 내 깃허브 아이디가 아니라 모든 사용자가 공통으로 쓰는 접속 계정입니다. 깃허브는 이 계정으로 들어온 연결을 어떤 공개 키로 인증했는지 보고 사용자를 구분합니다. ssh -T [email protected]처럼 아이디를 넣으면 실패합니다.

기존 저장소의 원격 주소를 HTTPS에서 SSH로 바꾸기

SSH 연결이 성공해도 이미 HTTPS로 클론한 저장소는 여전히 HTTPS로 push합니다. 원격 주소를 확인하고 바꿉니다.

git remote -v
# origin  https://github.com/USER/REPO.git (fetch)
# origin  https://github.com/USER/REPO.git (push)

git remote set-url origin [email protected]:USER/REPO.git
git remote -v
# origin  [email protected]:USER/REPO.git (fetch)
# origin  [email protected]:USER/REPO.git (push)

SSH 주소는 github.com 뒤가 슬래시가 아니라 콜론(:)이라는 점이 자주 틀리는 부분입니다. [email protected]/USER/REPO.git처럼 쓰면 Git이 이를 로컬 경로로 해석해 does not appear to be a git repository를 출력합니다. 저장소 페이지의 초록색 Code 버튼에서 SSH 탭을 누르면 정확한 주소를 복사할 수 있습니다.

모든 깃허브 저장소를 일괄로 SSH로 쓰고 싶다면 URL 재작성 규칙을 걸어 두는 방법도 있습니다. 이렇게 하면 HTTPS 주소로 클론해도 내부적으로 SSH를 씁니다.

git config --global url."[email protected]:".insteadOf "https://github.com/"

다만 이 설정은 npm·Go 모듈처럼 HTTPS로 공개 저장소를 받는 도구에도 적용되어, SSH 키가 없는 CI 환경 등에서 예상치 못한 실패를 부를 수 있습니다. 원격 저장소 작업 흐름 전반은 Git 원격 저장소와 협업에서 다룹니다.

Permission denied (publickey) 원인 찾기

가장 흔한 오류입니다.

[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

이 메시지는 “클라이언트가 내민 공개 키 중 깃허브 계정에 등록된 것이 하나도 없었다”는 뜻입니다. 원인은 대부분 다음 중 하나입니다.

  1. 키를 만들었지만 깃허브에 등록하지 않았거나, Signing Key로만 등록했습니다.
  2. 키를 기본 이름이 아닌 파일로 저장했는데 ~/.ssh/config에 IdentityFile을 적지 않았고 에이전트에도 추가하지 않았습니다.
  3. 윈도우에서 Git이 내장 OpenSSH가 아닌 Git for Windows의 ssh를 써서 에이전트의 키를 보지 못합니다(앞의 core.sshCommand 설정).
  4. sudo git push처럼 sudo로 실행했습니다. 이 경우 ssh는 내 홈이 아닌 root의 ~/.ssh에서 키를 찾습니다. 저장소 폴더의 소유권 문제 때문에 sudo를 붙였다면 sudo 대신 chown으로 소유권을 고치는 것이 맞습니다.
  5. 개인 키 파일 권한이 너무 열려 있어 ssh가 키를 무시했습니다(뒤의 권한 절 참고).

어느 경우인지는 추측하지 말고 상세 로그로 확인하는 것이 빠릅니다.

ssh -vT로 어떤 키를 내밀었는지 보기

ssh -vT [email protected]

출력이 길지만 볼 곳은 정해져 있습니다.

debug1: Reading configuration data /home/user/.ssh/config
debug1: Will attempt key: /home/user/.ssh/id_ed25519 ED25519 SHA256:abc... agent
debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:abc... agent
debug1: Server accepts key: /home/user/.ssh/id_ed25519 ED25519 SHA256:abc... agent
debug1: Authenticated to github.com ([20.200.245.247]:22) using "publickey".
  • Will attempt key 줄에 내 키가 없다면 ssh가 키를 찾지 못한 것입니다. 파일 이름과 config의 IdentityFile 경로를 확인합니다. 줄 끝에 agent가 붙어 있으면 에이전트에서 가져온 키입니다.
  • Offering public key 다음에 Server accepts key가 없고 곧바로 다음 키로 넘어가거나 No more authentication methods to try.로 끝난다면, 키는 제대로 내밀었지만 깃허브 계정에 등록되지 않은 키입니다. 로그에 찍힌 SHA256:... 지문을 ssh-keygen -lf ~/.ssh/id_ed25519.pub 결과, 그리고 깃허브 SSH 키 설정 화면에 표시된 지문과 비교하면 어느 키가 어긋났는지 바로 보입니다.
  • Reading configuration data 줄로 어떤 config 파일을 읽었는지도 알 수 있습니다. 윈도우에서 Git이 쓰는 ssh와 PowerShell의 ssh가 다른 config를 읽는지 확인할 때 유용합니다.

git push 자체의 SSH 로그가 필요하면 GIT_SSH_COMMAND="ssh -v" git push로 Git이 실행하는 ssh에 옵션을 넘길 수 있습니다. 윈도우 PowerShell에서는 $env:GIT_SSH_COMMAND="ssh -v"를 먼저 설정한 뒤 git push를 실행합니다.

인증은 되는데 저장소에 접근할 수 없을 때

ssh -T는 성공하는데 push만 다음처럼 실패한다면 SSH 문제가 아니라 권한 문제입니다.

ERROR: Repository not found.
fatal: Could not read from remote repository.
ERROR: Permission to OWNER/REPO.git denied to OTHER-USER.

깃허브는 비공개 저장소의 존재 자체를 숨기기 위해, 권한이 없는 계정에는 “없다”고 답합니다. ssh -T에서 나온 USERNAME이 그 저장소에 접근 권한이 있는 계정인지, 저장소 주소의 오타는 없는지 확인합니다. denied to deploy key라는 문구가 보이면 그 키가 다른 저장소의 Deploy key로 등록되어 있어 해당 저장소에만 쓸 수 있는 상태입니다. 같은 공개 키는 깃허브 전체에서 한 곳(한 계정 또는 한 저장소의 Deploy key)에만 등록할 수 있으므로, 개인 계정용으로 쓰려면 Deploy key 쪽에서 지우거나 새 키를 만들어야 합니다.

Host key verification failed와 REMOTE HOST IDENTIFICATION HAS CHANGED

키 인증 이전 단계, 즉 “이 서버가 진짜 깃허브인가”를 확인하는 단계에서 실패하면 이런 메시지가 나옵니다.

Host key verification failed.
fatal: Could not read from remote repository.

단독으로 이 메시지만 나오는 대표적인 경우는 처음 접속하는데 질문에 답할 수 없는 상황입니다. CI 러너나 Docker 빌드처럼 터미널이 없는 환경에서는 “continue connecting?”에 아무도 yes를 입력할 수 없으니 바로 실패합니다. 이런 환경에서는 깃허브 호스트 키를 미리 known_hosts에 넣어 둡니다.

mkdir -p ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts

ssh-keyscan은 네트워크에서 받은 키를 그대로 저장하므로 그 순간의 중간자 공격은 막지 못합니다. 보안이 중요한 파이프라인이라면 ssh-keyscan 대신 깃허브 공식 문서에 있는 known_hosts 항목을 복사해 넣거나, https://api.github.com/meta 응답의 ssh_keys 값과 비교한 뒤 넣는 편이 안전합니다. StrictHostKeyChecking no로 확인 자체를 끄는 방법이 검색에 많이 나오지만, 이는 호스트 확인이라는 보호 장치를 통째로 없애는 것이라 권하지 않습니다.

2023년 3월 깃허브 RSA 호스트 키 교체

예전에 잘 되던 PC에서 갑자기 아래 경고가 나왔다면 대개 이 경우입니다.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s.
Please contact your system administrator.
Add correct host key in /home/user/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/user/.ssh/known_hosts:1
Host key verification failed.

깃허브는 2023년 3월 24일, RSA 호스트 개인 키가 공개 저장소에 잠깐 노출된 사실을 확인하고 RSA 호스트 키를 교체했습니다. 그 전에 known_hosts에 옛 RSA 키를 저장해 둔 PC는 새 키를 받으면 서버가 바뀌었다고 판단해 접속을 막습니다. ssh 입장에서는 정당한 교체와 중간자 공격을 구분할 수 없으니 경고가 과격하게 나오는 것입니다.

경고에 찍힌 지문이 위에서 본 깃허브 공식 RSA 지문(SHA256:uNiVztks...)과 같은지 먼저 확인합니다. 같다면 옛 항목을 지우고 다시 접속해 새 키를 받으면 됩니다.

ssh-keygen -R github.com
ssh -T [email protected]

ssh-keygen -R은 known_hosts에서 해당 호스트의 항목만 지우고 원본을 known_hosts.old로 백업합니다. 지문이 공식 값과 다르다면 지우지 말고 접속을 멈춰야 합니다. 공용 와이파이나 회사 프록시가 SSH 연결을 가로채고 있을 가능성이 있습니다. 경고에 Offending RSA key in ...:1처럼 줄 번호가 나오므로 해당 줄을 직접 지워도 결과는 같습니다. 443번 포트로 접속하는 설정(다음 절)을 쓰고 있다면 저장된 이름이 [ssh.github.com]:443이라 ssh-keygen -R "[ssh.github.com]:443"로 지워야 합니다.

22번 포트가 막힌 네트워크: ssh.github.com:443

회사·학교·카페 네트워크는 22번 포트의 외부 연결을 막아 두는 경우가 있습니다. 이때는 오류 메시지가 인증과 무관하게 나옵니다.

ssh: connect to host github.com port 22: Connection timed out
kex_exchange_identification: Connection closed by remote host

깃허브는 이런 환경을 위해 HTTPS와 같은 443번 포트로 SSH를 받는 ssh.github.com을 운영합니다. 먼저 이 경로가 열려 있는지 확인합니다.

ssh -T -p 443 [email protected]

Hi USERNAME!이 나오면 ~/.ssh/config에 다음을 넣어 github.com으로 가는 연결을 항상 이쪽으로 보냅니다.

Host github.com
  HostName ssh.github.com
  Port 443
  User git

이렇게 하면 저장소 원격 주소([email protected]:USER/REPO.git)는 그대로 두고 연결 경로만 바뀝니다. 처음 접속할 때 [ssh.github.com]:443에 대한 호스트 키 확인 질문이 한 번 더 나오는데, 지문은 github.com과 같으므로 같은 방법으로 확인하고 yes를 입력합니다. 443번조차 TLS 검사 프록시가 가로채는 네트워크라면 SSH로는 방법이 없고, HTTPS + 토큰 방식이 현실적인 대안입니다.

Bad owner or permissions와 UNPROTECTED PRIVATE KEY FILE

OpenSSH는 다른 사용자가 읽거나 고칠 수 있는 키·설정 파일을 신뢰하지 않습니다. 남이 config를 고칠 수 있다면 접속 대상을 바꿀 수 있고, 개인 키를 읽을 수 있다면 그 사람도 나로 인증할 수 있기 때문입니다. 파일 권한이 맞지 않으면 다음 두 가지 메시지가 나옵니다.

Bad owner or permissions on /home/user/.ssh/config
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/user/.ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/user/.ssh/id_ed25519": bad permissions
[email protected]: Permission denied (publickey).

두 번째 경우는 마지막 줄이 Permission denied (publickey)라서 앞부분을 놓치면 원인을 엉뚱한 곳에서 찾게 됩니다. 키를 다른 PC에서 복사해 왔거나, 압축 파일·클라우드 드라이브에서 꺼냈을 때 흔히 생깁니다.

macOS·리눅스

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 ~/.ssh/config
chmod 644 ~/.ssh/id_ed25519.pub

소유자가 다른 사용자(예: sudo로 파일을 만들어 root 소유가 된 경우)라면 sudo chown -R $USER:$USER ~/.ssh로 소유권부터 돌려놓습니다.

Windows: chmod 대신 ACL

윈도우의 내장 OpenSSH는 파일 권한을 NTFS ACL로 검사합니다. 내 계정과 SYSTEM, Administrators 외의 사용자(특히 상속된 Users 그룹이나 Authenticated Users)에게 읽기 권한이 있으면 같은 오류가 납니다. 파일 속성 → 보안 → 고급에서 상속을 끊고 내 계정만 남겨도 되고, PowerShell에서는 다음과 같이 할 수 있습니다.

$key = "$env:USERPROFILE\.ssh\id_ed25519"
icacls $key /inheritance:r
icacls $key /grant:r "$($env:USERNAME):F"
icacls $key

마지막 icacls $key 출력에 내 계정만(또는 SYSTEM·Administrators와 함께) 남아 있으면 됩니다. 도메인 계정이라 $env:USERNAME만으로 계정을 찾지 못하면 "$env:USERDOMAIN\$env:USERNAME" 형식으로 지정합니다. config 파일에서 같은 오류가 나면 같은 방법을 $env:USERPROFILE\.ssh\config에 적용합니다. Git Bash에서 chmod 600을 실행해도 NTFS ACL은 바뀌지 않으므로 윈도우 내장 ssh의 오류는 해결되지 않습니다.

깃허브 계정 두 개를 한 PC에서 쓰기

개인 계정과 회사 계정을 한 PC에서 쓸 때 문제가 생기는 이유는, 깃허브가 사용자를 공개 키로만 구분하기 때문입니다. 접속 주소는 둘 다 [email protected]이라 SSH 입장에서는 같은 서버이고, ssh는 가진 키를 순서대로 내밉니다. 개인 키가 먼저 받아들여지면 회사 저장소에 개인 계정으로 접근하게 되어 Repository not found가 납니다. 또 같은 공개 키는 두 계정에 동시에 등록할 수 없어서(Key is already in use), 계정마다 키를 따로 만들어야 합니다.

ssh-keygen -t ed25519 -C "work-laptop" -f ~/.ssh/id_ed25519_work

이 키를 회사 깃허브 계정에 등록한 뒤, ~/.ssh/config에 호스트 별칭을 만듭니다.

# 개인 계정 (기본)
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes

# 회사 계정
Host github-work
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes

Host 뒤의 github-work는 내가 지어낸 별칭이고, 실제 접속은 HostName의 github.com으로 갑니다. 핵심은 IdentitiesOnly yes입니다. 이 옵션이 없으면 ssh는 IdentityFile에 적은 키뿐 아니라 에이전트에 올라가 있는 다른 키도 함께 내밀고, 에이전트의 개인 키가 먼저 받아들여지면 별칭을 나눈 의미가 없어집니다. IdentitiesOnly yes는 “이 호스트에는 config에 적은 키만 쓰라”고 고정합니다.

회사 저장소는 원격 주소의 호스트 부분을 별칭으로 바꿔서 씁니다.

git clone git@github-work:COMPANY/REPO.git

# 이미 클론한 저장소라면
git remote set-url origin git@github-work:COMPANY/REPO.git

# 확인: 회사 계정 이름이 나와야 함
ssh -T git@github-work

SSH 키는 push 권한만 결정하고, 커밋에 찍히는 작성자 이름·이메일은 별개입니다. 회사 저장소에서 개인 이메일로 커밋하지 않으려면 저장소 안에서 git config user.email "[email protected]"을 따로 설정하거나, ~/.gitconfig에 includeIf "gitdir:~/work/"로 회사 작업 폴더 전체에 다른 설정 파일을 적용합니다. Git 설정과 자주 쓰는 명령은 Git 필수 명령어와 실무 팁에 정리되어 있습니다.

키를 바꾸거나 PC를 정리할 때 해야 할 일

SSH 키는 한 번 등록하면 잊어버리기 쉽지만, 깃허브 설정 화면에는 “Last used” 날짜와 함께 남아 있습니다. 다음 상황에서는 깃허브 쪽도 같이 정리해야 합니다.

PC를 처분하거나 노트북을 잃어버렸다면 Settings → SSH and GPG keys에서 그 기기의 키를 Delete합니다. 개인 키 파일을 지우는 것만으로는 부족합니다. 복사본이 어디에 남아 있을지 알 수 없고, 깃허브에서 공개 키를 지워야 그 개인 키가 쓸모없어집니다. 기기 이름을 Title로 적어 두었다면 어느 키를 지울지 바로 찾을 수 있습니다. 같은 화면에서 오랫동안 쓰지 않은 키도 함께 지워 두면 좋습니다.

새 키로 교체할 때는 순서를 지킵니다. 새 키를 만들어 깃허브에 등록하고, ssh -T [email protected]이 새 키로 성공하는지(ssh -vT의 Server accepts key 줄에서 새 키 경로 확인) 본 뒤에 옛 키를 깃허브와 에이전트(ssh-add -d ~/.ssh/id_rsa)에서 지웁니다. 옛 키부터 지우면 그 사이 push가 막힙니다.

SSH를 접고 HTTPS로 돌아가고 싶다면 git remote set-url origin https://github.com/USER/REPO.git로 주소만 되돌리면 됩니다. url.insteadOf 규칙을 걸어 두었다면 git config --global --unset url."[email protected]:".insteadOf로 함께 지웁니다. 윈도우에서 core.sshCommand를 설정했다면 git config --global --unset core.sshCommand로 기본값으로 되돌릴 수 있습니다.

이제 연결이 되었다면 서버 접속에도 같은 키와 ~/.ssh/config 별칭을 활용할 수 있습니다. 서버용 별칭, 포트 포워딩, 접속 유지 설정 같은 내용은 SSH 원격 접속 팁에서 이어서 다룹니다.