The Problem
Your playbook uses local_action to run a task on the Ansible control node, but it fails:
fatal: [server]: FAILED! => {"msg": "Failed to connect to the host via ssh: ssh: connect to host localhost port 22: Connection refused"}Or:
fatal: [server]: FAILED! => {"msg": "Invalid data passed to 'local_action', it requires a dictionary, got a string"}Or the task runs on the remote host instead of locally.
Why This Happens
local_action errors stem from:
SSH connection to localhost - Ansible tries SSH instead of local execution.
Wrong syntax format - Using incorrect local_action syntax (old vs new).
Missing local connection config - localhost not configured for local connection.
Environment variable issues - Variables not available in local context.
Permission issues - Running as wrong user locally.
Understanding local_action
local_action runs a task on the Ansible control node (where ansible-playbook runs) instead of the target host:
- hosts: webservers
tasks:
- name: Run locally
local_action: command whoami
# Runs on control node, not webserverModern equivalent using delegate_to:
- name: Run locally
command: whoami
delegate_to: localhost
connection: localDiagnosing the Issue
Check localhost configuration:
ansible localhost -m ping
ansible localhost -m setupCheck connection type:
ansible-config dump | grep connectionDebug local execution:
- name: Debug local action
local_action: command hostname
register: local_host
- debug:
msg: "Ran on: {{ local_host.stdout }}"The Fix
Fix 1: Configure localhost for Local Connection
Add localhost to inventory with local connection:
# inventory.yml
all:
hosts:
localhost:
ansible_connection: localOr in ansible.cfg:
[defaults]
# Default connection for localhost
localhost_connection = localFix 2: Use Correct local_action Syntax
Old vs new syntax:
```yaml # OLD SYNTAX (still works) - name: Old style local action local_action: command hostname
# NEW SYNTAX (recommended) - name: New style local action command: hostname delegate_to: localhost connection: local ```
For module with arguments:
```yaml # OLD SYNTAX - can be confusing - name: Old style with args local_action: module: copy src: files/config.conf dest: /tmp/config.conf
# NEW SYNTAX - clearer - name: New style with args copy: src: files/config.conf dest: /tmp/config.conf delegate_to: localhost connection: local ```
Fix 3: Fix SSH Connection Errors
If Ansible tries SSH to localhost:
```yaml # WRONG - SSH connection fails - hosts: webservers tasks: - name: Local task without connection command: hostname delegate_to: localhost # Ansible may try SSH to localhost
# CORRECT - explicit local connection - hosts: webservers tasks: - name: Local task with local connection command: hostname delegate_to: localhost connection: local ```
Fix 4: Handle Variable Context
Variables in local context may differ:
```yaml - hosts: webservers tasks: - name: Use inventory_hostname locally debug: msg: "Processing {{ inventory_hostname }}" delegate_to: localhost connection: local # inventory_hostname is still webserver, not localhost
- name: Use local facts
- debug:
- msg: "{{ ansible_facts }}"
- delegate_to: localhost
- connection: local
- # ansible_facts are from localhost, not webserver
`
Gather facts locally if needed:
```yaml - hosts: webservers tasks: - name: Gather localhost facts setup: delegate_to: localhost delegate_facts: yes connection: local
- name: Use localhost facts
- debug:
- msg: "{{ hostvars['localhost']['ansible_facts'] }}"
`
Fix 5: Common local_action Use Cases
Git operations on control node: ```yaml - hosts: webservers tasks: - name: Clone repo locally git: repo: https://github.com/example/repo.git dest: /tmp/repo delegate_to: localhost connection: local
- name: Copy to remote
- synchronize:
- src: /tmp/repo/
- dest: /opt/app/
`
API calls from control node:
``yaml
- hosts: webservers
tasks:
- name: Register with API
uri:
url: https://api.example.com/register
method: POST
body:
host: "{{ inventory_hostname }}"
delegate_to: localhost
connection: local
File operations locally: ```yaml - hosts: webservers tasks: - name: Generate config locally template: src: templates/app.conf.j2 dest: /tmp/app-{{ inventory_hostname }}.conf delegate_to: localhost connection: local
- name: Upload to server
- copy:
- src: /tmp/app-{{ inventory_hostname }}.conf
- dest: /etc/app/app.conf
`
Fix 6: Handle Permission Issues
Run local tasks as correct user:
- name: Local task as specific user
command: whoami
delegate_to: localhost
connection: local
become: yes
become_user: rootOr use ansible_user for localhost:
# inventory.yml
all:
hosts:
localhost:
ansible_connection: local
ansible_user: "{{ lookup('env', 'USER') }}"Fix 7: local_action with run_once
Combine local_action with run_once:
- hosts: webservers
tasks:
- name: One-time local setup
command: /opt/prepare.sh
delegate_to: localhost
connection: local
run_once: yes
# Runs once locally, not per webserverVerifying the Fix
Test local_action:
```yaml # test_local.yml - hosts: localhost connection: local gather_facts: no tasks: - name: Test local action command: hostname register: result
- name: Verify
- debug:
- msg: "Hostname: {{ result.stdout }}"
`
Run:
ansible-playbook test_local.yml -vExpected:
``
TASK [Test local action] ***********************************************************
changed: [localhost]
TASK [Verify] ***********************************************************
ok: [localhost] => {"msg": "Hostname: ansible-control-node"}
Prevention
Add localhost to inventory always:
```yaml # inventory.yml all: hosts: localhost: ansible_connection: local
webservers: hosts: web1: web2: ```
Use explicit connection:
- name: Any local task
command: something
delegate_to: localhost
connection: local # Always specify