SSH Deploy Keys: The Complete Guide I Wish I Had on Day One

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“

Then you clone like this:

git clone [email protected]:yourusername/your-repo.git

Enter fullscreen mode

Exit fullscreen mode

Notice the difference:

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.

How to debug it:

ssh -vvv [email protected]

Enter fullscreen mode

Exit fullscreen mode

Look for lines like:

Common causes:

  1. You’re using the wrong user (root vs deployuser)
  2. The deploy key isn’t added to GitHub
  3. The SSH config Host name doesn’t match your clone URL
  4. File permissions are wrong (keys must be 600, .ssh must be 700)

Problem 2: Keys Work, But Still Can’t Clone

Why it happens:

The deploy key is added to GitHub, but with read-only access, and you’re trying to push.

The fix:

When adding the deploy key on GitHub, check the box for “Allow write access” if you need to push from the server.

Problem 3: Multiple Repos, Keys Colliding

Why it happens:

You have two repos, both on GitHub, and SSH doesn’t know which key to use.

The fix:

Create separate SSH config blocks for each repo:

Host github.com-project1 HostName github.com User git IdentityFile ~/.ssh/deploy_key_project1 IdentitiesOnly yes Host github.com-project2 HostName github.com User git IdentityFile ~/.ssh/deploy_key_project2 IdentitiesOnly yes

Enter fullscreen mode

Exit fullscreen mode

Then clone with:

git clone [email protected]:user/repo1.git git clone [email protected]:user/repo2.git

Enter fullscreen mode

Exit fullscreen mode

Problem 4: It Works Once, Then Breaks Later

Why it happens:

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:

  1. SSH Key Pair (private + public)

    → Lives in ~/.ssh/deploy_key_projectname

    → Your “muscle memory” — create one per repo

  2. SSH Config (the map)

    → Lives in ~/.ssh/config

    → Your “compass” — tells SSH which key to use

  3. 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:

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.

Quick Reference

Create a deploy key:

ssh-keygen -t ed25519 -C "deploy@server" -f ~/.ssh/deploy_key_project

Enter fullscreen mode

Exit fullscreen mode

Add to SSH config:

Host github.com-project HostName github.com User git IdentityFile ~/.ssh/deploy_key_project IdentitiesOnly yes

Enter fullscreen mode

Exit fullscreen mode

Test connection:

ssh -T [email protected]

Enter fullscreen mode

Exit fullscreen mode

Clone with deploy key:

git clone [email protected]:username/repo.git

Enter fullscreen mode

Exit fullscreen mode

Got questions? Hit me in the comments. Got corrections? Even better — I’m still learning this stuff too.

For further actions, you may consider blocking this person and/or reporting abuse

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Читайте також