Cloning a private GitHub repository over SSH on Ubuntu
- ubuntu
- git
- github
- ssh
- deploy-keys
- linux
Cloning a public repository is one command and no credentials. A private one needs GitHub to know who is asking, and over SSH that means a key it recognises. The mechanics are simple; the error messages are not, which is where the time goes.
This guide is the narrow version of the task. For installing Git, tokens, the gh CLI, commit
signing and running two accounts on one machine, see the full Git and GitHub
guide.
The short version
ssh-keygen -t ed25519 -C "your@email.com"
cat ~/.ssh/id_ed25519.pub # paste this into GitHub
ssh -T git@github.com # verify
git clone git@github.com:owner/repo.git
If that works, you are done. The rest of this page is for when it does not, and for the case where the machine doing the cloning is a server rather than your laptop.
First decide which kind of key
This is the choice that matters, and it is usually made by accident.
| Personal key | Deploy key | |
|---|---|---|
| Scope | Every repo your account can reach | One repository |
| Lives on | Your own machines | A server |
| Write access | Yes | Optional, off by default |
| If the machine is compromised | Your whole GitHub account | That one repository, read-only |
Your laptop: personal key. You need to reach many repositories and push to them.
A server: deploy key. A VPS that pulls one repository to deploy it has no business holding credentials for everything else you own. This is not paranoia — a leaked personal key on a web server is one of the more common ways a GitHub account gets taken over.
Cloning onto your own machine
1. Check for a key you already have
ls -la ~/.ssh
id_ed25519 and id_ed25519.pub mean you have one. Skip to step 3.
2. Generate one
ssh-keygen -t ed25519 -C "your@email.com"
Press Enter for the default path, and set a passphrase — on a personal machine there is no good reason not to. The agent in the next step means you type it once per session.
3. Load it into the agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
To avoid repeating that in every terminal, put this in ~/.ssh/config:
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
AddKeysToAgent yes
4. Give GitHub the public key
cat ~/.ssh/id_ed25519.pub
Copy the whole line. On GitHub: Settings → SSH and GPG keys → New SSH key. Key type Authentication Key, name it after the machine.
Only the .pub file. The other one never leaves the machine.
5. Test before cloning
ssh -T git@github.com
Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.
That is success. It exits with status 1 and it still worked — GitHub has no shell to give you.
Testing here rather than going straight to git clone is worth the extra ten seconds: it separates
“my key is wrong” from “my repository path is wrong”, and those have very different fixes.
6. Clone
Get the URL from the repository page: green Code button → SSH tab. It looks like
git@github.com:owner/repo.git. Then:
git clone git@github.com:owner/repo.git
Into a specific directory:
git clone git@github.com:owner/repo.git /opt/myapp
Cloning onto a server with a deploy key
This is the case worth getting right. Run these on the server.
1. Generate a dedicated key
ssh-keygen -t ed25519 -C "deploy@myserver" -f ~/.ssh/myapp_deploy -N ""
-N "" means no passphrase, and here that is deliberate: an unattended git pull from cron or a
deploy script cannot type one. That is precisely why this key must be scoped to a single repository
and read-only — the passphrase is not what protects you, the scope is.
2. Add it as a deploy key
cat ~/.ssh/myapp_deploy.pub
On GitHub, in the repository — not in your account settings: Settings → Deploy keys → Add deploy key. Paste it, title it after the server, and leave Allow write access unticked unless the server genuinely needs to push.
3. Point SSH at it with a host alias
Since this key is not the default, give it a name in ~/.ssh/config:
Host github-myapp
HostName github.com
User git
IdentityFile ~/.ssh/myapp_deploy
IdentitiesOnly yes
Lock the permissions, or SSH will refuse to use the files:
chmod 700 ~/.ssh && chmod 600 ~/.ssh/myapp_deploy ~/.ssh/config && chmod 644 ~/.ssh/myapp_deploy.pub
4. Test and clone through the alias
ssh -T git@github-myapp
The greeting for a deploy key names the repository rather than a user — something like
Hi owner/repo! You've successfully authenticated. That is the confirmation that the key is bound
to the right repository.
git clone git@github-myapp:owner/repo.git /opt/myapp
Note the alias replaces the hostname, but the owner/repo part stays exactly as it is.
One deploy key, one repository
A deploy key can be attached to only one repository across the whole of GitHub. Try to add the same public key to a second repo and you get:
Key is already in use
This surprises people setting up a server that deploys three services. Two ways out:
- One key per repository, each with its own
Hostalias. Fine for a handful, tedious beyond that. - A machine user — a separate GitHub account created for the server, given read access to the repositories it needs, with one normal SSH key. This is what GitHub recommends at scale, and it costs a seat on paid plans.
For a personal VPS running one or two projects, one key per repository is the simpler answer.
Adding a second repository to a server that already has one
This is worth its own section, because the symptom points nowhere near the cause.
The setup: a machine already deploying one project — call it conduit — with a deploy key sitting
at the default path, ~/.ssh/id_ed25519. You clone a second, unrelated repository the obvious way:
git clone git@github.com:owner/newproject.git /opt/newproject
ERROR: Repository not found.
Nothing is broken. SSH found the default key, offered it, and GitHub accepted it — as the conduit
deploy key, which has no visibility of newproject. A key scoped to one repository asking about
another gets the same 404 as a stranger.
The diagnosis
ssh -T git@github.com
Read the greeting carefully, because it names who GitHub thinks you are:
| Greeting | Meaning |
|---|---|
Hi username! |
A personal account key. Access follows your account |
Hi owner/repo! |
A deploy key, scoped to exactly that repository |
Permission denied (publickey) |
No key GitHub recognises |
Being greeted by the name of a different repository is the whole answer: the key works, it is simply the wrong one for what you are cloning.
The fix
A second key, and an alias so the two never compete.
ssh-keygen -t ed25519 -C "server-newproject" -f ~/.ssh/newproject_deploy -N ""
cat ~/.ssh/newproject_deploy.pub
Add it under the new repository’s Settings → Deploy keys. Then in ~/.ssh/config:
Host github-newproject
HostName github.com
User git
IdentityFile ~/.ssh/newproject_deploy
IdentitiesOnly yes
ssh -T git@github-newproject
The greeting should now name the new repository. Then clone through the alias:
git clone git@github-newproject:owner/newproject.git /opt/newproject
The existing project is untouched — it still resolves github.com to the default key, and the new
block only applies to the alias.
Why IdentitiesOnly yes is doing the real work here
Without it, SSH offers every key it can find, in whatever order it likes. On a machine with two
deploy keys that means it will frequently present the first one, get authenticated as the wrong
repository, and hand you Repository not found — while the correct key sits unused two lines down
in the same directory. IdentitiesOnly yes restricts the alias to exactly the key you named, which
turns an intermittent, order-dependent failure into a deterministic one.
The alias URL is written into .git/config when you clone, so subsequent git pull runs need no
special treatment.
Make the first key explicit too
The remaining weakness is that the original project still depends on being whichever key happens to be the default. That is a fact about your filesystem, not a decision, and it will not survive contact with a third project.
Give it a name as well:
Host github-conduit
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Then repoint the existing clone:
cd /opt/conduit && git remote set-url origin git@github-conduit:owner/conduit.git
Now each repository names its own key, nothing relies on a default, and adding a fourth project is the same three steps as the second. Once that list gets long enough to annoy you, that is the point to move to a machine user.
Cloning without touching ~/.ssh/config
For a one-off, point Git at a key directly:
GIT_SSH_COMMAND="ssh -i ~/.ssh/myapp_deploy -o IdentitiesOnly=yes" git clone git@github.com:owner/repo.git
Useful in a script or a Dockerfile. If you want that key to keep being used for pull and push
in the resulting clone, persist it in the repository’s own config:
git config core.sshCommand "ssh -i ~/.ssh/myapp_deploy -o IdentitiesOnly=yes"
Cloning as a different user
Deploys usually run as a dedicated unprivileged user, and the key has to belong to that user — SSH
looks in the home directory of whoever is running the command, so a key in /root/.ssh is invisible
to deploy.
sudo -u deploy git clone git@github-myapp:owner/repo.git /opt/myapp
If the target directory does not exist yet, create it and hand it over first:
sudo mkdir -p /opt/myapp && sudo chown deploy:deploy /opt/myapp
Running the clone as root and fixing ownership afterwards works too, but it leaves a .git
directory that root wrote and the deploy user then has to be able to update. Cloning as the right
user from the start avoids a class of permission problems later.
Troubleshooting
Repository not found
This is the confusing one, and on private repositories it is almost never about the repository existing.
ERROR: Repository not found.
fatal: Could not read from remote repository.
GitHub deliberately does not distinguish between “this does not exist” and “this exists but you cannot see it” — telling you which would leak the existence of private repositories to anyone guessing names. So an authentication problem surfaces as a 404.
In practice it means one of:
-
The key is not attached to an account with access. Check who GitHub thinks you are:
ssh -T git@github.com -
You used the wrong host alias, so SSH offered a key scoped to a different repository.
-
A deploy key belonging to another repository — very common on a server that already deploys something else. If
ssh -Tgreets you asHi owner/some-other-repo!, that is your answer; see adding a second repository to a server that already has one. -
The owner or repository name is wrong. Case matters in the path.
-
An organisation with SSO. The key has to be authorised for the org separately — Settings → SSH keys → Configure SSO → Authorize. Until you do, access silently behaves as if you have none.
Permission denied (publickey)
The key is not being offered, or not accepted. Ask SSH what it tried:
ssh -vT git@github.com
Read the Offering public key: lines. If yours is not among them, the agent does not have it:
ssh-add -l
Could not open a connection to your authentication agent means the agent is not running:
eval "$(ssh-agent -s)"
Too many authentication failures
SSH is walking through every key it can find and GitHub disconnects after five attempts. Add
IdentitiesOnly yes to the relevant Host block so it offers only the one you named.
Host key verification failed
The host key is unknown or changed. Reset and reconnect:
ssh-keygen -R github.com
ssh -T git@github.com
Compare the fingerprint against GitHub’s published list before accepting it.
Port 22 is blocked
Common on corporate and hotel networks. GitHub also serves SSH on 443:
Host github.com
HostName ssh.github.com
Port 443
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Bad owner or permissions on ~/.ssh/config
SSH ignores configuration files that others can write:
chmod 600 ~/.ssh/config
Switching an existing clone from HTTPS to SSH
If you cloned over HTTPS and are tired of the token prompts, no need to re-clone:
git remote -v
git remote set-url origin git@github.com:owner/repo.git
git remote -v
Everything else — branches, history, stashes — is untouched.
Cheat sheet
# personal machine
ssh-keygen -t ed25519 -C "you@example.com"
cat ~/.ssh/id_ed25519.pub # -> GitHub, Settings, SSH keys
ssh -T git@github.com
git clone git@github.com:owner/repo.git
# server, deploy key
ssh-keygen -t ed25519 -C "deploy@server" -f ~/.ssh/app_deploy -N ""
cat ~/.ssh/app_deploy.pub # -> repo, Settings, Deploy keys
# ~/.ssh/config: Host github-app / IdentityFile ~/.ssh/app_deploy / IdentitiesOnly yes
ssh -T git@github-app
git clone git@github-app:owner/repo.git /opt/appComments
Corrections, additions and "this broke on my machine" reports are all welcome. You can post anonymously — no account needed.