Facts #
When managing dozens to thousands of servers with varying specifications, automation can’t run blindly. You can’t use the exact same configuration file for a server with 2GB of RAM and a server with 64GB of RAM. You also can’t run the same package management commands on a CentOS server and an Ubuntu server. For your playbooks to run intelligently and adaptively, Ansible must know the physical and logical characteristics of managed nodes in real time.
Ansible solves this system discovery need through the facts mechanism. Facts are automatic system variables Ansible collects directly from target servers at the start of every play execution. This article deeply dissects how facts are collected, presents a reference table of the most popular facts, introduces reading system environment variables (ansible_env), guides accessing facts across target servers, creating your own application custom facts, and configuring performance optimizations through disabling and caching facts in large-scale environments.
How Facts Are Collected #
By default, at the start of every play execution, Ansible inserts an implicit task in your terminal output named TASK [Gathering Facts]. Behind the scenes, this task executes a special module called setup on the target server.
The setup module does a thorough investigation into the target operating system: reads files in the /proc directory, calls system utility commands like ip, df, lshw, and detects virtualization status. All that found data is then compressed into a structured JSON data structure and sent back to the Control Node, where it’s stored as dynamic variables prefixed with ansible_*.
You can try calling the setup module manually from the terminal CLI to inspect all available facts on a target host using an ad-hoc command:
# Collect and display all facts from web-01 in JSON format
ansible -i inventory/ hosts.ini web-01 -m setup
Because the returned JSON data is very large (can reach thousands of lines), you can filter the output using the filter argument:
# Only display data related to the operating system
ansible -i inventory/ hosts.ini web-01 -m setup -a "filter=ansible_distribution*"
# Only display data related to memory allocation
ansible -i inventory/ hosts.ini web-01 -m setup -a "filter=ansible_memory*"
Popular Facts Table for Playbook Conditionals #
The facts collected by the setup module are grouped into several main categories. Here’s a reference table of fact data most often used as automation logic parameters in playbooks:
1. Operating System Information (OS Facts) #
| Variable Name | Example Values | Usage Description |
|---|---|---|
ansible_distribution | "Ubuntu", "Debian", "Rocky", "CentOS" | Determines the specific target OS distribution name. |
ansible_distribution_version | "22.04", "9.2", "11" | Checks the OS release version for feature alignment. |
ansible_distribution_major_version | "22", "9", "11" | Used for major version operations (e.g. RHEL 8 vs 9). |
ansible_os_family | "Debian", "RedHat", "Suse" | Groups package manager commands (apt vs dnf). |
2. Hardware Information (Hardware Facts) #
| Variable Name | Example Values | Usage Description |
|---|---|---|
ansible_processor_count | 2 | The number of physical CPU sockets installed. |
ansible_processor_vcpus | 4 | The number of logical CPU threads (very useful for thread tuning). |
ansible_memtotal_mb | 8192 | Total physical RAM capacity in Megabytes. |
ansible_memfree_mb | 2048 | Remaining free RAM capacity when gathering facts ran. |
ansible_architecture | "x86_64", "aarch64" | Selects the binary installation package (Intel vs ARM). |
3. Network Information (Network Facts) #
| Variable Name | Example Values | Usage Description |
|---|---|---|
ansible_hostname | "web-prod-01" | The short server hostname without the domain (non-FQDN). |
ansible_fqdn | "web-prod-01.local" | The full domain name of the target server. |
ansible_default_ipv4.address | "192.168.1.50" | The main IP address used for the default outbound route. |
ansible_default_ipv4.interface | "eth0" | The active main NIC (eth0, ens3, etc.). |
ansible_all_ipv4_addresses | ["192.168.1.50", "10.0.0.5"] | A list containing all IPv4 addresses registered on the system. |
4. Date & Time Information (Time Facts) #
| Variable Name | Example Values | Usage Description |
|---|---|---|
ansible_date_time.date | "2026-06-17" | The current date in YYYY-MM-DD format. |
ansible_date_time.time | "12:00:00" | The current local time in HH:MM:SS format. |
ansible_date_time.iso8601 | "2026-06-17T05:00:00Z" | The international standard timestamp for audit logging. |
ansible_date_time.epoch | "1781672400" | Unix timestamp for time difference calculations. |
Here’s a practical example of writing a task leveraging the facts table above to adjust an application configuration template file:
# Automatically adjust the Apache web server worker thread count
- name: Apply the Apache HTTPD configuration
template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
vars:
# Allocate 2 threads per logical CPU, with a minimum limit of 2 threads
max_workers: "{{ [2, (ansible_processor_vcpus | int * 2)] | max }}"
Reading System Environment Variables with ansible_env
#
Besides hardware and OS information, Ansible also captures all active environment variables for the SSH user used to execute commands on the managed node. These variables are stored in the ansible_env dictionary.
You can use ansible_env to detect important system variables like the home directory path, HTTP proxy configuration, or system executable paths.
# Reading target system environment variables
- name: Create a downloads directory in the target user's home
file:
path: "{{ ansible_env.HOME }}/downloads"
state: directory
mode: '0755'
- name: Use a binary from the target's custom PATH if available
command: my-custom-tool --version
environment:
PATH: "{{ ansible_env.PATH }}:/opt/custom/bin"
Accessing Other Hosts’ Facts (hostvars)
#
By default, fact variables are only available in the execution thread of the relevant host. However, in production cluster orchestration, you often need information from other servers.
For example, when configuring the front proxy web server (Nginx), you need to know the default IP addresses of several backend application servers (PHP-FPM or NodeJS) to put into the Nginx upstream block.
Ansible solves this need through the global hostvars variable. With hostvars, you can break through host isolation limits and read facts from any server registered in your inventory, as long as the target server is in the same play run or its data has been stored in cache.
# FILE: playbook.yml
- name: Load Balancer Proxy Configuration
hosts: loadbalancers
tasks:
- name: Configure the Nginx Upstream proxy
template:
src: upstream.conf.j2
dest: /etc/nginx/conf.d/upstream.conf
notify: Restart Nginx
# FILE: templates/upstream.conf.j2
# Upstream configuration using the dynamic IP addresses of backend servers
upstream backend_app {
{% for host in groups['webservers'] %}
# ✓ Takes the default IP of each server in the webservers group
server {{ hostvars[host]['ansible_default_ipv4']['address'] }}:8080;
{% endfor %}
}
[!WARNING] Using
hostvarsrequires the target server whose data you want to fetch to have already executed theGathering Factsstage in the same play or a previous play. If that host is skipped or excluded from the play targets, Ansible returns an Undefined Variable error. To fix this, you can enable the Fact Caching feature.
Custom Facts Implementation #
Besides the built-in system facts collected by the setup module, you can also define your own custom facts on managed nodes. Custom facts are very useful for storing application-specific statuses managed by developer teams (for example microservice release versions, database replication status, or server responsible owners).
Custom facts must be stored on the target server in the /etc/ansible/facts.d/ directory as files with the .fact extension. These files can be:
- Static files in INI format
- Static files in JSON format
- Executable script files (like Bash or Python) producing standard JSON output to stdout.
1. Example Static Custom Fact (JSON) #
You can deploy a static metadata configuration file using Ansible:
// /etc/ansible/facts.d/app_info.fact
{
"metadata": {
"application": "payment-api",
"version": "1.4.2",
"owner": "fintech-team"
}
}
2. Example Dynamic Custom Fact (Executable Script) #
If you want the fact to be dynamic (for example checking remaining backup space dynamically), you can place a shell script returning JSON output:
#!/bin/bash
# /etc/ansible/facts.d/system_health.fact
# Executable scripts must return valid JSON format to stdout
FREE_SPACE=$(df -h / | awk 'NR==2 {print $4}')
echo "{\"disk_root_free\": \"$FREE_SPACE\"}"
Custom Facts Deployment Playbook Scenario: #
Here’s a playbook to install the directory, copy custom facts, and trigger a system facts reload:
# 1. Prepare the custom facts storage directory on the managed node
- name: Ensure the facts.d directory is available
file:
path: /etc/ansible/facts.d
state: directory
owner: root
group: root
mode: '0755'
# 2. Copy the custom fact data file to the target server
- name: Deploy the static custom fact
copy:
src: files/app_info.fact
dest: /etc/ansible/facts.d/app_info.fact
owner: root
group: root
mode: '0644'
notify: Update System Facts
# 3. Trigger the update so Ansible reads the new variables right away
handlers:
- name: Update System Facts
setup:
filter: ansible_local
# ✓ The setup module is re-run specifically to load the ansible_local variables
Once the .fact file is installed, Ansible automatically reads it under the ansible_local namespace. You can access its values inside playbook tasks like this:
- name: Display application metadata from custom facts
debug:
msg: "Application {{ ansible_local.app_info.metadata.application }} version {{ ansible_local.app_info.metadata.version }}"
Performance Optimization: Disabling Gathering Facts #
Although facts are very useful, the fact-gathering process takes a fairly long time. For every target host, Ansible must make an SSH connection, transfer the setup module, execute it, and wait for the JSON payload to come back. This process takes an average of 1 to 3 seconds per server.
If you have a playbook that only does simple tasks (like distributing static files or triggering service restarts) without needing OS or memory condition evaluation, you’re strongly advised to disable the fact-gathering process to save execution time.
You can disable fact collection by adding the gather_facts: false parameter at the play level:
# Playbook with high-speed optimization without fact gathering
- name: Fast Static Image Asset Deployment
hosts: static_servers
gather_facts: false # ✓ Disables Gathering Facts (Saves initial SSH time)
tasks:
- name: Copy the promo banner file
copy:
src: files/promo.png
dest: /var/www/html/assets/promo.png
In large-scale production environments with hundreds to thousands of servers, writing gather_facts: false can cut total playbook execution waiting time by several minutes.
Fact Caching Configuration in ansible.cfg #
If you still need fact data for complex orchestration but want your playbook to run lightning fast without repeated SSH connection overhead, the best solution is enabling Fact Caching.
With Fact Caching, Ansible stores fact data collected on the first execution into external storage media. On subsequent executions, Ansible reads the facts directly from the cache without querying the target server.
Caching Backend Options #
jsonfile: Stores static JSON files in a local folder on the Control Node. Easy to configure, no extra dependency server needed, great for small teams.redis: Stores fact data in the memory of a centralized Redis server. Highly recommended for enterprise-scale teams due to incredibly fast memory read-write performance and high data consistency across runners.memcached: An alternative distributed memory-based backend for session caching management.
Here’s a flow diagram visualizing the fact-reading decision process using system caching:
flowchart TD
Start(["Start Playbook Run"]) --> CheckSmart{"Rule: gathering = smart?"}
CheckSmart -- "Yes" --> CheckCache{"Is Host X's Cache Available & Valid?"}
CheckSmart -- "No (gathering = implicit)" --> RunSetup["Run the setup module via SSH (Slow)"]
CheckCache -- "Yes (Not Expired)" --> LoadCache["Load Facts from Cache (Very Fast)"]
CheckCache -- "No (Expired / Empty)" --> RunSetup
RunSetup --> SaveCache["Save the New Facts Payload to the Cache Backend"]
SaveCache --> RunTasks["Continue Playbook Task Execution"]
LoadCache --> RunTasks
RunTasks --> End(["Playbook Run Finished"])To enable caching based on local JSON files, just add the following configuration to your project’s ansible.cfg file:
# FILE: ansible.cfg
[defaults]
# Set smart fact gathering (only if the cache is expired)
gathering = smart
# Use the local JSON file storage backend
fact_caching = jsonfile
# Determine the cache storage folder path on the Control Node
fact_caching_connection = .ansible_facts_cache
# Set the cache validity period (86400 seconds = 24 hours)
fact_caching_timeout = 86400
If you want to migrate to a centralized Redis database for large teams:
# FILE: ansible.cfg (Redis Enterprise Version)
[defaults]
gathering = smart
fact_caching = redis
# Connection format: host:port:db_index:password (optional)
fact_caching_connection = redis-server.internal.net:6379:0
fact_caching_timeout = 86400
Summary #
- System Discovery: Facts are automatic system discovery variables collected by the
setupmodule at the start of every play under theansible_*namespace.- Manual Setup Module: You can investigate available facts on target servers from the command line using the ad-hoc command
ansible <host> -m setup.- Cross-Host Variables: Use the
hostvars['host_name']['fact_name']syntax to access fact data from other servers for upstream/cluster integration needs.- Environment Variables: The
ansible_envdictionary provides direct access to all environment variables (likeHOME,PATH) of the SSH user on the managed node.- Custom Facts: You can deploy
.factextension files to the/etc/ansible/facts.d/directory to define custom metadata under theansible_localnamespace.- Performance Optimization: Use the
gather_facts: falseparameter on playbooks that don’t need system evaluation to save SSH handshake time.- Fact Caching: Enable the caching feature (
jsonfileorredis) inansible.cfgto avoid repeatedly calling thesetupmodule on the same target servers.