Introduction

Ansible facts are system properties collected automatically from managed hosts — OS name, IP addresses, CPU count, memory, disk space, network interfaces, and more. They're available as variables in your playbooks, letting you write conditional logic, generate templates, and make decisions based on actual system state.

How Facts Work

By default, Ansible runs the setup module at the start of every play to collect facts:

---
- name: Show facts
  hosts: all
  # gather_facts: true (default)
  tasks:
    - name: Print OS
      ansible.builtin.debug:
        msg: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
      # Output: "Ubuntu 24.04" or "RedHat 9.3"

Common Facts Reference

FactExample ValueDescription
ansible_hostnameweb-01Short hostname
ansible_fqdnweb-01.example.comFully qualified domain name
ansible_distributionUbuntuOS distribution
ansible_distribution_version24.04OS version
ansible_distribution_major_version24Major version
ansible_os_familyDebianOS family (Debian, RedHat, etc.)
ansible_kernel6.5.0-44-genericKernel version
ansible_architecturex86_64CPU architecture
ansible_default_ipv4.address10.0.0.5Primary IPv4 address
ansible_default_ipv4.interfaceeth0Primary network interface
ansible_all_ipv4_addresses["10.0.0.5", "172.17.0.1"]All IPv4 addresses
ansible_processor_vcpus4CPU core count
ansible_memtotal_mb8192Total RAM in MB
ansible_memfree_mb4096Free RAM in MB
ansible_swaptotal_mb2048Total swap in MB
ansible_devices{sda: {...}}Disk devices
ansible_mounts[{mount: "/", ...}]Mounted filesystems
ansible_env{HOME: "/root", ...}Environment variables
ansible_user_idrootCurrent user
ansible_python_version3.12.1Python version
ansible_virtualization_typekvmVirtualization type
ansible_service_mgrsystemdService manager
ansible_pkg_mgraptPackage manager
ansible_date_time.iso86012026-04-12T...Current date/time

View All Facts

# Show all facts for a host
ansible myhost -m setup

# Filter specific facts
ansible myhost -m setup -a "filter=ansible_distribution*"

# Show network facts
ansible myhost -m setup -a "filter=ansible_default_ipv4"

In a playbook:

- name: Dump all facts
  ansible.builtin.debug:
    var: ansible_facts

- name: Show specific facts
  ansible.builtin.debug:
    msg:
      os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
      ip: "{{ ansible_default_ipv4.address }}"
      cpus: "{{ ansible_processor_vcpus }}"
      ram_mb: "{{ ansible_memtotal_mb }}"
      disk: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_total') | first | human_readable }}"

Using Facts in Conditions

---
- name: OS-specific tasks
  hosts: all
  tasks:
    # Package management by OS family
    - name: Install packages (Debian/Ubuntu)
      ansible.builtin.apt:
        name: "{{ item }}"
        state: present
      loop:
        - nginx
        - python3-pip
      when: ansible_os_family == "Debian"

    - name: Install packages (RHEL/CentOS/Fedora)
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - nginx
        - python3-pip
      when: ansible_os_family == "RedHat"

    # Version-specific logic
    - name: Ubuntu 24.04+ specific config
      ansible.builtin.template:
        src: ubuntu24.conf.j2
        dest: /etc/myapp.conf
      when:
        - ansible_distribution == "Ubuntu"
        - ansible_distribution_major_version | int >= 24

    # Architecture check
    - name: Download binary for correct architecture
      ansible.builtin.get_url:
        url: "https://releases.example.com/app-{{ ansible_architecture }}.tar.gz"
        dest: /tmp/app.tar.gz

    # Memory-based decisions
    - name: Configure for high-memory servers
      ansible.builtin.set_fact:
        db_cache_size: "{{ (ansible_memtotal_mb * 0.7) | int }}m"
      when: ansible_memtotal_mb > 4096

    - name: Configure for low-memory servers
      ansible.builtin.set_fact:
        db_cache_size: "512m"
      when: ansible_memtotal_mb <= 4096

Facts in Templates

templates/nginx.conf.j2:

# Generated by Ansible for {{ ansible_fqdn }}
# OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
# CPUs: {{ ansible_processor_vcpus }}

worker_processes {{ ansible_processor_vcpus }};

events {
    worker_connections {{ (ansible_memtotal_mb / 4) | int }};
}

http {
    server {
        listen 80;
        server_name {{ ansible_fqdn }};

        # Access log includes hostname for multi-server setups
        access_log /var/log/nginx/{{ ansible_hostname }}-access.log;
    }
}

Disable Fact Gathering

Fact gathering adds 1-5 seconds per host. Disable it when you don't need system info:

---
- name: Quick task (no facts needed)
  hosts: all
  gather_facts: false
  tasks:
    - name: Restart service
      ansible.builtin.service:
        name: myapp
        state: restarted

Or gather only specific subsets:

---
- name: Gather minimal facts
  hosts: all
  gather_facts: false
  tasks:
    - name: Gather only network facts
      ansible.builtin.setup:
        gather_subset:
          - network
          - "!all"

    - name: Gather everything except hardware (faster)
      ansible.builtin.setup:
        gather_subset:
          - "!hardware"

Available subsets: all, min, hardware, network, virtual, ohai, facter

Custom Facts (Local Facts)

Create custom facts on managed hosts in /etc/ansible/facts.d/:

On the remote host, create /etc/ansible/facts.d/myapp.fact:

[general]
app_version=2.1.0
environment=production
deploy_date=2026-04-12

[database]
host=db.internal
port=5432

Or as a JSON file (/etc/ansible/facts.d/myapp.fact):

{
  "app_version": "2.1.0",
  "environment": "production"
}

Or as an executable script that outputs JSON:

#!/bin/bash
echo '{"disk_usage": "'$(df -h / | tail -1 | awk '{print $5}')'"}'

Access custom facts:

- name: Use custom facts
  ansible.builtin.debug:
    msg: "App version: {{ ansible_local.myapp.general.app_version }}"

Deploy Custom Facts with Ansible

---
- name: Deploy custom facts
  hosts: all
  become: true
  tasks:
    - name: Create facts directory
      ansible.builtin.file:
        path: /etc/ansible/facts.d
        state: directory
        mode: "0755"

    - name: Deploy custom fact file
      ansible.builtin.template:
        src: myapp.fact.j2
        dest: /etc/ansible/facts.d/myapp.fact
        mode: "0644"

    - name: Re-read facts after deploying
      ansible.builtin.setup:
        filter: ansible_local

Fact Caching

Cache facts to speed up subsequent runs:

# ansible.cfg
[defaults]
gathering = smart          # Only gather facts if not cached
fact_caching = jsonfile     # Cache backend
fact_caching_connection = /tmp/ansible-facts-cache
fact_caching_timeout = 3600 # Cache for 1 hour (seconds)

Other cache backends: redis, memcached, mongodb, yaml

# Redis caching
[defaults]
fact_caching = redis
fact_caching_connection = localhost:6379:0
fact_caching_timeout = 7200

ansible_hostname vs inventory_hostname

VariableSourceExample
ansible_hostnameGathered from remote host (hostname -s)web-01
ansible_fqdnGathered from remote host (hostname -f)web-01.example.com
inventory_hostnameFrom inventory fileweb-01.example.com or 10.0.0.5
inventory_hostname_shortFirst part of inventory hostnameweb-01
# These can differ!
- ansible.builtin.debug:
    msg:
      ansible_hostname: "{{ ansible_hostname }}"           # web-01
      inventory_hostname: "{{ inventory_hostname }}"       # 10.0.0.5 (if IP in inventory)

Troubleshooting

"ansible_default_ipv4 is undefined"

The host has no default route or network facts weren't gathered:

- name: Gather network facts explicitly
  ansible.builtin.setup:
    gather_subset:
      - network

- name: Use with fallback
  ansible.builtin.debug:
    msg: "{{ ansible_default_ipv4.address | default('unknown') }}"

Slow Fact Gathering

# Option 1: Disable entirely
gather_facts: false

# Option 2: Gather only what you need
- ansible.builtin.setup:
    gather_subset:
      - "!all"
      - "!min"
      - network

# Option 3: Use fact caching (see above)

Custom Facts Not Appearing

Check the fact file on the remote host:

# Must be in /etc/ansible/facts.d/
# Must end in .fact
# Must be valid INI, JSON, or executable returning JSON
# Must be readable (chmod 644 for files, 755 for scripts)

Conclusion

Ansible facts give you automatic access to system information — OS, IPs, CPU, memory, disks — without writing any discovery logic. Use them for OS-specific conditionals (ansible_os_family), template generation (ansible_processor_vcpus), and capacity-based decisions (ansible_memtotal_mb). Disable gathering with gather_facts: false for speed, use gather_subset for partial collection, and enable fact caching for large inventories. Custom facts in /etc/ansible/facts.d/ extend the system with your application-specific data.