RFD 0005Document view
Catalog
RFD 0005
draftmdx0005.mdx

SSH Config Setup for GitHub on macOS

A practical baseline for configuring ~/.ssh/config and Apple Keychain so GitHub SSH auth works cleanly for humans, agents, and harnesses.

Authors
Ian Cleary
Updated
Apr 21, 2026, 12:00 AM

Summary

This RFD documents a simple baseline for reliable GitHub SSH authentication on macOS. It complements the broader macOS workflow guidance in 0003 and the Windows workflow guidance in 0004.

The determination is to:

  • use a dedicated GitHub SSH key,
  • configure github.com explicitly in ~/.ssh/config,
  • enable AddKeysToAgent,
  • enable UseKeychain,
  • enable IdentitiesOnly,
  • add the key to Apple Keychain once with ssh-add --apple-use-keychain.

This is the preferred baseline because it makes GitHub SSH behavior deterministic across:

  • interactive terminals,
  • fresh shells,
  • editor-integrated workflows,
  • agent-driven Git operations,
  • harnesses and automation that may not load shell startup files.

Problem

GitHub SSH failures often come from configuration living in the wrong place.

Common symptoms include:

  • Permission denied (publickey) even though a valid key exists,
  • Git working in one terminal but not another,
  • automation behaving differently from interactive shells,
  • the wrong identity being offered first,
  • fixes depending on .zshrc, .zprofile, or similar shell startup files.

These problems become more visible when commands are launched by agents, IDE task runners, or automation harnesses. Those environments may not execute the same shell initialization path as an interactive terminal, so any setup that depends on startup-time ssh-add calls is inherently brittle.

Constraints and goals

The desired setup should:

  • work for normal human terminal usage,
  • work for non-interactive Git and SSH invocations,
  • choose the intended GitHub key explicitly,
  • avoid depending on shell-profile side effects,
  • keep passphrase handling practical on macOS,
  • be easy to inspect and debug later.

Applied configuration

The GitHub SSH key is assumed to live at:

TEXT
~/.ssh/github_id_ed25519
~/.ssh/github_id_ed25519.pub

If a dedicated key does not already exist, create one:

BASH
ssh-keygen -t ed25519 -C "iancleary@hey.com" -f ~/.ssh/github_id_ed25519

~/.ssh/config should contain:

TEXT
Host github.com
IdentityFile ~/.ssh/github_id_ed25519
AddKeysToAgent yes
UseKeychain yes
IdentitiesOnly yes

The config file should also have restricted permissions:

BASH
chmod 600 ~/.ssh/config

Why these settings

  • IdentityFile ~/.ssh/github_id_ed25519
    • binds GitHub auth to the intended private key.
  • AddKeysToAgent yes
    • allows SSH to register the key with the agent after first use.
  • UseKeychain yes
    • stores the passphrase in Apple Keychain on macOS.
  • IdentitiesOnly yes
    • prevents SSH from trying unrelated identities first.

IdentitiesOnly yes is especially important on machines with multiple SSH keys. Without it, authentication can fail simply because the client offers the wrong keys before it reaches the correct one.

Why this is better for agents and harnesses

Agent and harness reliability improves when SSH behavior is host-scoped and configuration-driven instead of shell-driven.

Examples of fragile setups include:

  • an agent launching git fetch in a non-interactive shell,
  • an IDE spawning Git without a login shell,
  • a harness inheriting a different agent state than the user’s main terminal,
  • a machine carrying multiple identities with no explicit host mapping.

By moving the durable behavior into ~/.ssh/config, GitHub key selection becomes explicit and repeatable. By using Apple Keychain integration, the passphrase is available without requiring every execution environment to bootstrap ssh-add manually.

Enrollment step

After the SSH config is in place, add the key to Apple Keychain once:

BASH
ssh-add --apple-use-keychain ~/.ssh/github_id_ed25519

Expected output:

TEXT
Identity added: /Users/iancleary/.ssh/github_id_ed25519 (iancleary@hey.com)

Verification

Direct GitHub SSH verification:

BASH
ssh -T git@github.com

Expected result:

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

That message confirms authentication succeeded.

To inspect the resolved SSH config for GitHub:

BASH
ssh -G github.com | grep -E 'identityfile|identitiesonly|addkeystoagent|usekeychain'

Additional high-leverage configuration notes

A few SSH behaviors are worth making explicit because they matter for reliability:

Config precedence and ordering

Per ssh_config(5), SSH reads configuration in this order:

  1. command-line options,
  2. user config (~/.ssh/config),
  3. system config (/etc/ssh/ssh_config).

It also uses the first value obtained for a given parameter. In practice, that means:

  • put host-specific rules near the top,
  • put broad defaults later,
  • avoid accidentally overriding a specific GitHub stanza with a wildcard stanza.

Use ssh -G to inspect effective config

When debugging agent or harness behavior, do not guess which config is winning.

Use:

BASH
ssh -G github.com

This is one of the fastest ways to confirm the resolved identityfile, identitiesonly, and related settings.

Use BatchMode yes for non-interactive harnesses

Per ssh_config(5), BatchMode yes disables interactive prompts such as password and host-key confirmation prompts. That is useful for scripts, agents, and harnesses where waiting for a hidden prompt is worse than failing fast.

A per-host example:

TEXT
Host github.com
IdentityFile ~/.ssh/github_id_ed25519
AddKeysToAgent yes
IdentitiesOnly yes
BatchMode yes

This is most appropriate for automation contexts, not necessarily for every interactive workflow.

Use SetEnv TERM=xterm-256color only on the host that needs it

If a single SSH target needs a specific terminal type, set it only for that host:

TEXT
Host example-host
SetEnv TERM=xterm-256color

This is a good pattern because it changes only the connection that actually needs the override. It avoids broad terminal-environment changes that might affect unrelated hosts or local shell behavior.

If you want to set the terminal type for all SSH hosts, use:

TEXT
Host *
SetEnv TERM=xterm-256color

That broader form is useful when you want the same terminal type everywhere, but the per-host form is still preferable when only one target needs the override.

As with other host-scoped SSH settings, this keeps the configuration explicit, narrow, and easy to inspect later with ssh -G <host>.

Windows

For this workflow, assume Git Bash.

That assumption simplifies the model substantially, because the main Windows reliability problem is split SSH environments. If you test in Git Bash but an agent or harness runs in PowerShell, cmd, or WSL, you may be exercising different binaries, agents, config files, and key stores.

With Git Bash as the standard environment, the same core SSH ideas apply:

  • use a dedicated GitHub key,
  • use a per-user SSH config,
  • load keys into an agent,
  • make GitHub host selection explicit,
  • verify from Git Bash itself.

High-leverage Windows-specific notes:

  • GitHub documents Windows-specific SSH onboarding and notes that some environments may surface an ssh-add illegal option message when macOS-specific flags are used.
  • If Git Bash is the chosen shell, treat it as the source of truth for both manual verification and harness behavior whenever possible.
  • Reliability improves when the harness uses the same SSH client family and config path that you use interactively.

Practical baseline for Windows with Git Bash:

  • keep keys under %USERPROFILE%\.ssh\,
  • use %USERPROFILE%\.ssh\config for host-specific rules,
  • verify with ssh -T git@github.com from Git Bash,
  • avoid mixing validation across Git Bash, PowerShell, and WSL unless you intentionally support all of them.

The biggest Windows footgun is still split environments. If the automation path does not use Git Bash semantics, SSH may look flaky even when the Git Bash setup itself is correct.

Linux

On Linux, the client configuration model is the same as on macOS because it is standard OpenSSH:

  • ~/.ssh/config remains the primary per-user client config,
  • IdentityFile, IdentitiesOnly, AddKeysToAgent, and BatchMode work the same way,
  • ssh_config(5) remains the reference for client behavior.

The main distribution-level differences are usually about how the SSH agent is started and persisted.

Ubuntu

Ubuntu’s server documentation emphasizes modular SSH configuration via:

  • /etc/ssh/sshd_config,
  • /etc/ssh/sshd_config.d/*.conf.

That is primarily server-side guidance, but the broader lesson is useful: keep configuration explicit and inspectable.

For client-side GitHub usage on Ubuntu, the usual baseline is:

  • keep host-specific client rules in ~/.ssh/config,
  • use ssh-agent or a desktop keyring/session agent,
  • verify with ssh -T git@github.com,
  • prefer config-driven key selection over shell-local workarounds.

Arch

The ArchWiki has especially useful client-side guidance for SSH keys:

  • AddKeysToAgent yes can be set in ~/.ssh/config so clients such as Git store keys in the agent on first use,
  • IdentitiesOnly yes plus IdentityFile is recommended when managing multiple keys,
  • Arch documents both traditional ssh-agent usage and a systemd --user approach using ssh-agent.service and SSH_AUTH_SOCK.

That makes Arch a good model for agent-aware setups where you want SSH behavior to persist across terminals without depending on ad hoc shell snippets.

Alpine

Alpine uses the same OpenSSH client semantics, but it is often deployed in more minimal environments. In practice that means:

  • fewer desktop keyring conveniences by default,
  • more cases where you explicitly start ssh-agent,
  • more cases where shell or container lifecycle determines whether agent state persists.

Alpine’s wiki guidance around SSH focuses more on OpenRC and server management than on client convenience. So for GitHub client auth on Alpine, the high-leverage pattern is usually to rely on standard OpenSSH client config in ~/.ssh/config, then decide explicitly how agent state should be initialized in that environment.

What not to rely on

Avoid treating these as the primary solution:

  • repeated ssh-add commands in .zshrc, .zprofile, or .bashrc,
  • implicit key selection when multiple identities exist,
  • debugging Git before testing ssh -T git@github.com directly,
  • environment-specific fixes that only work in one terminal flavor.

Those approaches may appear to work temporarily, but they increase variance across tools and make the machine harder to reason about.

Troubleshooting

Permission denied (publickey)

Usually means one of:

  • the wrong key is configured,
  • the public key is not uploaded to GitHub,
  • the private key path is wrong,
  • IdentitiesOnly yes is missing,
  • file permissions are too loose.

Useful checks:

BASH
ls -l ~/.ssh
ssh -T -v git@github.com

No ~/.ssh/config file exists

That is fine. Create it and restrict permissions:

BASH
touch ~/.ssh/config
chmod 600 ~/.ssh/config

GitHub works in one environment but not another

That is usually evidence that the setup depends on shell initialization behavior instead of SSH client configuration. Move the durable logic into ~/.ssh/config and Keychain-backed key enrollment.

Security notes

  • Use a dedicated key for GitHub instead of a general-purpose SSH key.
  • Protect the private key with a passphrase.
  • Prefer Keychain-backed passphrase storage over plaintext workarounds.
  • Keep ~/.ssh/config permissions restricted.

Determination

For macOS GitHub development environments, the default SSH baseline should be:

  • explicit Host github.com configuration in ~/.ssh/config,
  • a dedicated GitHub key referenced with IdentityFile,
  • AddKeysToAgent yes,
  • UseKeychain yes,
  • IdentitiesOnly yes,
  • one-time enrollment with ssh-add --apple-use-keychain.

This approach is simpler, more reproducible, and more reliable for both humans and automation than shell-startup-based workarounds.

Future expansion

This draft could later expand to cover:

  • multiple GitHub accounts,
  • Linux and Windows equivalents,
  • Include-based SSH config layouts,
  • interaction with dotfiles and bootstrap scripts,
  • additional guidance for CI, remote dev shells, and agent harnesses.
RFD 0005 · SSH Config Setup for GitHub on macOS