Setting Up Git: SSH Keys and Global Configuration

Setting Up Git: SSH Keys and Global Configuration
A properly configured Git setup saves hours every week. SSH key authentication means no more typing passwords; a good global .gitconfig enforces consistent commit identity, enables useful aliases, and configures sensible defaults. This guide covers everything from generating a key pair to managing multiple GitHub accounts on the same machine.
Why SSH Over HTTPS
HTTPS authentication for Git requires a Personal Access Token (PAT) instead of your password—GitHub removed password auth in 2021. PATs are fine but expire, and you need to store them somewhere or retype them. SSH keys are:
- More convenient: authenticate once with
ssh-agent, then never type a password - More secure: private key never leaves your machine (vs. PAT which can be stolen if stored)
- Revocable per device: each machine has its own key, so revoking one device doesn't affect others
Generating an SSH Key Pair
Ed25519 is the recommended algorithm—smaller, faster, and more secure than RSA 4096.
# Generate an Ed25519 key pair
ssh-keygen -t ed25519 -C "your-email@example.com"
# You'll be prompted:
# Enter file in which to save the key (/home/you/.ssh/id_ed25519): [Enter for default]
# Enter passphrase (empty for no passphrase): [use a strong passphrase]
# Enter same passphrase again: [repeat]
# Result:
# Private key: ~/.ssh/id_ed25519 ↠never share this
# Public key: ~/.ssh/id_ed25519.pub ↠this goes to GitHubAlways use a passphrase. It encrypts the private key on disk. If your machine is stolen, the attacker cannot use the key without the passphrase.
# View your public key (this is safe to share)
cat ~/.ssh/id_ed25519.pub
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... your-email@example.comIf you're on a machine that doesn't support Ed25519 (rare):
# RSA fallback (use 4096 bits minimum)
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"Adding the Key to ssh-agent
ssh-agent holds your decrypted private key in memory so you only enter the passphrase once per session.
# Start ssh-agent if not running
eval "$(ssh-agent -s)"
# Agent pid 1234
# Add your key to the agent
ssh-add ~/.ssh/id_ed25519
# Enter passphrase for /home/you/.ssh/id_ed25519: ***
# Identity added: /home/you/.ssh/id_ed25519
# List keys in agent
ssh-add -l
# 256 SHA256:abc123... your-email@example.com (ED25519)Persist ssh-agent Across Sessions
On Linux (add to ~/.bashrc or ~/.zshrc):
# Auto-start ssh-agent and load keys
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)" > /dev/null
ssh-add ~/.ssh/id_ed25519 2>/dev/null
fiOn macOS (uses system Keychain to persist passphrase):
# Add to ~/.ssh/config
Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
# Add key once (macOS Keychain stores passphrase)
ssh-add --apple-use-keychain ~/.ssh/id_ed25519On Windows (PowerShell, enable ssh-agent service):
# Enable and start the OpenSSH Authentication Agent service
Set-Service -Name ssh-agent -StartupType Automatic
Start-Service ssh-agent
# Add key
ssh-add $env:USERPROFILE\.ssh\id_ed25519Adding Your Key to GitHub
# Copy public key to clipboard
# macOS:
pbcopy < ~/.ssh/id_ed25519.pub
# Linux (requires xclip):
xclip -selection clipboard < ~/.ssh/id_ed25519.pub
# Windows PowerShell:
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | Set-Clipboard
# Or just print and copy manually:
cat ~/.ssh/id_ed25519.pubOn GitHub:
- Settings → SSH and GPG keys → New SSH key
- Title: descriptive name for this device (e.g., "MacBook Pro Work 2025")
- Key type: Authentication Key
- Paste your public key
- Add SSH key
Verify it works:
ssh -T git@github.com
# Hi yourusername! You've successfully authenticated, but GitHub does not provide shell access.Global Git Configuration
# Identity (required — appears in every commit)
git config --global user.name "Your Name"
git config --global user.email "your-email@example.com"
# Default branch name (avoid "master")
git config --global init.defaultBranch main
# Default editor (use your preferred editor)
git config --global core.editor "code --wait" # VS Code
git config --global core.editor "vim" # Vim
git config --global core.editor "nano" # Nano
# Merge strategy: use "merge" or "rebase" when pulling
git config --global pull.rebase false # merge (default)
# git config --global pull.rebase true # rebase (preferred by many teams)
# Push: only push the current branch, not all matching branches
git config --global push.default current
# Diff and merge tool
git config --global diff.tool vscode
git config --global merge.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
# Credential helper (cache credentials for 1 hour)
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600'
# On macOS, use Keychain for HTTPS credentials
git config --global credential.helper osxkeychain
# On Windows, use Git Credential Manager
git config --global credential.helper managerUseful Aliases
# Short status
git config --global alias.st "status -sb"
# Pretty one-line log graph
git config --global alias.lg "log --oneline --graph --decorate --all"
# Last commit details
git config --global alias.last "log -1 HEAD --stat"
# Undo last commit (keep changes staged)
git config --global alias.undo "reset --soft HEAD~1"
# List branches sorted by last commit
git config --global alias.recent "branch --sort=-committerdate"
# Amend without editing message
git config --global alias.amend "commit --amend --no-edit"
# Show changes in last commit
git config --global alias.show-last "diff HEAD~1 HEAD"
# Usage after aliases:
# git st → git status -sb
# git lg → pretty log graph
# git undo → undo last commitYour full .gitconfig lives at ~/.gitconfig:
[user]
name = Your Name
email = your-email@example.com
[init]
defaultBranch = main
[core]
editor = code --wait
autocrlf = input # LF on commit, keep as-is on checkout (Linux/macOS)
# autocrlf = true # Use on Windows: CRLF on checkout, LF on commit
[pull]
rebase = false
[push]
default = current
[alias]
st = status -sb
lg = log --oneline --graph --decorate --all
last = log -1 HEAD --stat
undo = reset --soft HEAD~1
recent = branch --sort=-committerdate
[credential]
helper = osxkeychain # or: manager (Windows), cache (Linux)Multiple GitHub Accounts on One Machine
Common scenario: personal GitHub account and work GitHub account on the same laptop.
Step 1: Generate Separate Keys
# Personal key
ssh-keygen -t ed25519 -C "personal@email.com" -f ~/.ssh/id_ed25519_personal
# Work key
ssh-keygen -t ed25519 -C "work@company.com" -f ~/.ssh/id_ed25519_workAdd both public keys to their respective GitHub accounts.
Step 2: Configure SSH Aliases
# ~/.ssh/config
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
AddKeysToAgent yes
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
AddKeysToAgent yesStep 3: Use the Right Host When Cloning
# Personal repo: use "github-personal" instead of "github.com"
git clone git@github-personal:yourusername/personal-project.git
# Work repo: use "github-work"
git clone git@github-work:company/work-project.git
# Fix an existing repo's remote
git remote set-url origin git@github-work:company/repo.gitStep 4: Per-Directory Git Identity
Use conditional includes to automatically apply work identity in work directories:
# ~/.gitconfig
[user]
name = Your Name
email = personal@email.com
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work# ~/.gitconfig-work
[user]
email = work@company.com
name = Your NameNow any repo under ~/work/ automatically uses the work email in commits.
Signing Commits with GPG
Signed commits prove that commits actually came from you, not someone who got access to your machine.
# Generate a GPG key (if you don't have one)
gpg --full-generate-key
# Choose: ECC and ECC (Ed25519), key does not expire, your name and email
# List your GPG keys
gpg --list-secret-keys --keyid-format=long
# sec ed25519/ABC123DEF456 2024-01-01 [SC]
# Export public key and add to GitHub (Settings → GPG keys)
gpg --armor --export ABC123DEF456
# Configure Git to use your key
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true # sign all commits
git config --global tag.gpgsign true # sign all tagsVerify a signed commit:
git verify-commit HEAD
# gpg: Good signature from "Your Name <email@example.com>"Frequently Asked Questions
Q: My ssh -T git@github.com says "Permission denied (publickey)". How do I debug?
Run ssh -vT git@github.com for verbose output. Common causes: (1) The key is not added to ssh-agent — run ssh-add ~/.ssh/id_ed25519. (2) The public key wasn't added to GitHub, or was added to the wrong account. (3) The ~/.ssh/config file has wrong permissions — it must be 600: chmod 600 ~/.ssh/config. (4) You're using the HTTPS URL instead of SSH — verify with git remote -v and change with git remote set-url origin git@github.com:user/repo.git.
Q: Should I use the same SSH key on every machine or generate one per machine?
Generate one key per machine. This follows the principle of least privilege: if one machine is compromised, you revoke only that key without affecting others. It also gives you an audit trail on GitHub—you can see which device was used for authentication. Name your keys descriptively in GitHub: "MacBook Pro Work 2025", "Home Desktop", "CI Server".
Q: What is core.autocrlf and should I set it?
Windows uses CRLF (\r\n) line endings; Linux/macOS use LF (\n). autocrlf = true (Windows) converts LF→CRLF on checkout and CRLF→LF on commit, keeping your editor happy while keeping the repo LF. autocrlf = input (Linux/macOS) converts CRLF→LF on commit only. Teams with mixed OS contributors should also add a .gitattributes file to enforce LF for all text files regardless of OS: * text=auto eol=lf.
Q: I accidentally committed with the wrong email address. How do I fix it?
For the most recent commit: git commit --amend --reset-author (updates the author to match your current config). For multiple commits before pushing: git rebase -i HEAD~N then git commit --amend --reset-author for each. For commits already pushed to a shared branch: this requires a force-push and coordination with your team since it rewrites history. Going forward, use includeIf in .gitconfig to prevent this automatically.
