Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Ansible By James Joyner IV · · 9 min read

Ansible Error Guide: 'Permission denied (publickey,password)' — Fix SSH Auth Failures

Quick answer

Fix Ansible's 'Permission denied (publickey,password)' SSH failure by correcting the remote user, key path, agent, and ansible_user vars so plays authenticate.

Part of the Ansible Playbook & Module Errors hub
  • #ansible
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

Ansible reports this when the SSH transport cannot authenticate to a managed host, so the host is marked UNREACHABLE before any task runs:

fatal: [web01]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: user@web01: Permission denied (publickey,password).", "unreachable": true}

The parenthetical (publickey,password) lists the authentication methods the server offered, and the message means every method Ansible tried was rejected. This is a connection-layer failure, distinct from a task failure or a sudo password problem — the play never reached the point of running a module.

Symptoms

  • Hosts are marked UNREACHABLE! with Permission denied (publickey,password) and zero tasks run.
  • A manual ssh user@host from the control node also fails or prompts unexpectedly.
  • The same play works for one host/group but fails for another with a different user or key.
  • It works interactively but fails under a systemd timer or CI where no SSH agent is loaded.
  • -vvvv shows the offered keys being rejected one by one before the failure.

Common Root Causes

  • Wrong remote useransible_user (or remote_user/-u) does not match an account that exists on the target.
  • Key not offered or wrong key — the private key Ansible uses is not the pair for the authorized key on the host, or no ansible_ssh_private_key_file is set and no agent is loaded.
  • SSH agent not available in automation — interactive shells load an agent; systemd/CI runners do not, so the key is never offered.
  • Public key not installed — the corresponding public key is missing from the target’s ~/.ssh/authorized_keys.
  • Permissions on the key or authorized_keys — a private key with loose permissions is ignored by SSH; a group-writable ~/.ssh is rejected by the server.
  • Password auth disabled — the server offers only publickey, so a password-based expectation fails outright.

Diagnostic Workflow

Reproduce the failure with a raw connectivity test and full SSH verbosity, which shows exactly which user and key are tried:

ansible web01 -i inventory/hosts -m ping -vvvv

Test the same connection with plain SSH to separate Ansible from the transport:

ssh -v -i ~/.ssh/id_ed25519 deploy@web01

Confirm what user and key Ansible thinks it should use for that host:

ansible-inventory -i inventory/hosts --host web01 | grep -i 'ansible_user\|private_key'

Check the local key and agent state:

ssh-add -l               # is a key loaded in the agent?
ls -l ~/.ssh/id_ed25519  # must be 600 and owned by you

A minimal inventory that sets the connection explicitly, removing ambiguity:

# inventory/hosts.yml
all:
  hosts:
    web01:
      ansible_host: 10.0.0.11
      ansible_user: deploy
      ansible_ssh_private_key_file: ~/.ssh/deploy_ed25519
ansible web01 -i inventory/hosts.yml -m ping
# web01 | SUCCESS => {"ping": "pong"}

Example Root Cause Analysis

A play ran fine from an engineer’s laptop but every host came back Permission denied (publickey,password) when the same play ran from a systemd timer. ansible web01 -m ping -vvvv from the timer’s environment showed no key being offered at all. The laptop had an SSH agent holding the deploy key; the timer ran under a service account with no agent and no ansible_ssh_private_key_file set, so Ansible had no key to present and the server, which had password auth disabled, rejected the connection.

The fix was to set ansible_ssh_private_key_file explicitly in group_vars so the key no longer depended on an agent being loaded, and to ensure the service account owned that key file with 600 permissions. A ping under the exact service-account environment confirmed the connection before the timer was re-enabled. The lesson: never rely on an interactive SSH agent for unattended runs — pin the key file.

Prevention Best Practices

  • Set ansible_user and ansible_ssh_private_key_file explicitly in inventory/group_vars instead of relying on an agent or SSH client defaults.
  • Keep private keys at 600 and ~/.ssh at 700; SSH silently ignores over-permissive keys.
  • Test unattended runs in the actual service-account environment, not just an interactive shell, so a missing agent surfaces before production.
  • Standardize the remote user across a fleet, or set it per-group, so one play does not assume a user that only some hosts have.
  • Use ssh -v alongside ansible ... -vvvv to isolate transport problems from Ansible configuration.

Quick Command Reference

# Raw connectivity test with full SSH detail
ansible <host> -i inventory -m ping -vvvv

# Plain SSH to isolate the transport
ssh -v -i ~/.ssh/<key> <user>@<host>

# See the resolved user/key for a host
ansible-inventory -i inventory --host <host>

# Check the local agent and key permissions
ssh-add -l
ls -l ~/.ssh/<key>

Conclusion

Permission denied (publickey,password) is an SSH authentication failure, not an Ansible bug: the server rejected every credential offered. Pin the remote user and private-key file explicitly in inventory, fix key and directory permissions, and always test unattended runs in the environment they will actually run in. Once a raw ansible -m ping succeeds, the play will connect — the transport is the whole problem to solve here.

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.