The Error

fatal: [myserver]: UNREACHABLE! => {
    "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname myserver:
    Name or service not known"
}

This means the control node's DNS can't resolve the hostname in your inventory to an IP address.

Quick Diagnosis

# Test from the Ansible control node (not your laptop)
ssh myserver
# ssh: Could not resolve hostname myserver: Name or service not known

# Try ping
ping -c 1 myserver
# ping: myserver: Name or service not known

# Try with FQDN
ping -c 1 myserver.example.com
# PING myserver.example.com (192.168.1.10) 56(84) bytes of data.

If FQDN works but short name doesn't, the issue is DNS search domain configuration.

5 Solutions

Solution 1: Use ansible_host with IP Addresses (Most Reliable)

# inventory.ini
[webservers]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11
db1  ansible_host=192.168.1.20
# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1:
          ansible_host: 192.168.1.10
        web2:
          ansible_host: 192.168.1.11

This decouples the inventory name from DNS — the most reliable approach.

Solution 2: Use FQDNs in Inventory

[webservers]
web1.example.com
web2.example.com

Solution 3: Add Entries to /etc/hosts

# On the Ansible control node
echo "192.168.1.10 web1 web1.example.com" | sudo tee -a /etc/hosts
echo "192.168.1.11 web2 web2.example.com" | sudo tee -a /etc/hosts

Or automate with Ansible (on a working host):

- name: Populate /etc/hosts
  ansible.builtin.lineinfile:
    path: /etc/hosts
    line: "{{ hostvars[item].ansible_host }} {{ item }}"
    state: present
  loop: "{{ groups['all'] }}"
  when: hostvars[item].ansible_host is defined

Solution 4: Fix DNS Configuration

# Check current DNS server
cat /etc/resolv.conf
# nameserver 8.8.8.8

# Test DNS resolution
nslookup myserver
dig myserver
host myserver

# Test with specific DNS server
dig @10.0.0.1 myserver.example.com

# Add search domain
echo "search example.com" | sudo tee -a /etc/resolv.conf

Solution 5: Fix DNS Search Domain

If short hostnames should resolve via a domain suffix:

# /etc/resolv.conf
nameserver 10.0.0.1
search example.com internal.company.com

Now myserver resolves as myserver.example.com.

Common Causes

CauseSymptomFix
Typo in inventoryOne host failsCheck spelling
DNS server downAll hosts failCheck /etc/resolv.conf
VPN not connectedPrivate hosts failConnect VPN first
Wrong inventory fileWrong hosts targetedansible-playbook -i correct_inventory
Cloud instance rebuiltIP changedUpdate ansible_host
Split DNS (VPN)Internal names failCheck DNS over VPN

Debugging Steps

1. Verify Which Inventory Ansible Uses

ansible-config dump | grep DEFAULT_HOST_LIST
# DEFAULT_HOST_LIST = ['/etc/ansible/hosts']

# Override with -i
ansible all -i my_inventory.ini --list-hosts

2. Test SSH Directly

# Test with the exact command Ansible would use
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 deploy@web1

3. Increase Verbosity

ansible web1 -m ping -i inventory -vvvv
# Shows the exact SSH command and DNS resolution attempt

4. Check for Stale DNS Cache

# Flush DNS cache
# systemd-resolved
sudo resolvectl flush-caches

# nscd
sudo nscd -i hosts

# dnsmasq
sudo systemctl restart dnsmasq

VPN and Network Issues

# Check if VPN is connected
ip route | grep tun
ip route | grep vpn

# Check if you can reach the network
ping -c 1 192.168.1.1  # Gateway

# Check firewall isn't blocking DNS
sudo ss -ulnp | grep :53

Ansible Configuration Options

# ansible.cfg
[defaults]
# Timeout for SSH connection
timeout = 30

# Retry failed hosts
retries = 3

[ssh_connection]
# SSH timeout
ssh_args = -o ConnectTimeout=30 -o ServerAliveInterval=15

Conclusion

"Could not resolve hostname" means DNS lookup failed on the control node. The most reliable fix is using ansible_host with IP addresses in your inventory. For DNS-based resolution, verify /etc/resolv.conf, check VPN connectivity for private networks, and test with dig/nslookup from the control node. Use ansible -vvvv to see the exact SSH command and pinpoint where resolution fails.