A server that needs to pull the latest code from several GitHub repos on its own, with no one around to type a password, is a bad place to keep a Personal Access Token. A PAT is scoped to the whole account (or needs fine-grained setup anyway), the leak risk is higher, and rotating it is a hassle. A deploy key fits better: it’s read-only, scoped to a single repo, and dies with the repo if removed, so the blast radius of a compromised server stays small.
For a handful of repos (under 10 or so), generate a separate keypair per repo rather than reusing one key everywhere - a reused key still works, but a leak exposes every repo it’s attached to. No passphrase is needed since the pull runs unattended, and that’s fine here because each key is already restricted to one repo, read-only, on a server that’s trusted to run the pull.
Generate a keypair per repo, named so it’s obvious which repo it belongs to:
ssh-keygen -t ed25519 -C "deploy-repo1" -f ~/.ssh/deploy_repo1 -N ""
ssh-keygen -t ed25519 -C "deploy-repo2" -f ~/.ssh/deploy_repo2 -N ""
# repeat for each repo
Add each public key to its repo under Settings > Deploy keys > Add deploy key, leaving “Allow write access” unchecked.
Point git at the right key per repo with a host alias in ~/.ssh/config, one block per repo:
Host repo1.github.com
HostName github.com
User git
IdentityFile ~/.ssh/deploy_repo1
IdentitiesOnly yes
Host repo2.github.com
HostName github.com
User git
IdentityFile ~/.ssh/deploy_repo2
IdentitiesOnly yes
IdentitiesOnly yes matters here - without it, SSH may offer other loaded keys before falling back to the one in the config, which against multiple deploy-key-restricted repos leads to auth failures or GitHub logging a pile of rejected key attempts.
Clone using the alias instead of the real github.com host:
git clone [email protected]:youruser/repo1.git
git clone [email protected]:youruser/repo2.git
For repos already checked out, just swap github.com for the alias in the existing remote URL:
git remote set-url origin [email protected]:youruser/repo1.git
Lock down the key files so SSH doesn’t complain about permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/deploy_repo1 ~/.ssh/deploy_repo2
This per-repo approach stops being the right fit once the repo count grows into the dozens under the same org - at that point a GitHub App with contents:read on selected repos, exchanged for a short-lived installation token before each pull, gives one credential to manage and per-repo access toggled centrally, at the cost of registering the app and scripting the token exchange. For a handful of repos, the per-repo deploy key setup above is simpler and avoids that extra machinery.
Verify each alias resolves to the right key before relying on it in an automated job:
ssh -T [email protected]
A successful connection replies with a message confirming the GitHub username tied to that deploy key, after which git pull in that repo’s directory should succeed without any prompt.