What Is Ansible register?

Ansible register is a task keyword that captures a task's output into a variable. You can then use that variable in conditions (when), display it (debug), or pass it to other tasks. Every task produces a result object — register saves it for later use.

Basic Usage

- name: Get hostname
  ansible.builtin.command: hostname
  register: hostname_result

- name: Show the result
  ansible.builtin.debug:
    var: hostname_result

Output structure:

{
  "changed": true,
  "stdout": "web-01",
  "stdout_lines": ["web-01"],
  "stderr": "",
  "stderr_lines": [],
  "rc": 0,
  "cmd": ["hostname"],
  "start": "2026-04-12 10:00:00",
  "end": "2026-04-12 10:00:00",
  "delta": "0:00:00.003"
}

Access Result Fields

- name: Run a command
  ansible.builtin.command: cat /etc/os-release
  register: os_info

# Access specific fields
- debug:
    msg: "Output: {{ os_info.stdout }}"

- debug:
    msg: "Lines: {{ os_info.stdout_lines }}"

- debug:
    msg: "Return code: {{ os_info.rc }}"

- debug:
    msg: "Errors: {{ os_info.stderr }}"

- debug:
    msg: "Changed: {{ os_info.changed }}"

Common Result Fields

FieldTypeDescription
stdoutstringStandard output (one string)
stdout_lineslistStandard output (list of lines)
stderrstringStandard error
stderr_lineslistStandard error (list of lines)
rcintReturn code (0 = success)
changedboolWhether the task changed anything
failedboolWhether the task failed
skippedboolWhether the task was skipped
msgstringModule message (varies by module)
resultslistLoop results (when used with loop)

Different modules return different fields:

# stat module
- ansible.builtin.stat:
    path: /etc/nginx/nginx.conf
  register: nginx_conf
# nginx_conf.stat.exists, nginx_conf.stat.size, nginx_conf.stat.mode

# uri module
- ansible.builtin.uri:
    url: http://localhost:8080/api/status
  register: api_response
# api_response.status, api_response.json, api_response.content

# apt module
- ansible.builtin.apt:
    name: nginx
  register: apt_result
# apt_result.changed, apt_result.cache_updated

Use with Conditions

# Check command exit code
- name: Check if app is running
  ansible.builtin.command: systemctl is-active myapp
  register: app_status
  ignore_errors: true

- name: Start app if not running
  ansible.builtin.service:
    name: myapp
    state: started
  when: app_status.rc != 0

# Check stdout content
- name: Get current version
  ansible.builtin.command: /opt/app/bin/version
  register: version

- name: Upgrade if outdated
  ansible.builtin.shell: /opt/app/upgrade.sh
  when: "'2.0' not in version.stdout"

# Check if file exists
- name: Check config
  ansible.builtin.stat:
    path: /etc/myapp/config.yml
  register: config_file

- name: Create default config
  ansible.builtin.template:
    src: config.yml.j2
    dest: /etc/myapp/config.yml
  when: not config_file.stat.exists

Use with set_fact

- name: Get disk usage
  ansible.builtin.shell: df -h / | tail -1 | awk '{print $5}' | tr -d '%'
  register: disk_cmd

- name: Store as fact
  ansible.builtin.set_fact:
    disk_usage_percent: "{{ disk_cmd.stdout | int }}"

- name: Alert if disk full
  ansible.builtin.debug:
    msg: "WARNING: Disk at {{ disk_usage_percent }}%!"
  when: disk_usage_percent | int > 80

Register with Loops

When using register with a loop, the result contains a results list:

- name: Check services
  ansible.builtin.command: "systemctl is-active {{ item }}"
  loop:
    - nginx
    - redis
    - postgresql
  register: service_checks
  ignore_errors: true

# service_checks.results is a list of individual results
- name: Show failed services
  ansible.builtin.debug:
    msg: "{{ item.item }} is DOWN (rc={{ item.rc }})"
  loop: "{{ service_checks.results }}"
  when: item.rc != 0

# Access specific result
- debug:
    msg: "First service: {{ service_checks.results[0].item }} — rc={{ service_checks.results[0].rc }}"

Register with ignore_errors

# Without ignore_errors — playbook stops on failure
- ansible.builtin.command: /opt/check.sh
  register: result
  # If this fails, playbook STOPS here

# With ignore_errors — continues and you can check the result
- ansible.builtin.command: /opt/check.sh
  register: result
  ignore_errors: true

- name: Handle failure
  ansible.builtin.debug:
    msg: "Check failed: {{ result.stderr }}"
  when: result.failed

- name: Handle success
  ansible.builtin.debug:
    msg: "Check passed: {{ result.stdout }}"
  when: not result.failed

Practical Patterns

Check Before Acting

- name: Check if Docker is installed
  ansible.builtin.command: docker --version
  register: docker_check
  ignore_errors: true
  changed_when: false

- name: Install Docker
  ansible.builtin.apt:
    name: docker-ce
  when: docker_check.rc != 0

Parse JSON Output

- name: Get container info
  ansible.builtin.command: docker inspect myapp
  register: container_info

- name: Parse JSON
  ansible.builtin.set_fact:
    container_ip: "{{ (container_info.stdout | from_json)[0].NetworkSettings.IPAddress }}"

- debug:
    msg: "Container IP: {{ container_ip }}"

Collect Info from Multiple Hosts

- name: Get hostname on each host
  ansible.builtin.command: hostname -f
  register: fqdn_result

# In a later play, access other hosts' results:
- debug:
    msg: "Web server FQDN: {{ hostvars['web-01']['fqdn_result']['stdout'] }}"

Wait for Condition

- name: Wait for app to start
  ansible.builtin.uri:
    url: http://localhost:8080/health
    status_code: 200
  register: health
  until: health.status == 200
  retries: 30
  delay: 5

changed_when and failed_when

Control how Ansible interprets the result:

# Read-only command — never "changed"
- ansible.builtin.command: cat /etc/hostname
  register: hostname
  changed_when: false

# Custom change detection
- ansible.builtin.shell: /opt/app/deploy.sh
  register: deploy
  changed_when: "'deployed' in deploy.stdout"

# Custom failure detection
- ansible.builtin.command: /opt/app/check.sh
  register: check
  failed_when: check.rc > 1    # rc=1 is warning, rc>1 is failure

Troubleshooting

"undefined variable" After register

The variable is only set on hosts where the task ran. If the task was skipped (when condition), the variable isn't set:

- command: whoami
  register: result
  when: false         # Task skipped!

- debug:
    msg: "{{ result.stdout }}"  # ERROR: result is undefined

# Fix: check if defined
- debug:
    msg: "{{ result.stdout }}"
  when: result is defined

Register Scope

Registered variables persist for the rest of the playbook for that host, across plays:

- hosts: all
  tasks:
    - command: hostname
      register: my_hostname

- hosts: all
  tasks:
    - debug:
        msg: "{{ my_hostname.stdout }}"  # Still available!

Conclusion

register captures any task's output for later use. Access stdout, rc, changed, and module-specific fields. Combine with when for conditional logic, ignore_errors for error handling, and changed_when/failed_when for custom result interpretation. With loops, results are stored in a .results list. Always use changed_when: false for read-only commands to keep playbook output accurate.