How to Fix SSH Key Permissions Error When Using Shared Deployment Keys in Git Hooks


2 views

In collaborative development environments where multiple users need to trigger deployments through Git hooks, we often face a permissions dilemma. The standard security model of SSH keys clashes with practical deployment requirements when:

  • Multiple developers need to push to a shared Git repository
  • The post-receive hook must deploy using a common identity
  • Traditional authorized_keys management becomes unwieldy

SSH enforces strict permissions on private keys for good reason. When you see:

Permissions 0640 for 'id_rsa' are too open.
It is required that your private key files are NOT accessible by others.

This is OpenSSH enforcing that private keys must have 600 permissions (rw-------). The security model assumes private keys should only be accessible by their owner.

Option 1: The SSH Agent Approach

Forward an agent with the deployment key pre-loaded:

# In your post-receive hook:
eval $(ssh-agent)
ssh-add /path/to/deployment-key
export GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -F /dev/null"

# Then perform your Git operations
git push live

Option 2: Permission Wrapper Script

Create a setuid wrapper that temporarily adjusts permissions:

#!/bin/bash
# save as /usr/local/bin/ssh_deploy_wrapper

ORIG_PERMS=$(stat -c "%a" "$2")
chmod 600 "$2"
ssh -i "$2" "$@"
chmod $ORIG_PERMS "$2"

Then use it in your hook:

export GIT_SSH="/usr/local/bin/ssh_deploy_wrapper"

Option 3: SSH Config Override

Create a dedicated SSH config for deployment:

Host deploy-target
  HostName your.server.com
  User deploy-user
  IdentityFile /path/to/deployment-key
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
  IdentitiesOnly yes

Then set permissions:

sudo chown root:gitusers /path/to/deployment-key
sudo chmod 640 /path/to/deployment-key
sudo chown root:gitusers /etc/ssh/deploy_config
sudo chmod 640 /etc/ssh/deploy_config

For larger teams, consider a small HTTP service that handles deployments:

# deploy_proxy.py
from flask import Flask
import subprocess

app = Flask(__name__)

@app.route('/deploy', methods=['POST'])
def deploy():
    # Validate request
    if valid_request(request):
        subprocess.run([
            'ssh', 
            '-i', '/secure/keys/deploy_key',
            'deploy@target',
            'update-script.sh'
        ])
        return "Deployment triggered", 200
    return "Unauthorized", 403

This moves the permission problem to the service account running the proxy.

When working around SSH's permissions model:

  • Never store deployment keys in world-readable locations
  • Rotate deployment keys regularly
  • Consider using certificates instead of raw keys
  • Implement proper logging for all deployment actions

The best approach depends on your specific infrastructure and security requirements. For most teams, the SSH agent or config override solutions provide the best balance of security and practicality.


In multi-developer Git environments, a common pattern involves triggering deployments when pushing to specific branches. The post-receive hook runs under each user's context, but maintaining individual SSH keys for every developer on production servers becomes cumbersome. A shared deployment key with group permissions seems like the logical solution - until SSH rejects it.

OpenSSH enforces strict file permissions for private keys to prevent accidental exposure. The security model requires:

Private key: 600 (rw-------)
Public key: 644 (rw-r--r--)
~/.ssh directory: 700 (rwx------)

When SSH encounters group-readable keys (like 640 permissions), it throws the "UNPROTECTED PRIVATE KEY FILE" warning as a security measure.

Option 1: Permission Elevation with sudo

Create a dedicated deployment user and configure sudo to allow specific commands:

# /etc/sudoers.d/deploy
%deployers ALL=(deploy-user) NOPASSWD: /usr/bin/git, /usr/bin/rsync

Option 2: SSH Config Wrapper

Create a wrapper script that temporarily modifies permissions:

#!/bin/bash
TEMP_KEY=$(mktemp)
chmod 600 "$TEMP_KEY"
cat /path/to/shared_key > "$TEMP_KEY"
ssh -i "$TEMP_KEY" "$@"
rm -f "$TEMP_KEY"

Option 3: SSH Agent Forwarding

Configure your Git server to forward developer keys:

# ~/.ssh/config
Host production
  ForwardAgent yes
  User deploy-user

Modern platforms provide better alternatives:

# GitHub Actions Example
- name: Deploy to Production
  uses: appleboy/ssh-action@master
  with:
    key: ${{ secrets.DEPLOY_KEY }}
    host: production.example.com
    script: cd /app && git pull

While the permission error seems inconvenient, it enforces important security boundaries. Before bypassing these protections, consider:

  • Implementing audit trails for all deployments
  • Using short-lived credentials
  • Restricting network access to deployment targets