Introduction

Ansible magic variables are special built-in variables automatically set by Ansible during execution. They provide information about the current host, play, inventory, and execution environment — no need to define them yourself.

Host Variables

inventory_hostname

The name of the current host as defined in inventory (not the actual hostname):

- debug:
    msg: "Running on {{ inventory_hostname }}"
# Output: "Running on web-01"

# Use in templates
- template:
    src: config.j2
    dest: "/etc/myapp/{{ inventory_hostname }}.conf"

inventory_hostname_short

Short version (everything before the first dot):

# If inventory_hostname = "web-01.example.com"
- debug:
    msg: "{{ inventory_hostname_short }}"
# Output: "web-01"

ansible_host

The actual IP or hostname to connect to:

- debug:
    msg: "Connecting to {{ ansible_host }}"
# Output: "Connecting to 192.168.1.10"

hostvars

Access variables from ANY host in the inventory:

# Get DB server's IP
- debug:
    msg: "DB IP: {{ hostvars['db-01']['ansible_host'] }}"

# Access facts from another host
- debug:
    msg: "DB memory: {{ hostvars[groups['dbservers'][0]]['ansible_memtotal_mb'] }}"

# Use in templates (common pattern)
# templates/haproxy.cfg.j2
{% for host in groups['webservers'] %}
server {{ host }} {{ hostvars[host]['ansible_host'] }}:8080 check
{% endfor %}

Group Variables

groups

Dictionary of all groups and their hosts:

- debug:
    msg: "{{ groups['webservers'] }}"
# Output: ["web-01", "web-02", "web-03"]

- debug:
    msg: "All hosts: {{ groups['all'] }}"

# Loop over group members
- name: Configure upstream
  template:
    src: upstream.conf.j2
    dest: /etc/nginx/upstream.conf
  # Template uses: {% for host in groups['webservers'] %}

group_names

List of groups the current host belongs to:

- debug:
    msg: "{{ inventory_hostname }} is in groups: {{ group_names }}"
# Output: "web-01 is in groups: ['webservers', 'production']"

# Conditional based on group membership
- name: Install monitoring agent
  apt:
    name: datadog-agent
  when: "'production' in group_names"

- name: Enable debug logging
  template:
    src: debug-logging.conf.j2
    dest: /etc/myapp/logging.conf
  when: "'staging' in group_names"

Play Variables

ansible_play_hosts

All hosts in the current play (after limits/filters):

- debug:
    msg: "Play targets {{ ansible_play_hosts | length }} hosts"

# Check if we're the last host
- name: Final aggregation
  include_tasks: aggregate.yml
  when: inventory_hostname == ansible_play_hosts[-1]

ansible_play_hosts_all

All hosts targeted by the play (before any failures):

- debug:
    msg: "Originally targeted {{ ansible_play_hosts_all | length }} hosts"

play_hosts (deprecated)

Use ansible_play_hosts instead.

ansible_play_batch

Current batch of hosts in serial execution:

# With serial: 3, shows current batch of 3 hosts
- debug:
    msg: "Current batch: {{ ansible_play_batch }}"

Path Variables

playbook_dir

Directory of the playbook being executed:

- debug:
    msg: "Playbook is at: {{ playbook_dir }}"
# Output: "/home/user/ansible/playbooks"

# Read file relative to playbook
- copy:
    src: "{{ playbook_dir }}/files/config.yml"
    dest: /etc/myapp/config.yml

inventory_dir / inventory_file

Path to the inventory:

- debug:
    msg: "Inventory: {{ inventory_dir }}/{{ inventory_file }}"

role_path

Current role's directory (only available inside a role):

# Inside a role
- copy:
    src: "{{ role_path }}/files/custom.conf"
    dest: /etc/myapp/custom.conf

ansible_search_path

List of paths Ansible searches for files/templates:

- debug: var=ansible_search_path

Execution Variables

ansible_check_mode

Whether running in check mode (--check):

- name: Run only in check mode
  debug:
    msg: "Dry run — no changes will be made"
  when: ansible_check_mode

- name: Skip in check mode
  shell: dangerous_command.sh
  when: not ansible_check_mode

ansible_diff_mode

Whether running with --diff:

- debug:
    msg: "Diff mode enabled"
  when: ansible_diff_mode

ansible_version

Ansible version info:

- debug:
    msg: "Ansible {{ ansible_version.full }}"
# Output: "Ansible 2.17.0"

- assert:
    that: ansible_version.full is version('2.14', '>=')
    msg: "Requires Ansible 2.14+"

ansible_verbosity

Current verbosity level (0-4):

- debug:
    msg: "Extra debugging info..."
  when: ansible_verbosity >= 2    # Only shows with -vv or higher

Task Variables

ansible_loop

Loop metadata (when using loop):

- name: Show loop info
  debug:
    msg: "Item {{ ansible_loop.index }} of {{ ansible_loop.length }}: {{ item }}"
  loop: [a, b, c]
  loop_control:
    extended: true
VariableDescription
ansible_loop.indexCurrent iteration (1-based)
ansible_loop.index0Current iteration (0-based)
ansible_loop.firstTrue on first iteration
ansible_loop.lastTrue on last iteration
ansible_loop.lengthTotal items
ansible_loop.revindexIterations remaining (1-based)

ansible_failed_result

The result of the failed task (in rescue blocks):

- block:
    - command: /bin/false
  rescue:
    - debug:
        msg: "Failed: {{ ansible_failed_result.msg }}"

Connection Variables

ansible_connection

Connection type: ssh, local, winrm, docker, etc.

ansible_user

SSH user for the current host.

ansible_become

Whether privilege escalation is active.

Quick Reference Table

VariableDescription
inventory_hostnameHost name from inventory
inventory_hostname_shortShort hostname
ansible_hostActual connection address
hostvarsAll host variables (dict)
groupsAll groups and hosts (dict)
group_namesGroups current host belongs to
ansible_play_hostsActive hosts in current play
playbook_dirPlaybook directory path
role_pathCurrent role directory
ansible_check_modeTrue if --check
ansible_diff_modeTrue if --diff
ansible_versionAnsible version info
ansible_verbosityVerbosity level (0-4)
ansible_loopLoop metadata
ansible_failed_resultFailed task result (rescue)
inventory_dirInventory directory
ansible_config_fileConfig file path

Practical Examples

Generate Cluster Config

# templates/cluster.conf.j2
[cluster]
name = {{ group_names[0] }}
node_count = {{ groups[group_names[0]] | length }}

{% for host in groups[group_names[0]] %}
[node.{{ host }}]
address = {{ hostvars[host]['ansible_host'] }}
{% endfor %}

Rolling Deploy with Position Awareness

- name: Deploy with awareness
  debug:
    msg: >
      Host {{ ansible_loop.index }}/{{ ansible_play_hosts | length }}
      ({{ inventory_hostname }})
      {% if inventory_hostname == ansible_play_hosts[0] %}— FIRST{% endif %}
      {% if inventory_hostname == ansible_play_hosts[-1] %}— LAST{% endif %}

Conclusion

Magic variables give you runtime context without any configuration. The most important ones: inventory_hostname (who am I), hostvars (access any host's data), groups/group_names (group membership), playbook_dir (file paths), and ansible_check_mode (dry run detection). Master these and you can write playbooks that are aware of their execution environment and adapt dynamically.