| [ Web Proxy ] |
| Viewing: https://smallstep.com/docs/tutorials/protect-github-ssh/ | [Back] [Original] |
Sign up for our Device Identity Webinar Series!
This guide shows how to protect access to your GitHub organization's repositories with Smallstep, using SSH certificates issued to hardware-bound keys.
By configuring GitHub to trust an SSH Certificate Authority (CA),
you can use the Smallstep Agent to manage SSH credentials
that are protected by your device's native security hardwarea TPM on Windows and Linux systems, or the Secure Enclave on macOS devices.
Private keys never leave the secure hardware,
and the agent integrates with your git client through a standard SSH agent socket,
so developers authenticate without managing SSH keys at all.
SSH certificate authorities are a GitHub Enterprise feature. You'll need a GitHub Enterprise Cloud organization (or GitHub Enterprise Server) to complete this guide.
You will need:
Smallstep issues each user an SSH certificate whose principals must include the user's GitHub usernamethat's how GitHub maps a certificate to a GitHub account.
To make the username available, configure your identity provider to sync users to Smallstep
with a custom githubUsername attribute.
First, sync Okta users to Smallstep via SCIM provisioning, if you haven't already.
Then add the GitHub username attribute:
githubUsernamegithubUsernamegithubUsernameurn:scim:smallstep:ssh:schemapersonalgithubUsernameUser sync is available for Google Workspace and Entra ID. For help mapping a GitHub username attribute from these providers, contact us.
Create a credential that tells the Smallstep Agent to issue a hardware-bound SSH user certificate to each of your devices.
You will need an API token. See Smallstep API for details.
Store your API token in a headers file for curl:
set +o history
echo "Authorization: Bearer [your API token]" > api_headers
set -o history
Find the authority that should issue your SSH certificates with the List Authorities endpoint:
curl -sH @api_headers --request GET \
--url https://gateway.smallstep.com/api/authorities \
--header 'Accept: application/json' \
--header 'x-smallstep-api-version: 2025-01-01' | jq '.[] | {id, name, domain}'
Then create the credential with the Create Credential endpoint:
curl -sH @api_headers --request POST \
--url https://gateway.smallstep.com/api/credentials \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'x-smallstep-api-version: 2025-01-01' \
--data @- <<'EOF' | jq
{
"slug": "github",
"certificate": {
"type": "SSH_USER",
"authorityID": "[your authority ID]",
"fields": {
"keyId": {
"deviceMetadata": "smallstep:identity",
"static": "github"
},
"principals": {
"deviceMetadata": ["SSH.Principals", "smallstep:identity"]
}
}
},
"key": {
"protection": "HARDWARE_ATTESTED"
},
"managementMode": "agent",
"policy": {
"assurance": ["high"],
"operatingSystem": ["macOS", "Windows", "Linux"]
}
}
EOF
Here's what the fields mean:
certificate.type: "SSH_USER" issues SSH user certificates instead of X.509 certificates.fields.keyId sets the certificate's key ID, used for identification and audit logging.fields.principals populates the certificate's principals from device metadata.
SSH.Principals includes the user's SSH principals synced from your identity providerincluding the githubUsername attribute you configured aboveand smallstep:identity adds the email address of the user assigned to the device.key.protection: "HARDWARE_ATTESTED" generates the private key in the device's
secure hardware (TPM or Secure Enclave), where it can never be exported.policy selects the devices that receive this credential;
here, high-assurance macOS, Windows, and Linux devices.Once this credential is created, the Smallstep Agent on matching devices automatically requests and renews hardware-bound SSH certificates with the appropriate GitHub username as a principal.
The enforcement point is GitHub itself: your GitHub Enterprise organization is configured to trust SSH certificates signed by your Smallstep SSH CA.
On a machine with the step CLI installed,
bootstrap with your team and print your SSH user CA public key:
step ssh config --team [your team slug]
step ssh config --roots
The output is your SSH CA's public key. Copy the entire key; it will look similar to:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK...
step ssh config --roots into the Key fieldThe CA now appears in your list of SSH certificate authorities, and any SSH certificate signed by your Smallstep CA with a valid GitHub username principal will be accepted for authentication to your organization's repositories.
For more information, see GitHub's documentation on SSH certificate authorities.
GitHub Enterprise lets you require SSH certificates for all Git access to your organization's repositories, which blocks personal SSH keys entirely.
Only enable this setting once you have fully migrated all of your clients, remotes, and CI systems to SSH certificates.
Certificate authentication uses a special SSH login of the form org-[your org ID]@github.comthe usual git@github.com login is not compatible.
See Migrate your remotes and submodules below.
Client configuration for SSH is handled by the Smallstep Agentthere is no MDM-managed path for SSH credentials.
The agent exposes an SSH agent socket that your git and ssh clients use for authentication,
so the only client-side setup is pointing SSH_AUTH_SOCK at it.
The agent's SSH socket location per platform:
| Platform | SSH agent socket |
|---|---|
| macOS | ~/Library/Application Support/Smallstep/step-agent-ssh.sock |
| Linux | /run/step-agent/step-agent-ssh.sock |
| Windows | \\.\pipe\step-agent-ssh |
The steps below make the Smallstep Agent's keys your only SSH agent keys.
For gradual adoption, you can instead scope the agent to specific projects:
tools like direnv can set SSH_AUTH_SOCK per directory,
and git supports conditional configuration with
[includeIf "env:SSH_AUTH_SOCK=..."] directives.
Add the following to your shell configuration file (~/.zshrc for zsh, or ~/.bash_profile for bash):
export SSH_AUTH_SOCK="$HOME/Library/Application Support/Smallstep/step-agent-ssh.sock"
Reload your shell configuration with source ~/.zshrc (or source ~/.bash_profile).
When the agent runs under systemd, add the following to your ~/.bashrc or ~/.zshrc:
export SSH_AUTH_SOCK="/run/step-agent/step-agent-ssh.sock"
Reload your shell configuration with source ~/.bashrc (or source ~/.zshrc).
Set the environment variable for your user:
Win + X and select SystemSSH_AUTH_SOCK\\.\pipe\step-agent-sshConfirm the agent's certificate is available:
ssh-add -l
You should see your hardware-bound SSH certificate listed.
Next, find your GitHub organization's database IDcertificate authentication uses an org-[ID]@github.com login instead of git@github.com:
gh api /orgs/[your org name] --jq '.id'
Then test authentication:
ssh -T org-[your org ID]@github.com
You should receive a success message confirming your authentication.
The usual git@github.com SSH login is not compatible with SSH certificate authentication.
Update each clone's remotes (and any submodule URLs) to use the org-[ID]@github.com login:
git remote set-url origin org-[your org ID]@github.com:[your org]/[repo].git
To rewrite all GitHub remotes for your organization without touching each repository, you can add a global rewrite rule instead:
git config --global url."org-[your org ID]@github.com:[your org]/".insteadOf "git@github.com:[your org]/"
You can configure git to sign commits with the same hardware-protected SSH credential,
providing cryptographic proof of commit authorship.
Tell Git to use SSH for signing, and set your signing key:
git config --global gpg.format ssh
step ssh list --raw | head -1 | step crypto key format --ssh
git config --global user.signingkey "[your SSH public key]"
Configure allowed signers, so git can verify signatures locally:
echo '[certificate principal] namespaces="git" [key type] [base64 public key]' > ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile "~/.ssh/allowed_signers"
Sign commits with git commit -S, or enable signing for all commits:
git config --global commit.gpgsign true
Verify with a test commit:
git commit --allow-empty -S -m "Test signed commit"
git log --show-signature -1
To get the Verified badge on GitHub, add your SSH public key to your GitHub profile as a signing key:
gh ssh-key add [key file] --type signing --title "Smallstep"
For more, see GitHub's documentation on commit signature verification.
org-[ID]@github.com remote.ssh -vT org-[your org ID]@github.com and look for the certificate being offered during authentication.ssh-add -l to confirm the agent socket is reachable and a certificate is loaded.
If nothing is listed, confirm the device matches the credential's policy
(assurance level, operating system, and tags),
and see the agent troubleshooting guide.Last updated on July 8, 2026
Introducing
Ensure that only company-owned devices can access your enterprise's most sensitive resources.
Device Identity for companies with sensitive resources. Secure corporate Wi-Fi, SaaS Apps, VPNs, and more.
[AICPA SOC compliance logo]Device Identity for companies with sensitive resources. Secure corporate Wi-Fi, SaaS Apps, VPNs, and more.
[AICPA SOC compliance logo]| Web Proxy Viewer | New URL | Original Page |