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:
parent
181c04bed8
commit
2dbeb22ef9
2 changed files with 106 additions and 1 deletions
103
05-troubleshooting/ansible-ssh-host-alias-bypass.md
Normal file
103
05-troubleshooting/ansible-ssh-host-alias-bypass.md
Normal 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.
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
created: 2026-03-15T06:37
|
created: 2026-03-15T06:37
|
||||||
updated: 2026-04-19
|
updated: 2026-04-19T04:57
|
||||||
---
|
---
|
||||||
# 🔧 General Troubleshooting
|
# 🔧 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)
|
- [SSH Timeout During dnf upgrade on Fedora Hosts](ansible-ssh-timeout-dnf-upgrade.md)
|
||||||
- [Vault Password File Missing](ansible-vault-password-file-missing.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.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)
|
- [Fedora usrmerge: ebtables Symlink Blocks Directory Consolidation](fedora-usrmerge-ebtables-blocker.md)
|
||||||
|
|
||||||
## 📦 Docker & Systems
|
## 📦 Docker & Systems
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue