Your playbook fails because ansible_facts are empty or missing critical information. Facts are essential for conditional logic, templates, and inventory decisions. When they don't gather correctly, everything breaks.

Understanding the Problem

Facts gathering failures show up in different ways:

bash
TASK [Gather Facts] ***********************************************************
fatal: [webserver01]: FAILED! => {"msg": "Failed to gather facts"}

Or subtler - facts exist but are incomplete:

yaml
- debug:
    var: ansible_default_ipv4
# Result: undefined or missing

Or timeout errors during fact gathering:

bash
fatal: [webserver01]: FAILED! => {"msg": "Timeout (30s) waiting for fact gathering"}

Step 1: Test Facts Gathering Directly

First, verify the setup module works:

bash
ansible webserver01 -i inventory -m setup

This should output a large JSON structure. If it fails, the error will tell you why.

For specific facts:

```bash # Get only network facts ansible webserver01 -i inventory -m setup -a "filter=ansible_*ipv4*"

# Get only hardware facts ansible webserver01 -i inventory -m setup -a "gather_subset=hardware" ```

Step 2: Check Python Requirements

The setup module requires Python and certain libraries:

```bash # Check Python is available ansible webserver01 -i inventory -m raw -a "python3 --version"

# Or Python 2 on older systems ansible webserver01 -i inventory -m raw -a "python --version" ```

If Python is missing, install it:

```bash # On Debian/Ubuntu ansible webserver01 -i inventory -b -m raw -a "apt-get update && apt-get install -y python3"

# On RHEL/CentOS ansible webserver01 -i inventory -b -m raw -a "yum install -y python3" ```

Step 3: Verify Python Interpreter

Ansible might be using the wrong Python interpreter:

bash
# Check what Ansible detects
ansible webserver01 -i inventory -m debug -a "var=ansible_python_interpreter"

Set it explicitly if needed:

ini
# In inventory
[all:vars]
ansible_python_interpreter=/usr/bin/python3

Or in ansible.cfg:

ini
[defaults]
interpreter_python = auto_silent

Step 4: Handle Fact Gathering Timeout

Slow fact gathering causes timeouts. Increase the timeout:

ini
# ansible.cfg
[defaults]
gather_timeout = 60

Or disable subsets that are slow:

yaml
- hosts: all
  gather_subset:
    - min  # Only minimum facts
    - network  # Add network facts
    - "!hardware"  # Exclude hardware facts

Disable fact gathering entirely when not needed:

yaml
- hosts: all
  gather_facts: no
  tasks:
    - name: Quick task
      command: echo "done"

Then gather facts explicitly later:

```yaml - hosts: all gather_facts: no tasks: - name: Quick task first command: echo "done"

  • name: Now gather facts
  • setup:
  • when: ansible_os_family is not defined
  • `

Network facts can hang on systems with problematic network configurations:

bash
# Check network facts specifically
ansible webserver01 -i inventory -m setup -a "filter=ansible_*network*"

If this hangs, exclude network facts:

yaml
- hosts: all
  gather_subset: "!network"

Or skip specific interfaces:

yaml
- hosts: all
  gather_subset: min
  tasks:
    - setup:
        filter: "ansible_*"
        gather_subset: "all"
      ignore_errors: yes

Step 6: Configure Fact Caching

Fact caching prevents re-gathering facts on each run:

ini
# ansible.cfg
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400  # 24 hours

For Redis caching:

ini
[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 86400
fact_caching_connection = localhost:6379:0

Install required Python package:

bash
pip install redis

Verify cache:

bash
ansible webserver01 -i inventory -m setup -a "cache=enabled"

Step 7: Debug Empty Facts

If facts return but are empty:

yaml
- name: Debug all facts
  debug:
    var: ansible_facts

Check if facts are being set at all:

bash
ansible webserver01 -i inventory -m setup | python3 -m json.tool | head -50

If ansible_facts is empty, the setup module might be failing silently. Run with verbosity:

bash
ansible webserver01 -i inventory -m setup -vvv

Look for errors in the output.

Step 8: Handle Permission Issues

Facts require certain permissions to gather:

bash
# Test with become
ansible webserver01 -i inventory -b -m setup

If this works but non-become doesn't, certain facts require root:

yaml
- hosts: all
  become: yes
  tasks:
    - setup:

Or gather without root and accept limited facts:

yaml
- hosts: all
  gather_subset: min  # Works without root

Step 9: Fix Custom Facts

Custom facts in /etc/ansible/facts.d/ might have errors:

```bash # Check custom facts directory ansible webserver01 -i inventory -m shell -a "ls -la /etc/ansible/facts.d/"

# Test custom fact script ansible webserver01 -i inventory -m shell -a "/etc/ansible/facts.d/custom.fact" ```

Custom facts must return valid JSON:

bash
# Should output JSON
ansible webserver01 -i inventory -m shell -a "cat /etc/ansible/facts.d/custom.fact"

Example valid custom fact:

bash
#!/bin/bash
echo '{"version": "1.0", "status": "active"}'

Make it executable:

bash
ansible webserver01 -i inventory -m file -a "path=/etc/ansible/facts.d/custom.fact mode=0755"

Step 10: Handle Virtualization and Container Issues

Containers often have limited facts:

yaml
- hosts: all
  tasks:
    - name: Check if container
      set_fact:
        is_container: "{{ ansible_virtualization_type == 'docker' }}"

If facts are missing in containers, adjust expectations:

yaml
- hosts: all
  gather_subset: min
  tasks:
    - name: Use fallback for missing facts
      set_fact:
        ansible_processor_cores: "{{ ansible_processor_cores | default(1) }}"
        ansible_memtotal_mb: "{{ ansible_memtotal_mb | default(512) }}"

Step 11: Troubleshoot Specific Fact Issues

**Missing ansible_default_ipv4:**

```bash # Check interfaces ansible webserver01 -i inventory -m setup -a "filter=ansible_interfaces"

# May need to specify interface - set_fact: primary_ip: "{{ ansible_eth0.ipv4.address | default(ansible_enp0s3.ipv4.address) }}" ```

**Missing ansible_distribution:**

yaml
- hosts: all
  gather_subset: min  # May not include distribution
  tasks:
    - setup:
        gather_subset: "!min"  # Get all facts
    - debug:
        var: ansible_distribution

Missing mount facts:

yaml
- hosts: all
  gather_subset: hardware  # Include hardware for mount facts

Quick Verification

Full facts test:

bash
ansible all -i inventory -m setup --tree /tmp/facts

Check specific facts:

```bash # IP address ansible all -i inventory -m setup -a "filter=ansible_default_ipv4"

# OS info ansible all -i inventory -m setup -a "filter=ansible_distribution*"

# Memory ansible all -i inventory -m setup -a "filter=ansible_mem*" ```

Verify facts in playbook:

yaml
- hosts: all
  tasks:
    - name: Verify facts are gathered
      assert:
        that:
          - ansible_facts is defined
          - ansible_os_family is defined
          - ansible_default_ipv4 is defined
        fail_msg: "Facts gathering failed"
        success_msg: "All facts present"

Prevention Best Practices

  1. 1.Use fact caching for performance:
ini
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = ~/.ansible/facts
fact_caching_timeout = 7200
  1. 1.Gather only needed facts:
yaml
- hosts: all
  gather_subset:
    - min
    - network
  1. 1.Handle missing facts gracefully:
yaml
- set_fact:
    # Use default if fact missing
    primary_ip: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}"
  1. 1.Pre-install Python on targets:
yaml
- hosts: all
  gather_facts: no
  tasks:
    - name: Bootstrap Python
      raw: |
        if command -v python3 >/dev/null 2>&1; then
          echo "Python3 installed"
        else
          apt-get update && apt-get install -y python3 || yum install -y python3
        fi
      changed_when: false

Fact gathering issues usually stem from Python availability, permissions, timeouts, or network problems. Test with the setup module directly, adjust timeouts, and use fact caching to improve reliability.