wiki: add Ansible SSH Host Alias Bypass troubleshooting article

Documents why `ansible myhost -m ping` fails with Permission denied
while `ssh myhost` works — SSH Host blocks match on literal pattern,
not on resolved HostName, so `ansible_host: <IP>` bypasses the alias
and the declared IdentityFile never gets applied. Covers the portable
fix (ansible_ssh_private_key_file in host_vars), the symlink sidebar
for standardizing key names across control nodes, alternatives, and
a diagnosis checklist.

Also catches index.md up with the ansible-check-mode-false-positives
article that was already published but missing from the nav.
This commit is contained in:
Marcus Summers 2026-04-21 09:15:22 -04:00
parent 181c04bed8
commit 2dbeb22ef9
2 changed files with 106 additions and 1 deletions

View file

@ -0,0 +1,103 @@
---
title: "Ansible Fails with Permission Denied While `ssh <alias>` Works (Host Alias Bypass)"
domain: selfhosting
category: troubleshooting
tags:
- ansible
- ssh
- ssh-config
- inventory
- host-vars
- authentication
- troubleshooting
status: published
created: 2026-04-21
updated: 2026-04-21
---
# Ansible Fails with Permission Denied While `ssh <alias>` Works (Host Alias Bypass)
## The Problem
Ansible can't connect to a host, but plain SSH to the same host works fine from the same shell:
```
$ ssh myhost
last login: ...
$ ansible myhost -m ping
myhost | UNREACHABLE! => {
"msg": "Failed to connect to the host via ssh: user@10.0.0.42: Permission denied (publickey).",
"unreachable": true
}
```
The host is online, the key works, the inventory defines the host — yet Ansible sees `Permission denied (publickey)`.
## Why It Happens
Ansible is connecting to the host **by IP**, and SSH doesn't apply `Host` alias blocks when you connect by IP.
Your `~/.ssh/config` likely looks like this:
```
Host myhost
HostName 10.0.0.42
User myuser
IdentityFile ~/.ssh/id_ed25519_custom
```
When you run `ssh myhost`, SSH matches the `Host myhost` block and uses the custom key.
When Ansible runs, its inventory specifies `ansible_host: 10.0.0.42`. SSH is invoked as `ssh user@10.0.0.42` — and the `Host` directive matches on **literal pattern**, not on resolved `HostName`. The string `10.0.0.42` doesn't match the pattern `myhost`, so the block is skipped. SSH falls back to default identity files (`id_rsa`, `id_ecdsa`, `id_ed25519`) — none of which are the actual key — and authentication fails.
Running `ssh -vv user@<IP>` confirms the custom key is never offered.
## The Fix
**Declare the key path in inventory** so Ansible passes it explicitly, independent of SSH config:
```yaml
# host_vars/myhost/vars.yml
ansible_user: myuser
ansible_host: 10.0.0.42
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
```
This is the portable fix — it lives in the inventory repo, so every control node that pulls the repo picks it up.
**Caveat:** if different control nodes store the same key under different filenames (e.g. `id_ed25519` on one machine, `id_ed25519_fleet` on another), pick a single canonical path and standardize. The simplest way is to symlink on the outlier machines:
```
ln -s id_ed25519_fleet ~/.ssh/id_ed25519
ln -s id_ed25519_fleet.pub ~/.ssh/id_ed25519.pub
```
Then host_vars can declare one path and work everywhere.
## Alternative Fixes (Less Portable)
**Second `Host` block matching the IP.** Add to `~/.ssh/config`:
```
Host myhost 10.0.0.42
HostName 10.0.0.42
User myuser
IdentityFile ~/.ssh/id_ed25519_custom
```
The `Host` line accepts multiple patterns. Works immediately but is per-machine — doesn't help other control nodes.
**Change `ansible_host` to the alias instead of the IP.** Requires DNS or `/etc/hosts` entry that resolves the alias fleet-wide. Works if you already have reliable name resolution; otherwise adds infrastructure for no real gain.
## How to Diagnose This
If `ssh <alias>` works but Ansible fails with `Permission denied (publickey)`:
1. Check `ansible_host` in host_vars / inventory. If it's an IP, suspect alias bypass.
2. Run `ssh -vv user@<IP>` (use the IP, not the alias) and look at the `Offering public key` lines. If the key from your `Host` block isn't listed, the block isn't being applied.
3. Fix in inventory, not SSH config, if you want the result portable across control nodes.
## Why This Gotcha Is Invisible
SSH doesn't log "`Host` block skipped because pattern didn't match." The key simply isn't offered, and the server responds with the same generic `Permission denied (publickey)` you'd see for any auth failure. The inventory and SSH config both look correct in isolation — it's the interaction between them that's broken.

View file

@ -1,6 +1,6 @@
---
created: 2026-03-15T06:37
updated: 2026-04-19
updated: 2026-04-19T04:57
---
# 🔧 General Troubleshooting
@ -23,6 +23,8 @@ Practical fixes for common Linux, networking, and application problems.
- [SSH Timeout During dnf upgrade on Fedora Hosts](ansible-ssh-timeout-dnf-upgrade.md)
- [Vault Password File Missing](ansible-vault-password-file-missing.md)
- [ansible.cfg Ignored on WSL2 Windows Mounts](ansible-wsl2-world-writable-mount-ignores-cfg.md)
- [Ansible Check Mode False Positives in Verify/Assert Tasks](ansible-check-mode-false-positives.md)
- [Ansible Fails with Permission Denied While `ssh <alias>` Works (Host Alias Bypass)](ansible-ssh-host-alias-bypass.md)
- [Fedora usrmerge: ebtables Symlink Blocks Directory Consolidation](fedora-usrmerge-ebtables-blocker.md)
## 📦 Docker & Systems