Skip to content
Latchkey

Ansible "Permission denied (publickey)" - Fix SSH Auth in CI

SSH reached the host but rejected authentication - the private key the runner offered is not accepted for the user being used. This is a credentials/config problem, not a network one.

What this error means

Ansible reports the host UNREACHABLE with Permission denied (publickey). The connection itself succeeds (so it is not a timeout), but no offered key authenticates, so every task is skipped. It fails identically on retry.

ansible output
fatal: [db01]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to
the host via ssh: deploy@10.0.4.7: Permission denied (publickey).", "unreachable": true}

Diagnose it: state, credentials, or drift?

Infrastructure failures in CI are usually about state access or credentials rather than the configuration itself. Confirm the runner can reach and lock state before reading the plan output.

Terminal
terraform init -backend=true -input=false
terraform providers
terraform plan -input=false -no-color -detailed-exitcode
#   exit 0 = no changes, 2 = changes present, 1 = error

Common causes

Private key not loaded in CI

The deploy key is not added to the SSH agent or referenced via --private-key, so Ansible has no key to offer. Common when the key lives in a secret that was never written to disk or loaded.

Wrong remote_user or missing public key

The play connects as the wrong user, or the matching public key is not in that user’s authorized_keys on the target, so authentication is refused.

How to fix it

Load the deploy key into the agent

Write the secret key to the agent (or a file) before running the playbook.

.github/workflows/deploy.yml
- run: |
    eval "$(ssh-agent -s)"
    echo "${{ secrets.DEPLOY_SSH_KEY }}" | ssh-add -
    ansible-playbook -i inventory site.yml

Match remote_user and authorized_keys

  1. Set ansible_user / remote_user to the account whose authorized_keys holds your public key.
  2. Verify manually: ssh -i key deploy@host should succeed before Ansible does.
  3. Confirm the public key is present on the target for that user.

How to prevent it

  • Store the deploy private key as a CI secret and load it per job.
  • Pin ansible_user explicitly in inventory so the right account is used.
  • Use a dedicated, narrowly scoped deploy key rather than a personal key.

Frequently asked questions

What causes Ansible "Permission denied (publickey)"?
There are 2 common causes: private key not loaded in ci and wrong remote_user or missing public key. The deploy key is not added to the SSH agent or referenced via --private-key, so Ansible has no key to offer.
How do I fix Ansible "Permission denied (publickey)"?
There are 2 fixes depending on which cause you have: load the deploy key into the agent and match remote_user and authorized_keys. Work through them in order, since the first is the most common.
What does Ansible "Permission denied (publickey)" actually mean?
Ansible reports the host UNREACHABLE with Permission denied (publickey).
How do I stop Ansible "Permission denied (publickey)" happening again?
Store the deploy private key as a CI secret and load it per job. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card