Your playbook uses delegate_to to run a task on a different host, but it fails with connection errors or unexpected behavior. Delegation is powerful for orchestrating tasks across hosts, but configuration errors can be tricky.
Understanding the Error
Delegate_to errors appear as:
fatal: [webserver01]: FAILED! => {"msg": "Failed to connect to delegated host 'localhost' via ssh"}Or:
fatal: [webserver01]: FAILED! => {"msg": "delegate_host is not a valid hostname"}Or:
fatal: [webserver01 -> localhost]: UNREACHABLE! => {"msg": "..."}Step 1: Understand Delegate_to Basics
delegate_to runs a task on a different host while keeping the context:
- name: Task on webserver01 but runs on localhost
command: ssh webserver01 "uptime"
delegate_to: localhostThe task appears in the play for webserver01 but actually executes on localhost.
Common uses:
```yaml # Run on control node - name: Local check command: curl http://{{ inventory_hostname }} delegate_to: localhost
# Run on specific host - name: Database check command: mysql -h {{ inventory_hostname }} -e "SHOW STATUS" delegate_to: dbserver01 ```
Step 2: Fix Connection Issues
Delegated hosts must be reachable:
# Verify delegated host in inventory
ansible-inventory -i inventory --host localhost
ansible-inventory -i inventory --host delegate_hostIf delegated host not in inventory:
# Add delegated host
[delegates]
localhost ansible_connection=local
delegate_host ansible_host=192.168.1.50Or use local connection:
- name: Local task
command: echo "local"
delegate_to: localhost
connection: localStep 3: Handle localhost Delegation
For localhost tasks, ensure localhost is defined:
# WRONG - localhost not properly configured
- name: Local task
command: echo "test"
delegate_to: localhost
# Fails if localhost not in inventoryConfigure localhost in inventory:
```ini [all:vars] ansible_connection=ssh
[local] localhost ansible_connection=local ```
Or use implicit localhost:
# ansible.cfg
[defaults]
localhost_vars = ansible_connection=localStep 4: Fix Variable Context
Variables in delegated tasks use different context:
- name: Task with variables
debug:
msg: "{{ ansible_hostname }}"
delegate_to: other_hostThis shows ansible_hostname from the delegated host, not the original host.
Use delegate_facts to change fact context:
- name: Get facts from target
setup:
delegate_to: other_host
delegate_facts: TrueTo use original host facts in delegated task:
- name: Use original host facts
debug:
msg: "{{ hostvars[inventory_hostname].ansible_hostname }}"
delegate_to: localhostStep 5: Handle Inventory_hostname vs Delegated Host
In delegated tasks, inventory_hostname still refers to original host:
- hosts: webservers
tasks:
- name: Show hostnames
debug:
msg: "Original: {{ inventory_hostname }} Delegated: {{ ansible_hostname }}"
delegate_to: localhostOutput:
Original: webserver01 Delegated: localhostUse delegate_to with inventory_hostname correctly:
- name: Check remote host from localhost
uri:
url: "http://{{ inventory_hostname }}:8080/health"
delegate_to: localhostStep 6: Fix Become with Delegate_to
Privilege escalation on delegated host:
- name: Task needing sudo on delegated host
command: /opt/admin/check.sh
delegate_to: admin_host
become: yes
become_user: adminThis uses become on admin_host, not the original host.
If become fails on delegated host:
# Test sudo on delegated host
ssh admin_host 'sudo whoami'Configure become for delegated host:
- name: Task with delegation
command: /opt/admin/check.sh
delegate_to: admin_host
become: yes
become_method: sudoStep 7: Handle Multiple Delegations
Delegation to multiple hosts:
- name: Check each from localhost
command: curl http://{{ item }}/health
delegate_to: localhost
loop: "{{ groups['webservers'] }}"Or delegate to each host:
- name: Run on each webserver from dbserver
command: ssh {{ item }} "uptime"
delegate_to: dbserver01
loop: "{{ groups['webservers'] }}"Step 8: Fix Connection Parameters
Delegated host might need different connection:
```yaml - name: Task on Windows host win_command: whoami delegate_to: windows_host
- name: Task with specific SSH port
- command: uptime
- delegate_to: "{{ delegate_host }}"
- vars:
- ansible_port: 2222
`
Or use delegate_to with inventory vars:
- name: Task
command: uptime
delegate_to: bastion_host
# bastion_host connection from its inventory entryStep 9: Handle Run_once with Delegate_to
run_once with delegate_to runs on one host only:
- name: Single task on localhost
command: /opt/admin/init.sh
delegate_to: localhost
run_once: yesThis runs once on localhost, regardless of how many hosts in play.
Step 10: Debug Delegation Issues
Verbose output shows delegation:
ansible-playbook playbook.yml -vvLook for:
TASK [Delegated task] ***********************************************************
changed: [webserver01 -> localhost] => ...Debug delegation context:
- name: Debug context
debug:
msg: |
inventory_hostname: {{ inventory_hostname }}
delegated host: {{ ansible_hostname }}
connection: {{ ansible_connection }}
delegate_to: localhostStep 11: Handle Failed Delegation Recovery
If delegation fails:
```yaml - name: Task with fallback command: /opt/check.sh delegate_to: primary_host ignore_errors: yes register: result
- name: Fallback task
- command: /opt/check.sh
- delegate_to: backup_host
- when: result.failed
`
Step 12: Fix Common Delegate_to Patterns
Bastion host pattern:
- name: Access through bastion
command: ssh {{ inventory_hostname }} "uptime"
delegate_to: bastion_hostLocal orchestration:
- name: Local orchestration task
command: ansible-inventory --list
delegate_to: localhost
run_once: yesDatabase operations:
- name: Database task
mysql_query:
query: "SELECT * FROM users"
login_host: "{{ inventory_hostname }}"
delegate_to: db_admin_hostQuick Verification
Test delegation:
- hosts: localhost
gather_facts: no
tasks:
- name: Test delegation
debug:
msg: "Running on {{ ansible_hostname }} for {{ inventory_hostname }}"
delegate_to: localhostRun it:
ansible-playbook delegate_test.ymlSuccess:
TASK [Test delegation] ***********************************************************
ok: [localhost -> localhost] => {
"msg": "Running on localhost for localhost"
}Prevention Best Practices
- 1.Ensure delegated host in inventory:
[local]
localhost ansible_connection=local- 1.**Use
connection: localfor localhost:**
delegate_to: localhost
connection: local- 1.Test delegation connectivity:
ansible delegated_host -m ping- 1.Understand variable context:
# Use hostvars for original host facts
msg: "{{ hostvars[inventory_hostname].ansible_facts }}"- 1.Configure become for delegated host:
delegate_to: admin_host
become: yesDelegate_to failures usually stem from unreachable delegated hosts, missing inventory entries, or variable context confusion. Ensure delegated hosts are configured, test connectivity, and understand which facts apply in the delegated context.