Guide · Ansible · about 8 minutes

Ansible cheatsheet: commands by task

Every command and playbook pattern from the Ansible basics track on one page, grouped by the job you're doing: reaching servers, inventory, playbooks, variables, templates, loops and failures, roles, Vault, patching, testing and rebuilding. Plus what Ansible's common errors mean, how to read a recap, and the Ansible for each bash habit. Each row links to the lesson that explains it.

Last checked November 2026

How to use this page

Find the job you're trying to do, and copy the pattern. Words in capitals, like HOST, GROUP or FILE, are yours to replace. Every row links to the lesson that explains it, with real output from the practice servers. Use the print button at the top to keep a copy next to you.

Three habits that make everything here easier:

  • Dry run first. ansible-playbook site.yml --check --diff shows what would change, and changes nothing. A failure in --check that depends on an earlier change is expected: the earlier change never really happened.
  • Read the recap, not the scroll. The last lines say, for every server, how many tasks were ok, changed or failed. On a second run, changed=0 is what healthy looks like.
  • Change the files, not the servers. A fix made by hand on a server lasts until the next rebuild. Put it in a playbook, and it comes back every time.

When Ansible says no

Start here when something fails. Match the message, then look where it says.

The message saysIt meansLook atLesson
UNREACHABLE!Ansible couldn't connect over SSH: the server is off, gone, or has a new addressvagrant status, then vagrant ssh-config > ssh_config1, 11
An error about temporary files, with --become-userBecoming a user other than root needs the acl package on the serverInstall acl first2
'NAME' is undefinedNothing this playbook can see sets that variable. A role's defaults only exist in plays that use the roleansible-inventory --host HOST, then put shared values in group_vars7
'role' is not a valid attribute for a PlayA mistyped keyword: here, role: for roles:ansible-playbook FILE --syntax-check10
Attempting to decrypt but no vault secrets foundA vault file needs opening, and Ansible has no vault password.vault_pass, and vault_password_file in ansible.cfg8
Decryption failed (no vault secrets were found that could decrypt)It has a vault password, but the wrong one: often after a rekeyWhich password the file was last encrypted with8
src file does not exist, only in --checkThe task depends on a file an earlier task would have written, and in a dry run didn'tNothing: read the diffs before it, then run for real5
FAILED! with "rc": 2 from a commandThe command exited non-zero. Sometimes that's the answer you wanted: getent gives 2 for "not found"What that command's exit codes mean6
Status code was -1 ... Connection refusedThe server answered, but nothing is listening on that port: the service is stopped, or failed to startsystemctl status, and the service's config test6, 9
unknown directive or invalid port from nginx -tnginx's config is broken. A running nginx keeps its last good config until a reload or rebootThe file and line number in the message5, 6
FAILED - RETRYING ... (N retries left)A task with until: is waiting for something. Not an error yetOnly the fatal after the last retry6
NO MORE HOSTS LEFTEvery server in the play, or the batch, failed, so the run stoppedThe first fatal above it9
[WARNING]: Module remote_tmp ... did not existAnsible made a temporary folder for another user. HarmlessNothing8, 12
couldn't resolve module/action 'community.general.ufw'The module comes from a collection that isn't installedansible-galaxy collection install -r requirements.yml12

Reading a recap

Every playbook ends with a PLAY RECAP: one line per server.

CountMeansLesson
okTasks that ran and found nothing to change, plus those that changed something3
changedTasks that changed the server. On a second run, it should be 03
failedTasks that failed. The server drops out of the rest of the play6
unreachableAnsible couldn't connect at all11
skippedTasks whose when: was false, or that don't run in --check6
rescuedFailures caught by a rescue:. They don't count as failed, so they don't fail the run6
ignoredFailures on tasks with ignore_errors: true6

Reaching the servers

ToRunLesson
Install Ansible (Mac)brew install ansible1
Start, stop the practice serversvagrant up, vagrant halt1
Tell SSH, and Ansible, how to reach themvagrant ssh-config > ssh_config, after every new VM1, 11
Check Ansible can reach themansible all -m ping -o1
Delete one VM, and make it againvagrant destroy -f web2, vagrant up web211
Point Ansible at an SSH config filessh_args = -F ssh_config, in ansible.cfg1
Stop the Python interpreter warningsinterpreter_python = auto_silent, in ansible.cfg1

Inventory and ad-hoc commands

ToRunLesson
See the inventory as a treeansible-inventory --graph, with --vars for settings2
One server's settings, from the inventoryansible-inventory --host HOST2, 7
Which servers a pattern picksansible "all:!db" --list-hosts2
Either group, both, all butweb:db, bookshop:&web, all:!db2
Run a command everywhereansible all -m command -a "uptime -p"1
Pipes and redirectsansible all -m shell -a "CMD | CMD"2
Run as root--become2
Run as another user--become --become-user USER, which needs acl2
Make sure a package is installedansible web -m apt -a "name=tree state=present" --become1
Print a variable on each serveransible web -m debug -a "var=http_port"2
See a server's factsansible HOST -m setup -a "filter=ansible_default_ipv4"4
Run a script from your computer on a serveransible HOST -m script -a audit.sh --become12
Find a module, read its optionsansible-doc -l, ansible-doc ansible.builtin.user2

Running playbooks

ToRunLesson
Check it parses, without serversansible-playbook site.yml --syntax-check3, 10
See which servers, and which tasks--list-hosts, --list-tasks3
Dry run, with file changes shownansible-playbook site.yml --check --diff3
Run itansible-playbook site.yml3
Run it on some servers only--limit web1, or --limit "web1,db1"3
Change a variable for one run-e "http_port=9999"4
Run handlers even after a failure--force-handlers5
Time a runtime ansible-playbook site.yml6

The shape of a playbook, from Lesson 3:

YAML
- name: Web servers          # a play: which servers, and what to do
  hosts: web
  become: true
  tasks:
    - name: Install nginx    # a task: one module, with its options
      ansible.builtin.apt:
        name: nginx
        state: present

Variables and facts

ToWriteLesson
Settings for every servergroup_vars/all.yml4
Settings for one groupgroup_vars/web.yml, or files in group_vars/web/4, 8
Settings for one serverhost_vars/db1.yml, or web2 http_port=8080 in the inventory4
Use a value{{ http_port }}, quoted when it starts a line: "{{ ... }}"4
A fallback value{{ http_port | default(80) }}4
Leave a setting out if there's no value{{ item.comment | default(omit) }}6
Use a fact{{ ansible_facts['default_ipv4']['address'] }}4
Read another server's variable{{ hostvars['db1']['db_port'] }}4, 8
Save a task's resultregister: result, then result.stdout, result.rc4
Work out a new variableansible.builtin.set_fact:4
Print a variable while runningansible.builtin.debug: with var: or msg:4
Skip gathering factsgather_facts: false, on the play4
Turn text back into a number{{ port | int }}10

Which value wins, simplified, from weakest to strongest: a role's defaults, then group_vars/all, then group_vars/GROUP, then the server's own variables, then a play's vars:, then a role's vars, then register and set_fact, and strongest of all, -e. Two rules from the track: a value more than one playbook needs goes in the inventory (Lesson 7), and settings people may change go in a role's defaults, never its vars.

Templates and handlers

ToWriteLesson
Write a file from a templateansible.builtin.template with src, dest, mode5
Put in a value{{ http_port }}5
Include a line only sometimes{% if http_port != 80 %} ... {% endif %}5
Say a file is managed{{ ansible_managed }}, in a comment5
Test a new file before it's put in placevalidate: visudo -cf %s, or nginx -t -c %s for a whole config5, 12
Restart or reload only after a changenotify: Reload nginx on the task, and a handler with that name5
Reread config, keeping connectionsstate: reloaded, rather than restarted5
Check nginx's config, changing nothingansible.builtin.command: nginx -t, with changed_when: false5
Run handlers now, mid-play- ansible.builtin.meta: flush_handlers5

Loops, conditions and failures

ToWriteLesson
Run a task once per itemloop: "{{ LIST }}", then item inside it6
Print only part of each itemloop_control: with label: "{{ item.name }}"6
Remove somethingstate: absent, not deleting its line6
Run a task only sometimeswhen: result.stat.exists, with no {{ }}6
Look at a file without changing itansible.builtin.stat, then .stat.exists6
Retry until something is trueuntil:, retries: 5, delay: 26
Fail unless a condition holdsansible.builtin.assert with that:6
Decide what failure meansfailed_when: CONDITION6
Decide what a change meanschanged_when: CONDITION, or false for checks4, 12
A shell task that runs only oncecreates: /path/it/makes3
Try, recover, and finishblock:, rescue:, always:6
Fail on purpose, with a messageansible.builtin.fail with msg, at the end of a rescue9
Make a web requestansible.builtin.uri with url, return_content: true6

changed_when: false only stops a task saying it changed something. It doesn't stop the change. Use it for checks that really change nothing, like nginx -t.

Roles

ToWrite or runLesson
Make an empty roleansible-galaxy role init --init-path roles NAME7
A role's tasks, the only part it must haveroles/NAME/tasks/main.yml7
Its handlersroles/NAME/handlers/main.yml7
Its templates and files, used without a pathroles/NAME/templates/, roles/NAME/files/7
Settings anyone can overrideroles/NAME/defaults/main.yml7
Settings the inventory can't overrideroles/NAME/vars/main.yml, rarely7
Use roles in a playroles:, then - NAME7
Name a role's variables safelyThe role's name first: nginx_http_port, health_page10
Let the inventory keep its own namesnginx_http_port: "{{ http_port | default(80) }}", in defaults10
Prove a refactor changed nothing--check --diff, then a real run: changed=07

Secrets with Vault

ToRun or writeLesson
Keep the vault password out of git.vault_pass, mode 600, in .gitignore; check with git check-ignore -v .vault_pass8
Use it every runvault_password_file = .vault_pass, in ansible.cfg8
Name the secret where people can searchdb_password: "{{ vault_db_password }}" in vars.yml, the value in vault.yml8
Encrypt, read, change a fileansible-vault encrypt FILE, view FILE, edit FILE8
Start a new encrypted fileansible-vault create FILE8
Encrypt one value for YAMLansible-vault encrypt_string "VALUE" --name NAME8
Change the vault passwordansible-vault rekey --new-vault-password-file NEW FILE, then change the secrets too8
Keep a secret out of the outputno_log: true, on every task that uses it8
Give a GitHub workflow the passwordgh secret set VAULT_PASSWORD, then secrets.VAULT_PASSWORD10

Patching a fleet

ToWrite or runLesson
Run a play one server at a timeserial: 1, on the play9
Start small, then grow the batchesserial: [1, 5, "25%"]9
Stop at the first failure in a batchmax_fail_percentage: 09
Stop the whole run at the first failureany_errors_fatal: true6
Refresh the package listansible.builtin.apt with update_cache: true9
Upgrade packages, cautiouslyansible.builtin.apt with upgrade: safe9
Reboot only if Ubuntu asksstat on /var/run/reboot-required, then ansible.builtin.reboot9
Count waiting updatesapt list --upgradable. On a new server, refresh the list first: 0 can mean "never checked"9, 11
Patch the whole bookshopansible-playbook patch.yml9

Testing

ToRunLesson
Install the linterbrew install ansible-lint, or pipx install ansible-lint10
Lint the whole repositoryansible-lint; -f pep8 for one finding per line10
Skip a rule on one line, with a reason# noqa: RULE, and a comment saying why10, 12
Test idempotenceRun twice: the second recap must show changed=010
Run every check./test.sh10
Check the health of the web serversansible-playbook health.yml6
Wait for a pull request's checksgh pr checks --watch10

The lint rules the track met, and what each wants:

RuleWantsLesson
var-naming[no-role-prefix]A role's variables to start with the role's name10
no-changed-whenchanged_when, creates or removes on every command and shell task10
partial-becomebecome: true next to every become_user12
command-instead-of-moduleA module, where one exists, instead of running its command12

Rebuilding a server

BASH
vagrant destroy -f web2                       # gone, disk and all
vagrant up web2                               # a new, empty server
vagrant ssh-config > ssh_config               # its new key, and maybe a new address
ansible-playbook site.yml --limit web2        # describe it
ansible-playbook patch.yml --limit web2       # bring it up to date
ansible-playbook health.yml && ./test.sh      # prove it

About three minutes in Lesson 11. Everything in the playbooks comes back; anything done by hand doesn't.

Collections, and Ansible for each bash habit

BASH
ansible-galaxy collection install -r requirements.yml
ansible-galaxy collection list
In bashIn AnsibleLesson
apt-get install -y PKGansible.builtin.apt with name:3
id NAME || useradd ...ansible.builtin.user3
Compare, then install -m MODEansible.builtin.copy with mode, owner, group12
grep -q LINE FILE || echo LINE >> FILEansible.builtin.lineinfile12
ufw allow ...community.general.ufw12
Check fstab, append, mountansible.posix.mount with state: mounted12
createdb, psql -ccommunity.postgresql.postgresql_db, postgresql_table, postgresql_query12
runuser -u postgres --become: true and become_user: postgres12
A "changed" flag, then systemctl restartnotify: and a handler5, 12
systemctl enable --nowansible.builtin.systemd_service with enabled: true, state: started12
set -euo pipefail, and careNothing: a failed task stops the run12