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@hosttells 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 600on private keys andchmod 700on.sshdirectories โ both on the control node and the remote. - Be explicit in your inventory. Declare
ansible_userandansible_ssh_private_key_filefor 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.

