はじめに
現在多くの会社および個人の開発において、Githubでリポジトリを管理することは一般的です。しかし、一概にGithubといっても、Github.com、GHEC(GitHub Enterprise Cloud)、GHES(GitHub Enterprise Server)と複数サービスが存在しており、完全に連携しているわけではないため、混乱する方も少なくないかと思います。特にGHECやGHESはVPNで接続するケースも多く、前提知識がない状態で疎通が通らないと途方に暮れてしまう方もいるのではないかと思われます。この記事では「とりあえず動いている(疎通している)」から「なぜ動くのか(疎通できているのか)」が理解できている状況を目標にそれぞれの違いを整理しながら解説いたします。
TL;DR
- GitHub は権限がないリポジトリに対して 403 ではなく 404 を返す。だから「リモートが見つからない」はURL の打ち間違いではなく、認証・認可の失敗であることがほとんど。
- 企業向け GitHub にはホスト名が 3 パターンある。
github.com/<会社名>.ghe.com/ 社内サーバ(GHES)。ここを取り違えると永遠に 404 が出続ける。 - ユーザ名の末尾に
_ruserのようなアンダースコア付きサフィックスが付いていたら、それは Enterprise Managed User(EMU)。個人アカウントとは別人格なので、認証情報が混線しやすい。 - 切り分けは下から順に。「DNS → TLS → 認証 → 認可 → Git設定」の5層。上からいきなり
git pushを連打しても原因は分からない。
第1部: そもそも何が違うのか — 3種類の GitHub
初学者が最初につまずくのは、「GitHub」と一言で呼ばれているものが実は別のサービスであることに気づいていない点です。
| GitHub.com(無料/Pro) | GHEC(Enterprise Cloud) | GHES(Enterprise Server) | |
|---|---|---|---|
| 誰が運用 | GitHub社 | GitHub社 | 自社(社内やクラウドに自前で立てる) |
| Web のホスト名 | github.com |
github.com または <subdomain>.ghe.com
|
github.example.co.jp など任意 |
| API のホスト名 | api.github.com |
api.github.com または api.<subdomain>.ghe.com
|
github.example.co.jp/api/v3 |
| VPN の要否 | 不要 | 原則不要(IP許可リストがあれば必要) | ほぼ必須 |
| アカウント | 個人アカウント | 個人アカウント or EMU | サーバ内の独立アカウント(LDAP/SAML連携) |
| 個人アカウントとの関係 | それ自体 | 同じ or 完全に別人格 | 完全に別人格 |
最大の注意点: GHEC には2つの顔がある
GHEC は契約形態によって、ローカルから見た姿がまったく変わります。
パターンA: 従来型 GHEC(github.com 上の Organization)
- URL は
https://github.com/<org>/<repo> - あなたの個人アカウントのまま、Organization に招待されて入る
- 個人リポジトリと会社リポジトリが同じアカウントに同居する
- 会社リポジトリにアクセスするには SAML SSO の認可が別途必要
パターンB: EMU / Data Residency(<subdomain>.ghe.com)
- URL は
https://acme.ghe.com/<org>/<repo>のようにホスト名からして違う - 会社が IdP(Entra ID / Okta など)から払い出した専用アカウントを使う
- ユーザ名は
takeshi-fujimoto-01050576_ruserのように_<ショートコード>が末尾に付く - このアカウントでは個人の GitHub リポジトリにアクセスできない(制限されている)
チェックポイント
gh auth statusで表示されたユーザ名の末尾に_なにかが付いていたら、それは EMU アカウントです。冒頭の例(takeshi-fujimoto-01050576_ruser)はまさにこれ。
このとき、ログイン先ホストがgithub.comで正しいのか、<subdomain>.ghe.comであるべきなのかを必ず確認してください。ここがズレているとgh auth loginは成功するのにgit pushだけが 404 になります。
どちらのパターンか確認する方法はシンプルで、ブラウザでリポジトリを開いてアドレスバーを見るのが一番早いです。
# 自分がどのホストにログインしているかを確認
gh auth status
github.com
✓ Logged in to github.com account takeshi-fujimoto-01050576_ruser (keyring)
ここが github.com なのに、ブラウザのアドレスバーが acme.ghe.com なら、それが原因です。
第2部: ローカルPCから GitHub までの通信を「5層」で考える
トラブルシューティングが下手な人は、いきなり git push を叩いて「動かない」と言います。うまい人は層に分解して、下から順に潰します。
┌─────────────────────────────────────────────┐
│ L5 Git 設定 remote URL / insteadOf │ ← 意外とここが犯人
├─────────────────────────────────────────────┤
│ L4 認可(AuthZ) Org所属 / SSO / 権限 │ ← 404 の主犯
├─────────────────────────────────────────────┤
│ L3 認証(AuthN) Token / SSHキー │ ← gh auth login が効くのはここ
├─────────────────────────────────────────────┤
│ L2 TLS 証明書 / 社内プロキシ │ ← 社内網の主犯
├─────────────────────────────────────────────┤
│ L1 ネットワーク DNS / ルーティング / VPN │ ← VPN の主犯
└─────────────────────────────────────────────┘
エラーメッセージと層の対応表を頭に入れておくと、一発で当たりが付きます。
| エラーメッセージ | 疑うべき層 |
|---|---|
Could not resolve host |
L1(DNS。VPN未接続 or 社内DNS未使用) |
Connection timed out / Operation timed out
|
L1(経路・ファイアウォール・スプリットトンネル) |
SSL certificate problem: unable to get local issuer certificate |
L2(社内プロキシの TLS インスペクション) |
Authentication failed / Invalid username or password
|
L3(トークンが無効・期限切れ) |
Permission denied (publickey) |
L3(SSH鍵が登録されていない/読まれていない) |
Repository not found / remote: Not Found
|
L4(権限・SSO・アカウント違い) ※たまに L5 |
remote: The '<org>' organization has enabled or enforced SAML SSO |
L4(SSO認可が未実施) |
does not appear to be a git repository |
L5(URL やプロトコル指定のミス) |
第3部: 認証の仕組みを整理する
HTTPS の場合
git push
↓ (URLが https:// なので)
git は credential helper に「github.com のパスワードくれ」と聞く
↓
credential helper(osxkeychain / manager / gh)が保存済みトークンを返す
↓
Authorization ヘッダに載せて HTTPS で送信
ポイントは、Git 自身は認証情報を持っていないことです。持っているのは credential helper。だから「パスワードを変えたのに古い認証情報が使われる」という事故が起こり得ます。
現在の helper は次で確認できます。
git config --get-all credential.helper
gh auth login で Authenticate Git with your GitHub credentials? Yes を選ぶと、内部的にこれが走ります。
gh auth setup-git
これは credential.helper に gh auth git-credential を登録する処理です。つまり gh が保存しているトークンを Git が借りに来る構成になります。冒頭のログで疎通が直ったのは、まさにこれが理由です。
SSH の場合
git push
↓ (URLが git@... なので)
ssh クライアントが ~/.ssh/config を読み、鍵を選ぶ
↓
公開鍵認証でサーバに接続(22番 or 443番)
↓
GitHub 側は「この公開鍵は誰のものか」でユーザを特定
SSH はトークンを一切使いません。その代わり、SSH 鍵にも SSO 認可が必要(GHEC の場合)という落とし穴があります。
認証情報がどこに保存されるか
| 方式 | 保存場所 |
|---|---|
gh CLI |
macOS: Keychain / ~/.config/gh/hosts.yml
|
| Git (macOS) | Keychain(osxkeychain helper 経由) |
| Git (Windows) | 資格情報マネージャー(manager helper 経由) |
| Git (Linux) |
~/.git-credentials(store)または libsecret |
| SSH |
~/.ssh/ の秘密鍵 + ssh-agent |
この「保存場所が複数ある」ことが混線の元凶です。gh では新しいアカウントにログインしているのに、Keychain には古い個人アカウントのトークンが残っていて、そちらが優先される、という事故が頻発します。
第4部: なぜ「リポジトリが見つからない」と出るのか
これは仕様です。GitHub はプライベートリポジトリに対して、権限がない相手には「存在しない」と答えます。
理由はセキュリティで、403 を返すと「そのリポジトリが存在すること」自体が漏れるからです。社内の未発表プロジェクト名が推測できてしまう。
したがって Repository not found は、こう翻訳してください。
「あなたが誰なのか分からないか、あなたにはそれを見る権限がありません」
具体的には次の6つのいずれかです。
- 認証情報がそもそも送られていない(未ログイン)
- 別のアカウントとして認証されている(個人 vs EMU)
-
ホスト名が違う(
github.comに対してacme.ghe.comのリポジトリを探している) - Organization に所属していない、またはリポジトリへの権限がない
- SAML SSO の認可が済んでいないトークン/SSH鍵を使っている
- リポジトリ名が本当に間違っている(一番少ない)
第5部: 切り分けコマンド集
ここからが実務です。上から順に実行してください。
Step 0. 事実の確認(30秒)
# 今のリモートURLは?
git remote -v
# 今どのホストに、誰としてログインしている?
gh auth status
# ブラウザで見えているリポジトリのURLと、上のリモートURLのホスト名は一致しているか?
ここでホスト名が食い違っていたら、これ以上調べる必要はありません。第6部のケースAへ。
Step 1. ネットワーク到達性(L1)
# 名前解決できるか
dig +short github.com
dig +short acme.ghe.com # GHEC Data Residency の場合
dig +short github.example.co.jp # GHES の場合
# TCP で繋がるか(443 と 22 を両方見る)
nc -zv github.com 443
nc -zv github.com 22
# HTTPS が返ってくるか
curl -sS -o /dev/null -w '%{http_code}\n' https://github.com
VPN 環境での典型的な失敗:
-
digが何も返さない → 社内 DNS が使われていない。VPN 未接続か、スプリットトンネル設定漏れ。 -
digは返るがncがタイムアウト → 経路がない。VPN のルーティング対象に含まれていない。
macOS で「どの DNS サーバが使われているか」を確認するには次を使います。
scutil --dns | grep -A2 'resolver #1'
VPN 接続時に社内 DNS(10.x.x.x など)が resolver #1 に来ていなければ、GHES の名前解決は失敗します。
Step 2. TLS とプロキシ(L2)
# 環境変数のプロキシ設定を確認
env | grep -i proxy
# Git 独自のプロキシ設定を確認
git config --global --get-regexp '^http\.|^https\.'
# 証明書チェーンを確認(誰が発行者か見る)
curl -vI https://github.com 2>&1 | grep -i 'issuer\|subject'
issuer が DigiCert などではなく社名や Zscaler / Netskope などになっていたら、TLS インスペクションを受けています。この場合、社内 CA 証明書を Git に教える必要があります。
# 正しい対処(社内CA証明書を指定する)
git config --global http.sslCAInfo /path/to/corporate-ca.pem
# 🚫 絶対にやってはいけない対処
git config --global http.sslVerify false
sslVerify false は「暗号化の検証をやめる」設定です。動くようになりますが、中間者攻撃に対して無防備になります。Qiita でよく見かけますが、業務端末では使わないでください。
Step 3. 認証(L3)
# gh のトークンが実際に有効か、APIを直接叩いて確認する
curl -sS -H "Authorization: Bearer $(gh auth token)" \
https://api.github.com/user | grep '"login"'
GHES / Data Residency の場合はエンドポイントが変わります。
# GHES
curl -sS -H "Authorization: Bearer $(gh auth token --hostname github.example.co.jp)" \
https://github.example.co.jp/api/v3/user
# GHEC Data Residency
curl -sS -H "Authorization: Bearer $(gh auth token --hostname acme.ghe.com)" \
https://api.acme.ghe.com/user
SSH の場合はこれ一発です。
ssh -T git@github.com
# → Hi <username>! You've successfully authenticated... と返れば認証はOK
ここで返ってくるユーザ名が、意図したアカウントかどうかを必ず確認してください。 個人アカウント名が返ってきたなら、それが 404 の原因です。
Step 4. 認可(L4)
# リポジトリに実際にアクセスできるか、pushせずに確認する
git ls-remote https://github.com/<org>/<repo>.git
git ls-remote は何も変更せずに疎通と権限だけを確認できるので、切り分けに最適です。git push で試すのはやめましょう。
SAML SSO が原因の場合、次のような明示的なメッセージが出ます。
remote: The 'acme' organization has enabled or enforced SAML SSO.
この場合はブラウザで認可します。
https://github.com/orgs/<ORG>/sso
Personal Access Token を使っているなら、トークン設定画面の Configure SSO → Authorize を押す必要があります。SSH 鍵も同様に、鍵ごとに認可が必要です。
Step 5. 詳細ログを出す(最終手段)
# HTTPS の場合: HTTPステータスコードまで見える
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push origin main
# SSH の場合: どの鍵を試したかが全部見える
GIT_SSH_COMMAND="ssh -vvv" git push origin main
SSH の verbose ログでは Offering public key: /Users/xxx/.ssh/id_ed25519 という行を探してください。意図しない鍵を先に提示しているのがよくある原因です。
第6部: 症状別・処方箋
ケースA: gh auth login は成功するのに push だけ 404
原因: 認証先ホストとリポジトリのホストが違う。EMU 環境で最頻出。
# 1. 正しいホストにログインし直す
gh auth login --hostname acme.ghe.com
# 2. そのホスト用に credential helper を設定
gh auth setup-git --hostname acme.ghe.com
# 3. リモートURLを正しいホストに直す
git remote set-url origin https://acme.ghe.com/<org>/<repo>.git
# 4. 確認
git ls-remote origin
gh はホストごとに独立してログイン状態を持ちます。複数ホストに同時ログインでき、状態は次で一覧できます。
gh auth status # 全ホストの状態
gh auth switch # アクティブなアカウントを切り替え
ケースB: 個人アカウントと会社アカウントが混線している
症状: ssh -T git@github.com が個人アカウント名を返す。または Keychain の古いトークンが使われる。
まず古い認証情報を消します。
# macOS: Keychain から github.com の認証情報を削除
printf "protocol=https\nhost=github.com\n\n" | git credential-osxkeychain erase
# 汎用(helper を問わず)
printf "protocol=https\nhost=github.com\n\n" | git credential reject
# gh 側もログアウト
gh auth logout --hostname github.com
Windows の場合は「資格情報マネージャー」→「Windows 資格情報」から
git:https://github.comを削除してください。
その上で、ディレクトリごとに ID を切り替える設定を入れると恒久対策になります。
~/.gitconfig:
[user]
name = Taro Yamada
email = taro@example.com
# ~/work/ 配下だけ会社の設定を読み込む
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
~/.gitconfig-work:
[user]
name = takeshi-fujimoto-01050576_ruser
email = takeshi.fujimoto@acme.co.jp
HTTPS で同一ホストに複数アカウントがある場合は、パスごとに認証情報を分ける設定も有効です。
git config --global credential.https://github.com.useHttpPath true
ケースC: VPN を繋いだのに Could not resolve host
原因: スプリットトンネル構成で、GHES のドメインが VPN 経由の DNS に向いていない。
# 1. どの DNS を見ているか
scutil --dns | grep -A2 'resolver #1' # macOS
resolvectl status # Linux (systemd-resolved)
# 2. 社内 DNS を明示指定して引けるか試す
dig @10.0.0.53 github.example.co.jp
# 3. 社内DNSでは引けるのに通常の名前解決で失敗する
# → VPN クライアントの DNS 設定 or 検索ドメインの問題
社内 DNS を直接指定して引ければネットワーク的には到達しているので、あとは VPN クライアント設定の問題として情シスに投げられます。「繋がりません」より格段に話が早いです。
ケースD: SSH が 22番ポートで塞がれている
社内ネットワークやカフェの Wi-Fi では、22 番が閉じられていることがよくあります。
nc -zv github.com 22 # タイムアウトする
nc -zv github.com 443 # 通る
この場合、SSH を 443 番で喋らせます。
ssh -T -p 443 git@ssh.github.com
疎通したら ~/.ssh/config に固定します。
Host github.com
HostName ssh.github.com
Port 443
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
IdentitiesOnly yes が重要です。これがないと ssh-agent 内の全部の鍵を順に試し、意図しない鍵で認証されてしまいます(ケースBの原因にもなる)。
GHEC Data Residency(
*.ghe.com)や GHES にも同等の 443 エンドポイントが用意されている場合があります。ホスト名は環境ごとに異なるため、自社の管理者ドキュメントで確認してください。
ケースE: insteadOf に書き換えられている
企業環境では、セットアップスクリプトが URL 書き換えルールを仕込んでいることがあります。これに気づかないと、git remote -v が正しいのに違うホストに繋ぎに行くという怪奇現象が起きます。
git config --global --get-regexp 'url\..*\.insteadof'
出力例:
url.git@github.com:.insteadof https://github.com/
これがあると、HTTPS の URL を書いても実際には SSH で接続されます。意図しないものなら削除します。
git config --global --unset url.git@github.com:.insteadOf
ケースF: 「昨日まで動いていたのに」
トークンの有効期限切れが定番です。特に Fine-grained PAT はデフォルトで期限が短めです。
# gh のトークンで /user が引けるか(Step 3 の再掲)
curl -sS -o /dev/null -w '%{http_code}\n' \
-H "Authorization: Bearer $(gh auth token)" https://api.github.com/user
# 401 → トークン無効/期限切れ
gh auth refresh --hostname github.com
# または
gh auth login --hostname github.com
第7部: 最初から事故らないためのセットアップ
新しく参加するメンバーには、この順番でやってもらうと事故が減ります。
# 1. ブラウザでリポジトリを開き、ホスト名を確認する(これが全ての起点)
# 例: https://acme.ghe.com/platform/api-server
# 2. そのホストにログインする
gh auth login --hostname acme.ghe.com
# 3. Git の credential helper を紐づける
gh auth setup-git --hostname acme.ghe.com
# 4. 権限確認(cloneより先に)
git ls-remote https://acme.ghe.com/platform/api-server.git
# 5. ここまで通ってから clone
git clone https://acme.ghe.com/platform/api-server.git
ポイントは Step 4 です。 git clone で試すと、失敗した場合に「ネットワークなのか認証なのか権限なのか」が分かりません。git ls-remote なら副作用なしで L1〜L4 をまとめて検証できます。
チートシート
# ── 現状把握 ──────────────────────────────
git remote -v # リモートURL
gh auth status # ログイン中のホストとアカウント
git config --get-all credential.helper # 認証情報の保管庫
git config --global --get-regexp 'url\..*\.insteadof' # URL書き換えの罠
# ── L1 ネットワーク ───────────────────────
dig +short <host>
nc -zv <host> 443
scutil --dns | grep -A2 'resolver #1' # macOS
# ── L2 TLS ────────────────────────────────
env | grep -i proxy
curl -vI https://<host> 2>&1 | grep -i issuer
# ── L3 認証 ───────────────────────────────
ssh -T git@<host>
curl -sS -H "Authorization: Bearer $(gh auth token)" https://api.github.com/user
# ── L4 認可 ───────────────────────────────
git ls-remote <url> # 副作用なしの疎通確認
# ── L5 詳細ログ ───────────────────────────
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push
GIT_SSH_COMMAND="ssh -vvv" git push
# ── リセット ──────────────────────────────
printf "protocol=https\nhost=<host>\n\n" | git credential reject
gh auth logout --hostname <host>
まとめ
-
Repository not foundは「見つからない」ではなく「あなたには見えない」。URL を疑う前に、誰として認証されているかを疑う。 -
ホスト名がすべての起点。
github.com/*.ghe.com/ 自社 GHES のどれなのかを、ブラウザのアドレスバーで確定させてから作業を始める。 -
ghと Git と SSH は別々に認証情報を持つ。片方だけ直しても直らないことがある。 - 切り分けは下の層から。DNS → TLS → 認証 → 認可 → Git設定。この順で潰せば、情シスに投げるときも「DNS は引けていて 443 も通るが、ls-remote が 404 になる」と具体的に説明できる。
冒頭の gh auth login で解決したケースは、要するに L3(認証)が欠けていて、gh が credential helper として穴を埋めた、という話でした。何が起きていたのか説明できるようになれば、次に別のエラーが出たときも自力で辿れるはずです。