ply.wtf
Software & DevelopmentStep-by-step guide

Git on Ubuntu 20.04, 22.04 and 24.04: install, GitHub authentication and cloning over SSH

10 min read
  • ubuntu
  • git
  • github
  • ssh
  • linux

Git itself installs in one command. The part that eats an afternoon is authentication — GitHub removed password authentication in 2021, and the replacements are not obvious.

Installing Git

Git is in the Ubuntu repositories and, unlike Node, the packaged version is genuinely fine:

sudo apt update
sudo apt install -y git
git --version
Ubuntu release Git version
20.04 LTS 2.25.1
22.04 LTS 2.34.1
24.04 LTS 2.43.0

Good enough for almost everything. Two features in this guide need newer than 20.04 ships:

  • SSH commit signing needs 2.34+
  • git switch / git restore are stable from 2.23, so all three are fine there

If you are on 20.04 and want a current Git, the maintainers’ PPA is the least painful route:

sudo add-apt-repository ppa:git-core/ppa
sudo apt update
sudo apt install -y git

First-run configuration

Nothing works properly until Git knows who you are:

git config --global user.name "Your Name"
git config --global user.email "you@example.com"

Use the same address you have on GitHub, or commits will not link to your account. If you would rather not publish a real address, GitHub gives you a @users.noreply.github.com one under Settings → Emails — use that instead and turn on “Block command line pushes that expose my email”.

A few settings that save arguments later:

# Match GitHub's default branch name
git config --global init.defaultBranch main

# Rebase instead of merge on pull — no more "Merge branch 'main' of..." noise
git config --global pull.rebase true

# Only push the branch you are on
git config --global push.default simple

# Keep line endings sane if anyone on the team is on Windows
git config --global core.autocrlf input

# Whatever editor you actually use
git config --global core.editor "vim"

Check the result at any time:

git config --global --list

Authenticating with GitHub

There are three routes. They are not equivalent.

Method Best for Trade-off
SSH key Daily development Set up once, then invisible
HTTPS + token Locked-down networks, CI Token expires, needs storing somewhere
gh CLI Getting started fast An extra tool, wraps HTTPS underneath

SSH is what I use and what the rest of this guide assumes.

SSH keys, start to finish

1. Check for an existing key

ls -la ~/.ssh

If you see id_ed25519 and id_ed25519.pub, you already have one and can skip to step 3.

2. Generate a key

ssh-keygen -t ed25519 -C "you@example.com"

Ed25519 over RSA: shorter, faster, and stronger. Only fall back to ssh-keygen -t rsa -b 4096 if you have to talk to something ancient that cannot handle it.

Press Enter to accept the default path. Set a passphrase. A key file without one is a plaintext credential — anyone who reads your disk or a stray backup owns your GitHub account. The agent in the next step means you type it once per session, not once per push.

3. Load it into the agent

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

To avoid doing that in every terminal, write a ~/.ssh/config:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    AddKeysToAgent yes

IdentitiesOnly yes matters more than it looks. Without it, ssh offers every key it can find, one at a time, and GitHub disconnects after five failures — so on a machine with several keys you get “Too many authentication failures” while holding a perfectly good key.

Permissions must be tight or ssh refuses to use the file at all:

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

4. Add the public key to GitHub

cat ~/.ssh/id_ed25519.pub

Copy the whole line. On GitHub: Settings → SSH and GPG keys → New SSH key, paste, give it a name that identifies the machine (“thinkpad-work”), key type Authentication Key.

Only ever paste the .pub file. The other one never leaves the machine.

5. Test it

ssh -T git@github.com

First connection asks you to verify the host fingerprint. Compare it against GitHub’s published fingerprints before typing yes — this is the one moment a man-in-the-middle would have to get past you.

Success looks like:

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

That message is the goal. It is not an error.

Cloning

# SSH — no credentials to type, ever
git clone git@github.com:owner/repo.git

# HTTPS — will ask for a token
git clone https://github.com/owner/repo.git

# Shallow, for a big repo you only need the tip of
git clone --depth 1 git@github.com:owner/repo.git

# One branch only
git clone --branch main --single-branch git@github.com:owner/repo.git

Already cloned over HTTPS and tired of the prompts? Switch the remote in place:

git remote -v
git remote set-url origin git@github.com:owner/repo.git
git remote -v

HTTPS with a personal access token

Sometimes port 22 is blocked, or you are configuring CI. Then it is tokens.

GitHub has two kinds:

  • Fine-grained — scoped to specific repositories with per-permission control. Use these.
  • Classic — broad scopes, all-or-nothing. Only when something demands them.

Create one under Settings → Developer settings → Personal access tokens. Give it an expiry. For read/write on a repo you need Contents: Read and write.

Use the token as the password when Git asks. Then decide where it lives:

# Held in RAM for 15 minutes. Safe, mildly annoying.
git config --global credential.helper cache

# Longer window
git config --global credential.helper 'cache --timeout=28800'

# Written to ~/.git-credentials — PLAIN TEXT. Convenient and a genuinely bad idea
# on a shared or portable machine.
git config --global credential.helper store

The good option on a desktop Ubuntu is the system keyring, which encrypts at rest:

sudo apt install -y libsecret-1-0 libsecret-1-dev make gcc
sudo make --directory=/usr/share/doc/git/contrib/credential/libsecret
git config --global credential.helper \
  /usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret

SSH when port 22 is blocked

Corporate and hotel networks often block outbound 22. GitHub serves SSH on 443 as well:

Host github.com
    HostName ssh.github.com
    Port 443
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Test with ssh -T git@github.com as before. This is usually faster than migrating everything to HTTPS.

The gh CLI

GitHub’s official CLI handles authentication for you and is genuinely useful for pull requests from the terminal.

sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
  | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null
sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
  | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null

sudo apt update
sudo apt install -y gh

Then:

gh auth login

It walks you through browser or token authentication, and can generate and upload an SSH key for you. Afterwards:

gh repo clone owner/repo
gh pr create --fill
gh pr list
gh pr checkout 42

Deploy keys — the right way to give a server access

Do not copy your personal SSH key onto a VPS. If that box is compromised, the attacker has your whole GitHub account, every repository and organisation you belong to.

A deploy key is scoped to a single repository and read-only by default.

On the server:

ssh-keygen -t ed25519 -C "deploy@myserver" -f ~/.ssh/deploy_myapp -N ""
cat ~/.ssh/deploy_myapp.pub

An empty passphrase is the deliberate choice here: an unattended deploy cannot type one. That is exactly why the key must be per-repository and read-only.

On GitHub, in the repository: Settings → Deploy keys → Add deploy key. Paste, leave “Allow write access” unticked unless you truly need pushes.

There is a dedicated guide for cloning a private repository over SSH that covers this case in more depth, including the one-key-per-repository limit and the “Repository not found” error GitHub returns instead of a permission denial.

Point ssh at it with a host alias:

Host github-myapp
    HostName github.com
    User git
    IdentityFile ~/.ssh/deploy_myapp
    IdentitiesOnly yes

Then clone through the alias:

git clone git@github-myapp:owner/myapp.git

Two GitHub accounts on one machine

Work and personal on the same laptop is the classic case. Give each its own key and its own alias.

Two keys:

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

Upload each .pub to the matching account, then in ~/.ssh/config:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_personal
    IdentitiesOnly yes

Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes

Personal repositories clone normally. Work repositories use the alias:

git clone git@github-work:company/project.git

The remaining trap: commit email. Your global user.email is now wrong half the time. Fix it with conditional includes, so the directory decides:

# ~/.gitconfig
[user]
    name = Your Name
    email = personal@example.com

[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work
# ~/.gitconfig-work
[user]
    email = you@company.com

Everything under ~/work/ now uses the work identity automatically. Note the trailing slash — it is required.

Signing your commits

The “Verified” badge on GitHub means a commit was cryptographically signed. Since Git 2.34 you can sign with the SSH key you already have, which is far less work than GPG.

Ubuntu 20.04 ships Git 2.25 — add the PPA above first, or this will not work.

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

Then add the same public key to GitHub a second time, under Settings → SSH and GPG keys → New SSH key, this time with key type Signing Key. Authentication and signing are separate roles for the same key, and GitHub tracks them separately.

To verify signatures locally, tell Git which keys to trust:

echo "you@example.com $(cat ~/.ssh/id_ed25519.pub)" >> ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
git log --show-signature -1

Troubleshooting

Permission denied (publickey). The usual suspects, in order:

ssh -vT git@github.com     # verbose — shows which key it offered
ssh-add -l                 # is the key even loaded?
ssh-add ~/.ssh/id_ed25519  # load it

If ssh-add -l says “Could not open a connection to your authentication agent”, the agent is not running: eval "$(ssh-agent -s)".

Host key verification failed. The host key changed or was never accepted. Remove the stale entry and reconnect:

ssh-keygen -R github.com
ssh -T git@github.com

Too many authentication failures. ssh is cycling through keys and hitting the server limit. Add IdentitiesOnly yes to the relevant Host block.

Support for password authentication was removed. You are using HTTPS with an account password. Use a token, or move the remote to SSH.

Repository not found on a repository that exists. Usually the wrong identity — a personal key against a work repository. Check what GitHub thinks you are:

ssh -T git@github.com
ssh -T git@github-work

Passphrase asked on every single push. The agent is not persisting. Confirm AddKeysToAgent yes is in ~/.ssh/config; on a desktop session, gnome-keyring normally handles it after the first unlock.

A minimal cheat sheet

# identity
git config --global user.name "Name"
git config --global user.email "you@example.com"

# key
ssh-keygen -t ed25519 -C "you@example.com"
cat ~/.ssh/id_ed25519.pub          # paste into GitHub
ssh -T git@github.com              # verify

# work
git clone git@github.com:owner/repo.git
git switch -c feature/thing
git add -p
git commit -m "message"
git push -u origin feature/thing

Comments

Corrections, additions and "this broke on my machine" reports are all welcome. You can post anonymously — no account needed.