Git SSH「Permission denied (publickey)」:証拠に基づくトラブルシューティング(2026)

メンテナンス版(2026-09-01検証)。 2017年の原文は空ではなく、末尾に全文を保存した。当時のコマンド(未検証の ssh-keyscan 出力を追加する手順を含む)は保存資料であり、現在の手順ではない。以下では、何かを変更する前にホストの同一性、クライアント認証、agent署名、Gitの経路、サーバー認可を切り分ける。

まず失敗の層を分類する

同時に表示される三つのメッセージが、別々の層を示すことがある。

トレースの証拠意味
REMOTE HOST IDENTIFICATION HAS CHANGED または Host key verification failedサーバーの同一性クライアントがサーバー鍵を信頼できていない。ユーザー鍵の調査はまだ始めない。
Permission denied (publickey)ユーザー認証要求したユーザー名の公開鍵認証が完了しなかった。鍵が存在しないとは限らない。
sign_and_send_pubkey: ... agent refused operationクライアント署名SSHはidentityを見つけたか提示したが、agent/providerが署名を拒否した。公開鍵の再登録では直らない場合がある。
fatal: Could not read from remote repositoryGitの要約GitはSSH transportの失敗を伝えるだけで、どのSSH層かは示さない。

known_hosts の削除、全鍵の再生成、サーバー権限の変更を一度に行わない。証拠を失い、一つの失敗を複数に増やすおそれがある。

読み取り中心の証拠採取から始める

Gitを実行するのと同じアカウント、ターミナル、コンテナ、IDE、CIジョブで実行する。sudo git push は別のホーム、設定、agentを選びうるため、権限昇格は避ける。

git remote -v
git remote get-url --all origin
git remote get-url --push --all origin

ssh -G git-prod |
  awk '$1 ~ /^(hostname|user|port|identityfile|identitiesonly|identityagent)$/ { print }'

ssh -vvv -o BatchMode=yes -o ConnectTimeout=10 -T git-prod

ssh-add -l -E sha256
printf 'SSH_AUTH_SOCK=%sn' "${SSH_AUTH_SOCK:-<unset>}"
if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "$SSH_AUTH_SOCK" ]; then
  printf '%sn' 'agent socket exists'
else
  printf '%sn' 'agent socket is absent or not a socket'
fi

git-prod はremoteで実際に使うSSHホストまたはaliasに置き換える。ssh -G は有効なクライアント設定を出力して終了する。ssh -vvv ... BatchMode=yes はパスワードやホスト確認を求めずに認証を試し、Gitデータはpushしない。通常のshellサーバーでは -T でも非対話セッションが開く場合があるため、自動終了しなければ診断出力を得たところで停止する。

デバッグログにはアカウント名、ホスト名、IPアドレス、ローカルパスが含まれうる。共有前に伏せる一方、アルゴリズム名、fingerprint、Offering public keyServer accepts key、失敗行の順番は残す。

詳細トレースを順番に読む

  1. 接続先: Connecting to ...、port、Authenticating to ... as ... を確認する。正しい鍵でもホストまたはユーザー名が違えば失敗する。
  2. ホスト検証: 選択されたhost-key algorithmとfingerprintを見る。ホスト鍵エラーはユーザー認証より先に起こる。
  3. identityの発見: identity file ... type -1 はそのパスにファイルがないことを示す。Offering public key が実際に提示したidentityを示す。
  4. サーバー応答: 提示後も Authentications that can continue: publickey が続く場合、サーバーがそのユーザー名/鍵の組合せまたはpolicyを受け入れていない可能性が高い。
  5. 署名: Server accepts key の後に sign_and_send_pubkey: ... agent refused operation が出るなら、選択agent、鍵制約、unlock/confirmation状態、hardware providerに絞る。
  6. repository access: SSH認証が成功してからGit repository pathと認可を調べる。

クライアント版と時刻を添えた伏字済みトレースを保存する。別のマシンからコピーした一行を、このsessionの証拠にしない。

ホスト鍵を安全に検証する

ssh-keyscan はnetwork endpointが提示する鍵を取得するだけで、その鍵を認証しない。OpenSSHは、未検証のscan出力から known_hosts を作ると中間者攻撃にさらされると警告している。

予期するSHA-256 fingerprintを、認証済みまたはout-of-bandの経路で得る。たとえばログイン済みprovider console/公式文書、別に検証済みの経路で連絡したserver administrator、server consoleそのものを使う。次に known_hosts を変更せず比較する。

ssh-keygen -F git.example.com

scan_file=$(mktemp)
ssh-keyscan -T 5 -t ed25519 git.example.com >"$scan_file"
ssh-keygen -lf "$scan_file" -E sha256

非標準portの場合、known-hosts名は通常 [git.example.com]:2222 になる。GitHubでは現在のGitHub公式fingerprintページと比較し、forumやこの記事からfingerprintをコピーして信用しない。

上の ed25519 scanは例であり、compatibilityを保証しない。認証済みsourceが別のhost-key typeを公開しているなら、推測でclient policyを弱めず、実際に提示されるそのtypeを取得して比較する。

out-of-bandの値と完全一致した後だけ、backupしてentryを変更する。

mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"

if [ -f "$HOME/.ssh/known_hosts" ]; then
  known_hosts_backup="$HOME/.ssh/known_hosts.pre-change.$(date +%Y%m%d%H%M%S)"
  cp -p -- "$HOME/.ssh/known_hosts" "$known_hosts_backup"
fi

cat "$scan_file" >>"$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/known_hosts"
rm -f -- "$scan_file"

保存済み鍵が変わった場合、正規のrotationか侵害かを先に調査する。検証とbackupの後、ssh-keygen -R git.example.com(またはhostとportの角括弧形式)で該当する古い名前だけを消し、検証済みの鍵を追加する。StrictHostKeyChecking no、空の known_hosts、blind scan appendで解決してはならない。

identityとagentを確認する

private materialを表示せずfingerprintを一覧する。

ssh-add -l -E sha256
ssh-keygen -lf "$HOME/.ssh/id_ed25519_git_prod.pub" -E sha256
ls -ld "$HOME/.ssh" "$HOME/.ssh/config"
ls -l "$HOME/.ssh/id_ed25519_git_prod" "$HOME/.ssh/id_ed25519_git_prod.pub"

公開鍵fingerprintを、Gitアカウントに登録した鍵または対象ユーザーの authorized_keys と比較する。private keyをserver、ticket、chat、repository、shared directoryへコピーしてはならない。serverに必要なのは一行のpublic keyだけである。

対応するOpenSSHでは、agentがそのpublic keyに対応する署名を実行できるかテストできる。

ssh-add -T "$HOME/.ssh/id_ed25519_git_prod.pub"

ssh-add -l に表示されても、agentが広告していることしか証明しない。sign testや実際のSSHが拒否される原因には次がある。

  • desktop keychain、password manager、managed agentがlock中;
  • identityが毎回のconfirmationを要求するが、使えるpromptがない;
  • FIDO/security keyが不在、lock中、touch/PIN待ち;
  • SSH_AUTH_SOCK が古い、または意図しないagentを指す;
  • effective configの IdentityAgentSSH_AUTH_SOCK を上書きする;
  • forwarded agentまたはdestination-constrained agentがこのpathを拒否する;
  • providerが要求された署名操作をサポートしない。

identityを所有するagent/providerでunlockまたはconfirmし、再試行する。最初から全identityを削除したりdesktop agentをkillしたりしない。managed agentが本当に存在せず、isolated shell agentを試す場合だけ、そのlifecycleを明示する。

eval "$(ssh-agent -s)"
ssh-add "$HOME/.ssh/id_ed25519_git_prod"
ssh-add -l -E sha256

# When the isolated shell test is finished:
ssh-agent -k

hardware-backedまたは組織managed identityには適した修復ではない。そのproviderを直すか、正しいsocketを選ぶ。

正確なhost、user、keyを固定する

広い Host * exceptionではなく専用aliasを使う。

Host git-prod
  HostName git.example.com
  User git
  Port 22
  IdentityFile ~/.ssh/id_ed25519_git_prod
  IdentitiesOnly yes
  PubkeyAuthentication yes

IdentitiesOnly yes は、agentが多数のidentityを出しても、設定済みidentityを使うようOpenSSHに指示する。結果を確認する。

ssh -G git-prod |
  awk '$1 ~ /^(hostname|user|port|identityfile|identitiesonly|identityagent)$/ { print }'

GitHubのSSH usernameはGitHub profile名ではなく git である。self-hosted Git serviceも git を要求する場合がある一方、EC2への直接loginはOS accountを使う。あるserviceのusernameを別のserviceへ流用しない。

client fileは他userから書き込み可能であってはならず、private identity fileは読み取り可能でもいけない。backupを保持し対象を確認した後の保守的なmodeは次のとおり。

chmod 700 "$HOME/.ssh"
chmod 600 "$HOME/.ssh/config"
chmod 600 "$HOME/.ssh/id_ed25519_git_prod"
chmod 644 "$HOME/.ssh/id_ed25519_git_prod.pub"

agent-onlyまたはhardware-backed identityでは、IdentityFile がpublic key fileを指し、agent内の対応private keyを選択できる。providerの公式設定に従うこと。hardware-key stubが設定対象identity fileの場合もある。この例のlayoutに合わせるためにprivate materialをexportしない。

必要な場合だけ新しい鍵を生成する

動作中のidentityを上書きしない。client/server/providerが対応するならEd25519は現代的なdefaultであり、対応環境ではFIDO-backed Ed25519がhardware protectionを提供する。固有のfilenameと環境に合うpassphraseを使う。

umask 077
ssh-keygen -t ed25519 -a 64 
  -f "$HOME/.ssh/id_ed25519_git_prod" 
  -C "git-prod"

ssh-keygen -lf "$HOME/.ssh/id_ed25519_git_prod.pub" -E sha256

対応するFIDO hardwareとserverでは:

umask 077
ssh-keygen -t ed25519-sk 
  -f "$HOME/.ssh/id_ed25519_sk_git_prod" 
  -C "git-prod hardware key"

serviceの認証済みkey-management pageまたはadministratorのcontrolled processを通じ、.pub の内容だけを登録する。新しい鍵を検証するまで古い鍵を有効にし、その後計画的に失効させる。新しい鍵では、未検証host key、誤ったusername/remote、壊れたagent socketは修復できない。

Git remoteを検証する

Gitはfetch URLとpush URLを別に持てる。insteadOfpushInsteadOf rewriteが有効な接続先を変えることもある。すべて調べる。

git remote -v
git remote get-url --all origin
git remote get-url --push --all origin
git ls-remote --get-url origin

代表的なSSH形式:

ssh://git@git.example.com:22/team/project.git
git-prod:team/project.git
git@github.com:OWNER/REPOSITORY.git

scp-like URLのalias(git-prod:...)は Host git-prod blockと一致しなければならない。repository owner/pathと、serviceが .git suffixを必要とするかを確認する。旧値を記録するまでremoteを変更しない。修正が必要なら:

old_origin=$(git remote get-url origin)
printf 'old origin: %sn' "$old_origin"

git remote set-url origin git-prod:team/project.git
git remote get-url --all origin
git remote get-url --push --all origin

rollbackは git remote set-url origin "$old_origin"。fetchとpushが意図的に別のlocationを指すなら、それぞれをbackupして変更し、origin にURLが一つだけと仮定しない。

サーバー側を調査する

この節には既存console、recovery channel、または別のadministrator sessionが必要である。SSHを弱めてSSHを回復してはならない。managed Git hostではこの層をproviderが管理するため、filesystemを編集せずaccount/key audit toolを使う。

OpenSSH serverでは要求するOS accountとeffective daemon policyを確認する。

id USERNAME
sudo sshd -T -C user=USERNAME,addr=CLIENT_IP,host=CLIENT_HOST |
  grep -E '^(pubkeyauthentication|authorizedkeysfile|strictmodes|pubkeyacceptedalgorithms) '

sudo -u USERNAME ls -ld 
  /home/USERNAME 
  /home/USERNAME/.ssh 
  /home/USERNAME/.ssh/authorized_keys

sudo ssh-keygen -lf /home/USERNAME/.ssh/authorized_keys -E sha256
sudo journalctl --since '-10 min' -u ssh -u sshd --no-pager

log unit名とfile locationはOSごとに異なるため、そのplatformのSSH authentication logを使う。authorized_keys 全文をsupport requestへ貼らない。

defaultの StrictModes では、home directory、.ssh、またはfileが他userから書き込み可能だと sshdauthorized_keys を拒否する。一般的なmodeは .ssh700authorized_keys600 で、ownershipはlogin accountと一致させる。repair前にrecovery channelからbackupし、正しいgroupを確認する。

id USERNAME
sudo cp -p 
  /home/USERNAME/.ssh/authorized_keys 
  /home/USERNAME/.ssh/authorized_keys.pre-change

sudo chown USERNAME:USERGROUP /home/USERNAME/.ssh
sudo chown USERNAME:USERGROUP /home/USERNAME/.ssh/authorized_keys
sudo chmod 700 /home/USERNAME/.ssh
sudo chmod 600 /home/USERNAME/.ssh/authorized_keys
sudo sshd -t

permission workaroundとして StrictModes no にしない。既存admin sessionを開いたまま sshd -t を通し、OSの手順でreloadし、recovery sessionを閉じる前に別の新規接続を試す。意図した鍵またはaccess policyを誤って変えた場合はbackupを戻す。

EC2固有のusernameとaccessを確認する

EC2のSSH usernameはAWS account名ではなくAMIで決まる。AWSが現在記載する主なdefaultは次のとおり。

AMI family主なdefault username
Amazon Linuxec2-user
Ubuntuubuntu
Debianadmin
CentOScentos または ec2-user
Fedorafedora または ec2-user
RHEL/SUSEec2-user または root
Bitnamibitnami

名前を順に試すのではなく、正確なAMIとprovider documentを確認する。instance address、TCP 22(または設定port)へ到達できるsecurity-group route、private keyがinstanceへprovisionされたpublic keyと対応することも確認する。

EC2 machineは、ubuntu@host のようなOS loginと git@host のようなGit service endpointという二つのSSH identityを提供しうる。remote URLとserver designが要求する方を使う。EC2 Instance Connectはtemporary public-key accessも提供し、そのIAM ec2:osuser conditionはOS userに一致する必要がある。いずれもprivate keyをinstanceへコピーする必要はない。

algorithm errorを別に診断する

no matching host key type foundno matching key exchange method foundno mutual signature algorithm はnegotiationの証拠であり、key missingの一般的証明ではない。clientが対応するalgorithmとaliasが選ぶ値を調べる。

ssh -Q HostKeyAlgorithms
ssh -Q PubkeyAcceptedAlgorithms
ssh -Q kex

ssh -G git-prod |
  awk '$1 ~ /^(hostkeyalgorithms|pubkeyacceptedalgorithms|kexalgorithms)$/ { print }'

HostKeyAlgorithms はserverを認証し、PubkeyAcceptedAlgorithms はuser authenticationに関わる。またRSA keyはobsoleteな ssh-rsa SHA-1 signature algorithmと同義ではない。両端が対応すればmodern RSA keyはRSA-SHA2を使える。

推奨するrepairは古いendpointのupdate、または対応するmodern keyのprovisionである。文書化され期限付きのcompatibility exceptionが不可避なら、検証したerrorが名指しした一つのdirectiveだけを一つの正確なaliasに限定する。Host * の下へ置かず、推測で両方を有効にしない。

Host legacy-git
  HostName legacy-git.example.com
  User git
  # Temporary only, after verifying the exact negotiation error:
  # HostKeyAlgorithms +ssh-rsa
  # PubkeyAcceptedAlgorithms +ssh-rsa

ownerとremoval dateを記録する。永久化したtemporary exceptionは未解決のinfrastructure debtである。

rollbackと受入基準

client/server fileを変える前に、一意名でpermissionを保つbackupを作り、現在のfingerprintとremote URLを記録する。client config parsingは次で試す。

ssh -G -F "$HOME/.ssh/config" git-prod >/dev/null

server configはreload前に sudo sshd -t を実行し、動作するrecovery sessionを保持する。証拠が悪化したら一度に一変更だけ戻す。

該当するすべての条件を満たしたときだけrepairを受け入れる。

  1. server host-key fingerprintが認証済み/out-of-band sourceと一致する。
  2. ssh -G git-prod が意図したhostname、port、username、agent、identity、identitiesonly yes を示す。
  3. agentが広告するkey fingerprintがaccount/server registrationと一致し、必要なagent/providerが拒否せずsignできる。
  4. 新しい ssh -vvv -o BatchMode=yes -T git-prod が意図したendpointへ達し、host-key、public-key、agent-refusal failureで終わらない。GitHubではshellを提供せずnonzero statusになる場合があっても、successful-authentication messageが証拠になる。
  5. 読み取り専用Git transport testが成功し、意図したrepositoryを指す。
git ls-remote --symref origin HEAD
  1. その後にのみ、review済みの意図したpushを再試行する。ls-remote の成功はread authenticationを証明するが、write authorizationやserver hook acceptanceまでは保証しない。実際の意図したwriteが受理されるまでpush修復完了とは言わない。

一次・公式文書

---

2017年英語原文(逐字保存)

以下は当初 PERMISSION DENIED (PUBLICKEY). という題で2017-05-13に公開され、書き出しメタデータで2023-09-30更新と記録されたWordPress本文の全体である。repository hygieneのため、三つのlist行「Generate your key」「Configure ssh to use the key」「Copy your key to your server」の目に見えない行末空白だけを除いた。見える語句、古いcommand、link、indentation、欠落はすべて変更していない。この保存部を現在のauthorityとして扱ってはならない。


A problem occurred while I `git push` to my git server on ec2.

I handled this by following this three guide of which their original links are:

http://stackoverflow.com/questions/13363553/git-error-host-key-verification-failed-when-connecting-to-remote-repository

https://chenhuachao.com/2016/05/26/ssh%E5%87%BA%E9%94%99-sign-and-send-pubkey-signing-failed-agent-refused-operation/

Sorry for missing out the second source link, I will add that later.

sign_and_send_pubkey: signing failed: agent refused operation
 Permission denied (publickey).
 fatal: Could not read from remote repository.

1. `mkdir ~/.ssh`
2. `vim known_hosts` – if you already have *known_hosts*, skip this.
3. `ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts`
4. `ssh-keygen -t rsa -C "user.email"`
5. Add the *id_rsa.pub* key to SSH keys list on your GitHub profile.

Table of Contents

Toggle

- [Set up your client](https://blog.lazying.art/en/html/computer_internet/git/29/permission-denied-publickey-2.html/#Set_up_your_client)

## Set up your client

1. Generate your key

  - `ssh-keygen`

2. Configure ssh to use the key

  - `vim ~/.ssh/config`

3. Copy your key to your server

  - `ssh-copy-id -i /path/to/key.pub SERVERNAME`

Your config file from *step 2* should have something similar to the following:

Host SERVERNAME
Hostname ip-or-domain-of-server
User USERNAME
PubKeyAuthentication yes
IdentityFile ./path/to/key


eval “$(ssh-agent -s)”
 ssh-add

Leave a Reply