SSH Deploy Keys: The Complete Guide I Wish I Had on Day One
Jenkins
Source: Dev.to | Jan 24, 2026 (UTC)
How I learned to stop worrying and love the Permission denied (publickey) error
If you’ve ever stared at Permission denied (publickey) and wondered if you’re the only one who finds SSH confusing — you’re not, and this is for you.
This isn’t just another “here’s a script” tutorial. This is the war story, the mental models, the troubleshooting guide, and the production-ready setup I built after fighting with SSH for [however long you fought with it]. Everything you’re about to read came from real failures, real confusion, and real 2 a.m. debugging sessions.
The Ultimate Goal
What we’re building:
A system where your VPS can clone private GitHub repos without ever having your personal credentials, using deploy keys that are scoped to exactly one repository.
Why it matters:
Your personal SSH keys stay on your laptop (where they belong)
Each project gets its own isolated deploy key
If one server is compromised, only that one repo is exposed
You can revoke access without touching anything else
This is how professionals do it. And now you will too.
The Three Foundations You Need to Understand
Before we get to the script, let’s build the mental model. SSH confusion usually comes from not understanding these three things:
1. Root vs User: Who Is Actually Trying to Connect?
When you run git clone as root, SSH looks for keys in /root/.ssh/.
When you run it as deployuser, SSH looks in /home/deployuser/.ssh/.
The failure I had:
I created keys as root, then tried to use them as a regular user. SSH couldn’t find them because it was looking in a completely different directory.
The fix:
Always create and use SSH keys as the user who will actually run git clone.
2. Deploy Keys vs Personal Keys: Different Tools for Different Jobs
Type
Purpose
Scope
Where It Lives
Personal SSH Key
Your identity across all of GitHub
All repos you have access to
Your laptop
Deploy Key
Server’s identity for ONE repo
Single repository only
The VPS
The failure I had:
I tried to use my personal key on the server. This works, but it’s dangerous — if the server is compromised, the attacker gets access to everything you can access on GitHub.
The fix:
Generate a fresh SSH key on the server, add it as a deploy key to only the repo you need, and never let it leave that server.
3. SSH Config: The Map That Tells SSH Which Key to Use
SSH doesn’t magically know which key to use for which host. You have to tell it.
The ~/.ssh/config file is like a phonebook:
Host github.com-myproject HostName github.com User git IdentityFile ~/.ssh/deploy_key_myproject IdentitiesOnly yes
Enter fullscreen mode
Exit fullscreen mode
This says:
“When you see github.com-myproject in a git URL, use the key at ~/.ssh/deploy_key_myproject“
That -myproject part is the “Host” name from your config. It’s how SSH knows which key to grab.
The failure I had:
I created the config but forgot to use the custom host name in the clone URL. SSH fell back to looking for id_rsa, which didn’t exist, and I got Permission denied.
The fix:
Always match your clone URL to the Host name in your SSH config.
The Problems I Actually Hit (And How I Fixed Them)
Problem 1: Permission denied (publickey)
Why it happens:
SSH can’t find a valid key, or the key you have isn’t authorized on GitHub.
You ran the setup as root but your deploy script runs as a regular user, or vice versa.
The fix:
Consistency. Decide who owns the deployment process (usually a dedicated user like deployuser) and do everything as that user.
The Production-Ready Setup Script
This is what I wish I had on day one. Save this, use it on every new server.
#!/bin/bash # SSH Deploy Key Setup Script # Run this as the user who will deploy code (NOT as root) set -e # Exit on any error echo "=== SSH Deploy Key Setup ===" echo "" # 1. Get repo details read -p "Enter your GitHub username: " GITHUB_USER read -p "Enter the repository name: " REPO_NAME read -p "Enter a name for this deploy key (e.g., myproject): " KEY_NAME # 2. Set paths SSH_DIR="$HOME/.ssh" KEY_PATH="$SSH_DIR/deploy_key_$KEY_NAME" CONFIG_PATH="$SSH_DIR/config" # 3. Create .ssh directory if it doesn't exist mkdir -p "$SSH_DIR" chmod 700 "$SSH_DIR" # 4. Generate SSH key echo "" echo "Generating SSH key..." ssh-keygen -t ed25519 -C "deploy-$KEY_NAME@$(hostname)" -f "$KEY_PATH" -N "" # 5. Set correct permissions chmod 600 "$KEY_PATH" chmod 644 "$KEY_PATH.pub" # 6. Add to SSH config echo "" echo "Adding to SSH config..." cat >> "$CONFIG_PATH" << EOF # Deploy key for $REPO_NAME Host github.com-$KEY_NAME HostName github.com User git IdentityFile $KEY_PATH IdentitiesOnly yes EOF chmod 600 "$CONFIG_PATH" # 7. Display the public key echo "" echo "==========================================" echo "PUBLIC KEY (add this to GitHub):" echo "==========================================" cat "$KEY_PATH.pub" echo "==========================================" echo "" echo "Next steps:" echo "1. Copy the public key above" echo "2. Go to: https://github.com/$GITHUB_USER/$REPO_NAME/settings/keys" echo "3. Click 'Add deploy key'" echo "4. Paste the key and give it a title" echo "5. Check 'Allow write access' if you need to push" echo "" echo "Then test with:" echo " ssh -T [email protected]$KEY_NAME" echo "" echo "And clone with:" echo " git clone [email protected]$KEY_NAME:$GITHUB_USER/$REPO_NAME.git" echo ""
Enter fullscreen mode
Exit fullscreen mode
How to use it:
# Download and save as setup-deploy-key.sh chmod +x setup-deploy-key.sh # Run as the deploy user (NOT root) ./setup-deploy-key.sh
Enter fullscreen mode
Exit fullscreen mode
The 2 A.M. Debugging Checklist
When something breaks and you can’t think straight, run through this:
□ Am I running as the correct user? → Check with: whoami → Keys should be in /home/USERNAME/.ssh, not /root/.ssh □ Does the SSH key exist? → ls -la ~/.ssh/deploy_key_* □ Are permissions correct? → chmod 700 ~/.ssh → chmod 600 ~/.ssh/deploy_key_* → chmod 600 ~/.ssh/config □ Is the public key added to GitHub? → Check: github.com/USERNAME/REPO/settings/keys □ Does the Host name in SSH config match my clone URL? → Config says: Host github.com-myproject → Clone URL must use: [email protected]:user/repo.git □ Can SSH actually connect? → Test: ssh -vvv [email protected] → Look for "Authentication succeeded" □ If pushing fails, did I check "Allow write access"? → GitHub → Repo → Settings → Deploy Keys → Edit □ Are there other keys interfering? → Check: ssh-add -l → If needed, clear: ssh-add -D → Config should have: IdentitiesOnly yes
Enter fullscreen mode
Exit fullscreen mode
The Mental Model: Three Pieces, One System
This whole setup is just three pieces working together:
SSH Key Pair (private + public)
→ Lives in ~/.ssh/deploy_key_projectname
→ Your “muscle memory” — create one per repo
SSH Config (the map)
→ Lives in ~/.ssh/config
→ Your “compass” — tells SSH which key to use
GitHub Deploy Key (the authorization)
→ Lives on GitHub’s website
→ Your “permission slip” — lets that key access the repo
When all three are aligned:
SSH key exists → Config points to it → GitHub recognizes it → Clone works
Enter fullscreen mode
Exit fullscreen mode
When something breaks:
One of these three is missing or misaligned. The checklist above helps you find which one.
What’s Next
Now that you have the foundation, you can extend this to:
CI/CD pipelines — Same concept, but keys live in GitHub Actions secrets
Multiple environments — One deploy key per environment (staging, production)
Automated deployments — Combine this with systemd, cron, or webhook listeners
Full server bootstrap — Script that creates users, sets up SSH, installs Git, and configures everything
But first, test this setup. Break it on purpose. Re-run the script. Get comfortable with the debugging checklist.
You didn’t mess anything up by hitting these errors — you just ran into real infrastructure reality earlier than most people. This is how engineers are made.
Залишити відповідь