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.
