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
| Field | Type | Description |
|---|---|---|
stdout | string | Standard output (one string) |
stdout_lines | list | Standard output (list of lines) |
stderr | string | Standard error |
stderr_lines | list | Standard error (list of lines) |
rc | int | Return code (0 = success) |
changed | bool | Whether the task changed anything |
failed | bool | Whether the task failed |
skipped | bool | Whether the task was skipped |
msg | string | Module message (varies by module) |
results | list | Loop 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!
Related Articles
- Ansible debug Module
- Ansible when Conditional
- Ansible set_fact Module
- Ansible Loop Guide
- Ansible shell vs command
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.