Ansible Cheatsheet
On this page
CLI one-liners
# Playbook run
ansible-playbook -i inventories/production/ site.yml
ansible-playbook site.yml --check --diff # dry-run + diff
ansible-playbook site.yml -l web01 # limit to one host
ansible-playbook site.yml -l 'web:&prod' # web AND prod
ansible-playbook site.yml --tags nginx,config
ansible-playbook site.yml --skip-tags verify
ansible-playbook site.yml --start-at-task "Deploy config"
ansible-playbook site.yml --step # prompt per task
ansible-playbook site.yml -e "version=1.2.3" # extra vars (wins)
ansible-playbook site.yml -e @vars.yml # extra vars from file
ansible-playbook site.yml --ask-vault-pass
# Ad-hoc one-shot
ansible -i inv all -m ping
ansible -i inv web -m shell -a 'systemctl is-active nginx' -b
# Inventory introspection
ansible-inventory -i inv --list
ansible-inventory -i inv --host web01
ansible-inventory -i inv --graph
# List what a playbook would do
ansible-playbook site.yml --list-hosts
ansible-playbook site.yml --list-tasks
ansible-playbook site.yml --list-tags
# Vault
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-vault view secrets.yml
ansible-vault encrypt_string 's3cret' --name 'api_token'
ansible-vault rekey secrets.yml
# Galaxy / collections
ansible-galaxy collection install -r collections/requirements.yml
ansible-galaxy role install -r roles/requirements.yml
ansible-galaxy collection list
# Lint / syntax
ansible-playbook site.yml --syntax-check
ansible-lint
yamllint .
Ad-hoc modules
ansible all -m ping
ansible all -m setup # gather all facts
ansible all -m setup -a 'filter=ansible_distribution*'
ansible all -m command -a 'uptime' # no shell features
ansible all -m shell -a 'ps aux | grep nginx' # pipes/redirects OK
ansible all -m copy -a 'src=f.conf dest=/etc/f.conf mode=0644' -b
ansible all -m file -a 'path=/srv/app state=directory mode=0755' -b
ansible all -m service -a 'name=nginx state=restarted' -b
ansible all -m package -a 'name=htop state=present' -b
ansible all -m user -a 'name=deploy shell=/bin/bash groups=sudo append=yes' -b
ansible all -m lineinfile -a 'path=/etc/hosts line="10.0.0.5 api"' -b
ansible all -m uri -a 'url=http://localhost:8080/healthz return_content=yes'
Playbook boilerplate
---
- name: Configure web tier
hosts: web
become: true
gather_facts: true
vars:
app_port: 8080
vars_files:
- vars/common.yml
- vars/secrets.yml # vault-encrypted
pre_tasks:
- name: Assert required vars are set
ansible.builtin.assert:
that:
- app_version is defined
roles:
- role: baseline
- role: nginx
tags: [nginx]
tasks:
- name: Inline extra step
ansible.builtin.debug:
msg: "Deployed {{ app_version }}"
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
Task boilerplate
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
validate: 'nginx -t -c %s'
become: true
tags: [nginx, config]
when: ansible_os_family == 'RedHat'
notify: Restart nginx
Handler boilerplate
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
listen: "restart web" # any task can notify "restart web"
- name: Reload firewall
ansible.builtin.service:
name: firewalld
state: reloaded
# Force handlers to run mid-play
- name: Flush handlers now
ansible.builtin.meta: flush_handlers
when / register / failed_when / changed_when
- name: Is nginx running?
ansible.builtin.command: systemctl is-active nginx
register: nginx_state
changed_when: false # command modules always report 'changed' otherwise
failed_when: false # don't fail the play if inactive
- name: Restart only if nginx isn't running
ansible.builtin.service:
name: nginx
state: started
when: nginx_state.stdout != 'active'
- name: Skip on Debian
ansible.builtin.package:
name: httpd
state: present
when: ansible_os_family != 'Debian'
- name: Multiple conditions
ansible.builtin.debug:
msg: "production web host"
when:
- inventory_hostname in groups['web']
- env == 'production'
- name: Conditional with a registered var
ansible.builtin.debug:
msg: "healthcheck passed"
when:
- health.status == 200
- health.json.ok | default(false) | bool
Loops
- name: Install baseline packages
ansible.builtin.package:
name: "{{ item }}"
state: present
loop:
- htop
- jq
- curl
- name: Shortcut — package module accepts a list directly
ansible.builtin.package:
name: [htop, jq, curl]
state: present
- name: Loop with dicts and a custom label
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
append: true
loop:
- { name: alice, groups: [sudo, docker] }
- { name: bob, groups: [sudo] }
loop_control:
label: "{{ item.name }}" # prettier output
- name: Loop until condition met (retry)
ansible.builtin.uri:
url: http://localhost:8080/healthz
register: hc
until: hc.status == 200
retries: 10
delay: 3
# Legacy — prefer loop: in new code
- ansible.builtin.package:
name: "{{ item }}"
with_items: [htop, jq]
Variable precedence (short form)
Lowest to highest, simplified. Later beats earlier:
role defaults/main.ymlinventoryvariables (host + group vars in inventory file)inventory group_vars/allinventory group_vars/<group>playbook group_vars/<group>playbook host_vars/<host>host facts(gathered or cached)- Play
vars:,vars_files:,vars_prompt: - Task
vars: - Role
vars/main.yml block varsset_fact/include_vars- Extra vars (
-e) — always wins
Full 22-position list with caveats on Variable Precedence.
Jinja2 filters — most used
{{ myvar | default('fallback') }}
{{ myvar | default(omit) }} # omit the param entirely
{{ users | map(attribute='name') | list }}
{{ users | selectattr('active', 'true') | list }}
{{ users | rejectattr('locked') | list }}
{{ dict1 | combine(dict2, recursive=True) }}
{{ mylist | length }}
{{ mylist | unique }}
{{ mylist | join(', ') }}
{{ mylist | flatten(levels=1) }}
{{ path | basename }}
{{ path | dirname }}
{{ path | realpath }}
{{ 'Hello {name}' | format(name='world') }}
{{ 'abc' | regex_replace('^a', 'A') }}
{{ 'abc' | regex_search('b.') }}
{{ data | to_nice_yaml(indent=2) }}
{{ data | to_json }}
{{ raw | from_yaml }}
{{ raw | from_json }}
{{ password | password_hash('sha512') }} # for user.password
{{ 'file.txt' | hash('sha256') }}
{{ ip | ipaddr('network') }} # needs ansible.utils / community.general
Vault
# Create / edit an encrypted file
ansible-vault create group_vars/prod/vault.yml
ansible-vault edit group_vars/prod/vault.yml
# Encrypt / decrypt files in place
ansible-vault encrypt secrets.yml
ansible-vault decrypt secrets.yml
# Inline encrypted string
ansible-vault encrypt_string --vault-id prod@prompt \
's3cret' --name 'api_token'
# → paste the output directly into a YAML var file
# Multiple vault IDs (dev and prod secrets in the same repo)
ansible-playbook site.yml \
--vault-id dev@~/.vault/dev.txt \
--vault-id prod@~/.vault/prod.txt
# Vault password from a file (CI-friendly)
ansible-playbook site.yml --vault-password-file ~/.vault/prod.txt
# Recommended layout
group_vars/
├── all/
│ ├── vars.yml # plain
│ └── vault.yml # encrypted; reference via vault_* vars
Inventory patterns
# Target expressions for -l / hosts:
all # every host
web # group 'web'
web,db # union
'web:db' # union (colon form)
'web:&prod' # intersection: web AND prod
'web:!stage' # difference: web but not stage
'web[0]' # first host in group
'web[0:2]' # first three hosts
'~web\d+\.example\.com' # regex (~ prefix)
# INI inventory with groups
[web]
web01.example.com
web02.example.com
[db]
db01.example.com
[prod:children]
web
db
[web:vars]
ansible_user=deploy
# YAML inventory equivalent
---
all:
children:
prod:
children:
web:
hosts:
web01.example.com: {}
web02.example.com: {}
db:
hosts:
db01.example.com: {}
vars:
env: production
Tags
# On tasks
- name: Deploy config
ansible.builtin.template: { src: x.j2, dest: /etc/x }
tags: [nginx, config]
# On a play (applies to all tasks in the play)
- hosts: web
tags: [web]
tasks: …
# On a role call
- role: nginx
tags: [nginx]
# CLI
ansible-playbook site.yml --tags nginx
ansible-playbook site.yml --tags "nginx,config"
ansible-playbook site.yml --skip-tags verify
ansible-playbook site.yml --list-tags
# Special tags
tags: always # runs even if --tags specified (unless --skip-tags always)
tags: never # never runs unless explicitly requested via --tags
# useful for destructive one-off tasks
Full semantics on Tags, including import_role vs include_role.
Debug flags
-v # show task results
-vv # + input data
-vvv # + connection details (SSH debug)
-vvvv # + all the things; use sparingly
--check # dry-run — no changes made
--diff # show file-level diffs
--check --diff # combine — the before-you-apply pair
--start-at-task "X" # begin from a named task
--step # prompt before each task (y/n/c)
--list-hosts # who would this play target?
--list-tasks # what tasks would run?
--list-tags # what tags are defined?
-e '{"debug":true}' # extra var as JSON (can be wins-all)
Common errors → cause
| Error fragment | Cause |
|---|---|
UNREACHABLE! ... ssh: Permission denied (publickey) | SSH key/user wrong — check ansible_user and key in ssh-agent |
UNREACHABLE! ... Connection timed out | Host down, firewall, wrong hostname/IP, SSH port not 22 |
FAILED! ... Missing sudo password | become_method needs password — use --ask-become-pass or passwordless sudo |
FAILED! ... The conditional check ... failed | Referencing a variable that doesn't exist — use default() |
ERROR! 'dict object' has no attribute 'x' | Expected key missing from a dict var — add | default({}) or guard with is defined |
ERROR! Vault format unhexlify error | Wrong vault password, or mixed vault IDs with no --vault-id |
ERROR! We were unable to read either as JSON nor YAML | YAML syntax error — run ansible-playbook --syntax-check and yamllint |
skipping: no hosts matched | -l pattern too restrictive, or inventory group empty |
fatal: ... MODULE FAILURE with no other info | Run with -vvv — usually missing python deps or SELinux on remote |
Related: Quickstart for a beginner walkthrough, Debugging for deeper troubleshooting, Variable Precedence for the full 22-position list.