The Error
Your playbook dies on the very first task, before it does anything useful:
fatal: [192.168.1.10]: FAILED! => {"changed": false, "msg": "/usr/bin/python: not found", "rc": 127}
Ansible tried to run /usr/bin/python on the remote host. That path doesn't exist. It's a one-line failure that catches a lot of people upgrading to Ubuntu 20.04 or Debian 11 โ those distros ship Python 3 only, and /usr/bin/python (Python 2's home) is just gone.
Why This Happens
Ansible needs Python on the remote host to execute modules. Before version 2.12, it defaulted to /usr/bin/python โ a Python 2 path. Ubuntu 18.04 and older had both Python 2 and Python 3, so this worked fine. Ubuntu 20.04 dropped Python 2 entirely. Same story with Debian 11, Rocky Linux 8, and AlmaLinux 8.
Two things can trigger this:
- The remote host has Python 3 at
/usr/bin/python3, but no/usr/bin/pythonsymlink. - Your
ansible.cfgor inventory explicitly setsansible_python_interpreter=/usr/bin/python.
Quick Fix โ Set ansible_python_interpreter
Point Ansible at the right Python binary. That's all it takes.
Option 1: Per-host or per-group in your inventory
Add ansible_python_interpreter directly to the affected host or group:
[webservers]
192.168.1.10 ansible_python_interpreter=/usr/bin/python3
192.168.1.11 ansible_python_interpreter=/usr/bin/python3
Prefer group vars? Use this instead:
[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
Option 2: Auto-discovery (best for mixed environments)
Set the interpreter to auto and let Ansible figure it out:
[webservers:vars]
ansible_python_interpreter=auto
With auto, Ansible SSHes into each host, checks which Python versions are available, and picks the best one. No manual configuration per host. Works well when your fleet is a mix โ some hosts still on Python 2, most already on Python 3.
Permanent Fix โ ansible.cfg
Setting it per-inventory gets old fast. The cleaner approach: add a global default to ansible.cfg:
[defaults]
interpreter_python = auto_silent
auto_silent does the same auto-detection as auto, minus the deprecation warning. That warning clutters output in CI/CD pipelines โ silence it early.
All your hosts on Python 3? Skip the auto-detection overhead and be explicit:
[defaults]
interpreter_python = /usr/bin/python3
Alternative: Install python-is-python3 on the Remote Host
Sometimes you can't touch Ansible's config โ maybe it's a shared ansible.cfg, or you're running legacy playbooks with hardcoded paths. In that case, create the symlink on the remote host instead:
# Ubuntu / Debian
sudo apt install python-is-python3 -y
# Verify the symlink
ls -la /usr/bin/python
# /usr/bin/python -> /usr/bin/python3
This package creates the /usr/bin/python โ /usr/bin/python3 symlink. Old playbooks keep working without any Ansible config changes.
Verify the Fix
Run a quick ping to confirm Ansible can reach the host and execute:
ansible 192.168.1.10 -m ping -i inventory.ini
You want to see this:
192.168.1.10 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Want to double-check which interpreter Ansible actually picked? Run:
ansible 192.168.1.10 -m command -a "python3 --version" -i inventory.ini
For full visibility during a playbook run, add -v:
ansible-playbook site.yml -i inventory.ini -v
Look for this line in the output:
Using /usr/bin/python3 as the Python interpreter on host 192.168.1.10
Summary
- Ansible defaults to
/usr/bin/python, which doesn't exist on Python 3-only hosts (Ubuntu 20.04+, Debian 11+, Rocky Linux 8+). - Fastest fix: add
ansible_python_interpreter=/usr/bin/python3to your inventory. - Best long-term fix: set
interpreter_python = auto_silentinansible.cfg. - Confirm it works:
ansible <host> -m ping.

