Fix 'Permission denied (publickey)' SSH Error in Ansible When Connecting to Remote Hosts

beginner๐Ÿ”ง Ansible2026-05-20| Ansible 2.9+, Ubuntu/Debian/CentOS/RHEL, OpenSSH, Linux control node

Error Message

Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic)
#ssh#publickey#authentication#private-key#ansible-ssh

What's happening

You run a playbook or ansible -m ping and get this:

fatal: [192.168.1.10]: UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic)",
    "unreachable": true
}

Ansible offered its SSH key. The server said no. Nothing ran.

Note that this isn't a timeout โ€” the remote host is up, it just refused the key. In about 90% of cases the culprit is one of four things: wrong key path, key not listed in authorized_keys, wrong remote user, or broken file permissions on the key or .ssh directory.

Debug process

Step 1: Test SSH manually first

Skip Ansible for a moment and test the raw SSH connection:

ssh -i /path/to/private_key ansible_user@192.168.1.10

If that also fails, the problem is in SSH itself โ€” not Ansible. Fix SSH first and Ansible will just work.

Stuck? Add -v to see exactly where auth breaks down:

ssh -v -i /path/to/private_key ansible_user@192.168.1.10

Scan the output for lines like:

debug1: Offering public key: /path/to/private_key
debug1: Authentications that can continue: publickey
Permission denied (publickey).

Step 2: Run Ansible with verbose output

ansible all -m ping -vvv

-vvv prints the exact SSH command Ansible builds internally. You'll see which key it's loading, which user it's connecting as, and which port it's hitting.

Step 3: Check your inventory

cat inventory.ini

Confirm these variables are actually set:

[webservers]
192.168.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa

Common causes and fixes

Cause 1: Wrong or missing private key path

Ansible can't find โ€” or can't read โ€” the private key you pointed it at.

Fix: Confirm the key exists and has the right ownership:

ls -la ~/.ssh/id_rsa
# Should show: -rw------- 1 youruser youruser

Then set the path in ansible.cfg:

# ansible.cfg
[defaults]
private_key_file = ~/.ssh/id_rsa

Or per-host in inventory:

192.168.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=/home/youruser/.ssh/deploy_key

Cause 2: Public key not in authorized_keys on remote host

Your public key simply isn't there. The remote host has no idea who you are.

Fix: Push the key over with one command:

ssh-copy-id -i ~/.ssh/id_rsa.pub ansible_user@192.168.1.10

No password access yet? Add it by hand on the remote:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "your-public-key-content" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Cause 3: Wrong file permissions on key or .ssh directory

SSH silently discards keys that have overly-permissive permissions. It won't say "ignored due to permissions" โ€” it just says "permission denied" and moves on. Maddening.

Fix: Lock down permissions on the control node:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub

Do the same on the remote:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

If you keep mixing up 600 vs 644, the Unix Permissions Calculator at ToolCraft lets you click permission bits and get the correct numeric value instantly.

Cause 4: Wrong remote user

By default Ansible connects as your local username. On a fresh Ubuntu server that user is ubuntu, on Amazon Linux it's ec2-user, on CentOS it's centos โ€” your local username almost certainly isn't any of those.

Fix: Set the user explicitly. Pick whichever layer makes sense:

# In inventory
192.168.1.10 ansible_user=ubuntu

# Or in ansible.cfg
[defaults]
remote_user = ubuntu

# Or per-play in the playbook
- hosts: webservers
  remote_user: ubuntu

Cause 5: SELinux or StrictModes blocking the key

On RHEL/CentOS, SELinux can stop SSH from reading authorized_keys even when file permissions look correct. The label is wrong, not the mode bits.

Fix: Restore the SELinux context:

restorecon -Rv ~/.ssh

Also check /etc/ssh/sshd_config on the remote for StrictModes yes โ€” that setting enforces permission checks on ~/.ssh and everything inside it.

Cause 6: SSH agent not loaded or has the wrong key

When you don't specify a key file, Ansible pulls whatever key is currently in your SSH agent. If you loaded a personal key but the server expects a deploy key, auth fails โ€” and the error looks identical.

Fix: See what's actually loaded:

ssh-add -l

Load the right key:

ssh-add ~/.ssh/deploy_key

Or tell Ansible to ignore the agent entirely and use a specific key:

[ssh_connection]
ssh_args = -o ForwardAgent=no

Full working example

Here's a minimal setup that goes from zero to a working Ansible connection:

# 1. Generate a new ed25519 key pair
ssh-keygen -t ed25519 -f ~/.ssh/ansible_key -N ""

# 2. Push the public key to the remote host
ssh-copy-id -i ~/.ssh/ansible_key.pub ubuntu@192.168.1.10

# 3. Verify raw SSH works before involving Ansible
ssh -i ~/.ssh/ansible_key ubuntu@192.168.1.10 exit
echo "Exit code: $?"  # Must be 0

# 4. Write the inventory
cat > inventory.ini  {
    "changed": false,
    "ping": "pong"
}

Managing a fleet? Check all hosts at once with a cleaner one-liner:

ansible all -i inventory.ini -m ping --one-line

Lessons learned

  • Test SSH before touching Ansible. ssh -v -i key user@host tells you everything in 3 seconds. If that fails, Ansible has no chance.
  • Permissions fail silently. SSH drops keys with wrong permissions and says nothing useful. Always run chmod 600 on private keys and chmod 700 on .ssh directories โ€” both on the control node and the remote.
  • Be explicit in your inventory. Declare ansible_user and ansible_ssh_private_key_file for every host group. Relying on defaults is how this error appears three months later when someone runs Ansible from a different machine.
  • Use ed25519 for new keys. Shorter, faster, and no compatibility headaches compared to RSA-1024 or RSA-2048.

Related Error Notes