The Context
I recently ran into a headache while managing a fleet of about 75 mixed Ubuntu and CentOS nodes. To shave time off my deployments—fact gathering can easily add 3 to 5 seconds per host—I had explicitly set gather_facts: no at the top of my playbook. On a large inventory, this simple toggle can save you several minutes of waiting for SSH handshakes and system scrapes.
The playbook ran smoothly until a task popped up that needed to know which OS it was dealing with. I used a when conditional to apply specific configs for Ubuntu versus CentOS. That is when the execution hit a wall with a bright red error message.
fatal: [server-01]: FAILED! => {
"msg": "The conditional check failed. The error was: 'ansible_distribution' is undefined"
}
The Debugging Process
This error is about as literal as it gets. Ansible is telling you it has no record of a variable named ansible_distribution. In the Ansible world, variables prefixed with ansible_ are usually "facts"—metadata like IP addresses or OS versions that the setup module collects automatically at the start of a play.
A quick look at the playbook header revealed the smoking gun:
---
- name: Configure Web Servers
hosts: web_servers
gather_facts: no # <-- This is the culprit
tasks:
- name: Install Apache on Ubuntu
apt:
name: apache2
state: present
when: ansible_distribution == 'Ubuntu'
Once you set gather_facts: no, you are essentially telling Ansible to skip its internal audit of the remote host. Consequently, the local dictionary of facts stays empty. When the apt task tries to evaluate the host's distribution, it finds a null value, and the whole play fails.
The Solutions
I usually lean on one of three fixes, depending on the scale of the environment and how much I care about performance.
1. The Quick Fix: Re-enable Global Fact Gathering
If you are only managing a handful of servers, just flip the switch back to yes. You can also simply delete the line, as Ansible defaults to gathering facts anyway. This adds a bit of overhead but ensures every system variable is ready for use.
- name: Configure Web Servers
hosts: web_servers
gather_facts: yes
# ... tasks follow
2. The Surgical Approach: Use the Setup Module
When speed is a priority, you can keep facts disabled globally and only pull what you need right before a conditional task. This is my preferred method because it keeps the playbook lean and explicit.
- name: Configure Web Servers
hosts: web_servers
gather_facts: no
tasks:
- name: Grab only the distribution fact
setup:
filter: ansible_distribution
- name: OS specific task
debug:
msg: "Running on {{ ansible_distribution }}"
when: ansible_distribution is defined
The filter parameter tells Ansible to ignore the hundreds of other hardware and network facts, making this specific call lightning-fast.
3. Using Fact Caching
For production environments with hundreds of nodes, I highly recommend setting up a fact cache. By using Redis or local JSON files, you can keep gather_facts: no in your playbooks while pulling data from the previous run's cache. Update your ansible.cfg like this:
[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400 # Facts stay valid for 24 hours
Verification Steps
To confirm your fix actually worked without running the whole playbook, try this ad-hoc command from your terminal:
ansible <your_host> -m setup -a "filter=ansible_distribution"
A successful check should return a clean JSON block like this:
server-01 | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "Ubuntu"
},
"changed": false
}
Lessons Learned
- Mind the Trade-off: Disabling facts is a powerful optimization, but it creates a blind spot for your playbook logic. Always audit your
whenstatements before turning it off. - Fail Gracefully: When writing production-grade code, use
when: ansible_distribution is defined and ansible_distribution == 'Ubuntu'. This prevents a missing fact from crashing the entire run. - Filter Your Facts: You rarely need the full system profile. Use the
setupmodule with specific filters to get the data you need without the 10-second performance penalty.

