Skip to main content

Fixing 'Permission denied (publickey)' Git Deployment Errors on Cloudways

 Few things stop a deployment pipeline faster than the infamous Permission denied (publickey) error. You have likely encountered this scenario: you are ready to push a hotfix, you trigger the pull on your Cloudways server, and instead of a successful merge, the terminal rejects the connection to GitHub, GitLab, or Bitbucket.

While this error is generic to the SSH protocol, the architecture of managed hosting platforms like Cloudways introduces a specific layer of complexity regarding Linux user management. If you are logging in as the "Master" user but trying to deploy for a specific application, you are crossing a permission boundary that SSH cannot traverse.

This guide provides the root cause analysis of why this happens on Cloudways and a rigorous, step-by-step technical solution to fix it permanently.

The Root Cause: User Isolation and SSH Context

To solve the problem, we must understand how Cloudways structures its Linux users. Cloudways separates the server-level management (Master User) from the application-level execution (Application User).

When you SSH into your server using the default credentials provided in the "Master Credentials" tab, you are logged in as a privileged user (often named master_xxxx). If you generate an SSH key pair in this session, the keys are stored in:

/home/master_xxxx/.ssh/id_ed25519

However, when a PHP process, a deployment webhook, or a cron job runs a git pull command for a specific website, it executes as the Application User (e.g., app_yyyy).

The Handshake Failure

  1. The git command initiates a connection to GitHub.
  2. GitHub requests authentication.
  3. The SSH client (running as app_yyyy) looks for a private key in /home/app_yyyy/.ssh/.
  4. The directory is empty. Even if a key exists for the Master user, the Application user does not have read permissions for the Master user's home directory.
  5. The handshake fails, resulting in Permission denied (publickey).

Step-by-Step Solution

We will generate a modern ED25519 SSH key specifically for the application user and configure the SSH client to use it correctly.

1. Log in as the Application User

Do not use the Master credentials. Go to your Cloudways Application dashboard:

  1. Navigate to Access Details.
  2. Look for Application Credentials.
  3. Create a username and password if one does not exist.

Open your terminal and SSH directly into the application context:

# Replace username and IP with your Cloudways details
ssh app_user_name@123.45.67.89

Once logged in, verify you are the correct user:

whoami
# Output should be your specific application user, NOT 'root' or 'master_xxx'

2. Generate a Strong SSH Key Pair

We will use ed25519 rather than rsa. ED25519 is more secure and performant. We will also perform this strictly within the user's home directory.

# Generate the key. 
# -t specifies type
# -C adds a comment so you can identify this key on GitHub later
ssh-keygen -t ed25519 -C "cloudways_production_deploy_key"

# When prompted for a file path, press Enter to accept default (/home/username/.ssh/id_ed25519)
# When prompted for a passphrase, press Enter (twice) for no passphrase.
# NOTE: For automated deployments, the key must not have a passphrase.

3. Retrieve the Public Key

You need the public component of the key pair to give to your Git host.

cat ~/.ssh/id_ed25519.pub

Copy the output, which will look like this: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... cloudways_production_deploy_key

4. Authorize the Key on GitHub/GitLab

Security best practices dictate using Deploy Keys rather than adding this key to your personal GitHub account. A Deploy Key grants access only to a single repository.

For GitHub:

  1. Navigate to your repository.
  2. Go to Settings > Deploy keys.
  3. Click Add deploy key.
  4. Paste the public key.
  5. If your build process requires writing tags or pushing changes, check Allow write access. Otherwise, leave it read-only.

For GitLab:

  1. Navigate to your repository.
  2. Go to Settings > Repository.
  3. Expand Deploy keys.
  4. Paste the key and enable it.

5. Establish the Host Fingerprint

This is a critical step often missed in automated scripts. The first time you connect to a new host, SSH asks for confirmation (Are you sure you want to continue connecting (yes/no)?). This interactive prompt will crash an automated deployment script.

Manually trigger the handshake once to add GitHub to your known_hosts file:

# Use the -T flag to test connection without attempting a shell login
ssh -T git@github.com

Expected Output:

Hi username/repo! You've successfully authenticated, but GitHub does not provide shell access.

If you see this message, the permission error is resolved.

Advanced Configuration: Handling Multiple Repositories

If your Cloudways application needs to pull from multiple different private repositories (e.g., a main app repo and a private plugin repo), a single default SSH key is insufficient because GitHub prevents using the same Deploy Key on multiple repos.

You must create a config file to map specific keys to specific aliases.

1. Generate a Second Key

Repeat the generation process for the second repository:

ssh-keygen -t ed25519 -f ~/.ssh/id_plugin_repo -C "plugin_repo_key"

2. Create the SSH Config

Create or edit the config file:

nano ~/.ssh/config

Add the following configuration block:

# Main Application Repository
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519

# Secondary Private Repository
Host github-plugin
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_plugin_repo

3. Update Git Remotes

For the secondary repository, you must update the remote URL to use the Host alias defined above (github-plugin) instead of the standard github.com.

# Navigate to the submodule or secondary repo directory
cd public_html/wp-content/plugins/private-plugin

# Update the remote URL
git remote set-url origin git@github-plugin:username/repo-name.git

Common Pitfalls and Edge Cases

File Permission Strictness

SSH is incredibly strict about file permissions. If your .ssh directory is too open, the SSH client will refuse to read the key for security reasons.

If you copied keys manually instead of generating them, fix permissions immediately:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 644 ~/.ssh/known_hosts
chmod 644 ~/.ssh/config

Agent Forwarding (Why you should avoid it)

Some developers use SSH Agent Forwarding (ssh -A) to pass their local machine's credentials to the server. While convenient for interactive sessions, do not rely on this for deployment.

Agent forwarding requires an active client connection. If a cron job or a webhook triggers the deployment script, there is no active SSH agent to forward credentials from, and the deployment will fail. Always use server-side Deploy Keys for reliability.

The HTTPS vs. SSH Trap

Ensure your git remote is actually using SSH. If you cloned the repository using HTTPS, git will prompt for a username/password instead of using your SSH keys.

Check your remote URL:

git remote -v

If the output starts with https://, switch it to SSH:

git remote set-url origin git@github.com:username/repository.git

Conclusion

The "Permission denied" error on Cloudways is rarely about the key itself being invalid; it is almost always about user context. By generating keys specifically for the application user and understanding the Linux permission boundaries, you ensure that your deployment pipeline remains robust, secure, and independent of your personal developer credentials.