Ansible

Ansible Commands and Playbook Cheat Sheet

Ansible commands blur together after a while. You know the module exists, you just can’t remember the exact flag or the right ansible-playbook option to dry-run only tagged tasks. This cheat sheet is the page you keep open in a browser tab while working.

Original content from computingforgeeks.com - post 165300

It covers everything from quick ad-hoc commands through playbook execution flags, Jinja2 filters, lookup plugins, vault operations, async patterns, and performance tuning. Every command and output below was captured on real systems. Bookmark it, Ctrl+F when you need something, and get back to work. For a full walkthrough of setting up Ansible from scratch, see our Ansible installation guide.

Tested April 2026 on Rocky Linux 10.1, Ubuntu 24.04.4 LTS with ansible-core 2.16.14, community.general 12.5.0

Essential CLI Commands

These are the binaries you actually use day to day. If you only memorize one table from this page, make it this one.

CommandPurposeCommon Flags
ansibleRun ad-hoc commands against hosts-m (module), -a (args), -i (inventory), -b (become)
ansible-playbookExecute playbooks--check, --diff, --limit, --tags, -f (forks)
ansible-galaxyInstall roles and collectionsinstall, init, list, search
ansible-vaultEncrypt/decrypt sensitive dataencrypt, decrypt, view, edit, rekey
ansible-inventoryInspect and validate inventory--graph, --list, --host
ansible-docModule documentation offline-l (list all), -t (type: module, lookup, callback)
ansible-configView/dump configurationlist, dump, view

Quick version check to confirm your install:

ansible --version

List every installed collection and its version:

ansible-galaxy collection list

Pull up offline docs for any module without leaving the terminal:

ansible-doc ansible.builtin.copy

Ad-Hoc Command Patterns

Ad-hoc commands are the fastest way to do something across your fleet without writing a playbook. The syntax is always ansible <pattern> -m <module> -a <args>. Here are the modules you’ll reach for most often. For inventory setup and host grouping, see the inventory management guide.

ModuleExampleWhen to Use
pingansible all -m pingConnectivity check (SSH + Python)
shellansible all -m shell -a "uptime"Run arbitrary shell commands
commandansible all -m command -a "df -h"Run commands without shell features (safer)
copyansible all -m copy -a "src=./f.txt dest=/tmp/f.txt"Push files to remote hosts
fileansible all -m file -a "path=/tmp/f.txt state=absent"Create, delete, or set permissions on files
serviceansible all -m service -a "name=nginx state=started" -bStart, stop, restart services
packageansible all -m package -a "name=htop state=present" -bInstall packages (distro-agnostic)
setupansible all -m setup -a "filter=ansible_memory_mb"Gather system facts
useransible all -m user -a "name=deploy state=present" -bManage user accounts

Check uptime across all managed hosts:

ansible all -m shell -a "uptime"

The output shows each host name followed by its uptime and load average:

managed-rocky | CHANGED | rc=0 >>
 13:23:40 up 10 min,  2 users,  load average: 0.34, 0.35, 0.16
managed-ubuntu | CHANGED | rc=0 >>
 10:23:40 up 9 min,  1 user,  load average: 0.06, 0.10, 0.04

Gather memory facts from a specific host group:

ansible rocky_nodes -m setup -a "filter=ansible_memory_mb"

This returns structured JSON you can parse or feed into templates:

managed-rocky | SUCCESS => {
    "ansible_facts": {
        "ansible_memory_mb": {
            "real": { "free": 2727, "total": 3653, "used": 926 }
        }
    }
}

Push a file to all hosts, then clean it up:

ansible all -m copy -a "content='hello from ansible\n' dest=/tmp/ansible-test.txt mode=0644"

Confirm the file landed:

managed-rocky | CHANGED => { "changed": true, "dest": "/tmp/ansible-test.txt", "mode": "0644" }

Remove it when you’re done:

ansible all -m file -a "path=/tmp/ansible-test.txt state=absent"

The state changes to absent:

managed-rocky | CHANGED => { "changed": true, "path": "/tmp/ansible-test.txt", "state": "absent" }

For more on managing users and groups with ad-hoc commands and playbooks, see managing Linux users with Ansible.

Inventory Quick Reference

A well-organized inventory is the foundation. Here’s a minimal INI-style inventory with two groups:

[rocky_nodes]
managed-rocky ansible_host=10.0.1.11

[ubuntu_nodes]
managed-ubuntu ansible_host=10.0.1.12

[all:vars]
ansible_user=rocky
ansible_python_interpreter=/usr/bin/python3

Visualize the group hierarchy with --graph:

ansible-inventory -i inventory.ini --graph

This draws a tree of all groups and their members:

@all:
  |--@ungrouped:
  |--@rocky_nodes:
  |  |--managed-rocky
  |--@ubuntu_nodes:
  |  |--managed-ubuntu

Dump the full inventory as JSON (useful for debugging dynamic inventories):

ansible-inventory -i inventory.ini --list

Playbook Execution Flags

Knowing these flags saves you from accidentally deploying to production when you meant to do a dry run. The playbook tutorial covers writing playbooks from scratch; this section focuses on execution control.

FlagWhat It DoesExample
--checkDry run, no changes appliedansible-playbook site.yml --check
--diffShow file change diffs (combine with --check)ansible-playbook site.yml --check --diff
--syntax-checkValidate YAML syntax without runningansible-playbook site.yml --syntax-check
--list-tasksShow all tasks without executingansible-playbook site.yml --list-tasks
--list-hostsShow targeted hosts without executingansible-playbook site.yml --list-hosts
--limitRestrict execution to specific hosts/groupsansible-playbook site.yml --limit rocky_nodes
--tagsRun only tasks with matching tagsansible-playbook site.yml --tags "install,config"
--skip-tagsSkip tasks with matching tagsansible-playbook site.yml --skip-tags "debug"
--start-at-taskResume from a specific task nameansible-playbook site.yml --start-at-task "Configure Nginx"
-f / --forksNumber of parallel processesansible-playbook site.yml -f 20
-e / --extra-varsPass variables at runtimeansible-playbook site.yml -e "env=staging"
--stepInteractive: confirm each task before runningansible-playbook site.yml --step

Preview which tasks a playbook will execute:

ansible-playbook deploy-nginx.yml --list-tasks

The output lists every task name in execution order:

playbook: deploy-nginx.yml
  play #1 (all): Deploy Nginx on Rocky Linux and Ubuntu	TAGS: []
    tasks:
      Install firewall Python bindings (RHEL family)	TAGS: []
      Install Nginx (RHEL family)	TAGS: []
      Install Nginx (Debian family)	TAGS: []
      ...

See which hosts would be targeted:

ansible-playbook deploy-nginx.yml --list-hosts

Confirms the host pattern before you commit:

  play #1 (all): Deploy Nginx on Rocky Linux and Ubuntu	TAGS: []
    pattern: ['all']
    hosts (2):
      managed-rocky
      managed-ubuntu

A quick syntax validation catches YAML errors before anything touches a remote host:

ansible-playbook deploy-nginx.yml --syntax-check

Clean output means no errors found:

playbook: deploy-nginx.yml

Verbosity Levels

When something fails and the error message is useless, bump the verbosity. Each level adds more detail.

FlagLevelWhat It Shows
-v1Task results and changed status
-vv2Input parameters for each task
-vvv3SSH connection details, file transfers, command execution
-vvvv4Connection plugin internals (useful for SSH debugging)

For most debugging, -vvv is the sweet spot. Going to -vvvv floods the terminal but helps diagnose SSH key or connection issues.

ansible-playbook site.yml -vvv

Variables, Facts, and Loops

Variables are where playbooks get their flexibility. This section covers the patterns you’ll use constantly.

Register output from a command and use it later:

- name: Check disk space
  command: df -h /
  register: disk_output

- name: Show disk info
  debug:
    msg: "{{ disk_output.stdout_lines[0] }}"

Set a fact dynamically during execution:

- name: Set database port based on OS
  set_fact:
    db_port: "{{ 5432 if ansible_os_family == 'Debian' else 5433 }}"

Loop over a list of packages:

- name: Install monitoring tools
  package:
    name: "{{ item }}"
    state: present
  loop:
    - htop
    - iotop
    - strace
    - tcpdump

The older with_items syntax still works but loop is the modern replacement. Use loop for new playbooks.

Conditional execution with when:

- name: Install EPEL on RHEL family
  dnf:
    name: epel-release
    state: present
  when: ansible_os_family == "RedHat"

- name: Enable UFW on Ubuntu
  ufw:
    state: enabled
  when: ansible_distribution == "Ubuntu"

Loop with index:

- name: Create numbered config files
  copy:
    content: "server_id={{ idx }}"
    dest: "/etc/app/node-{{ idx }}.conf"
  loop: "{{ groups['all'] }}"
  loop_control:
    index_var: idx

For database automation examples using variables and loops, check managing PostgreSQL with Ansible.

Error Handling: block/rescue/always

The block/rescue/always pattern is Ansible’s try/catch/finally. Wrap risky tasks in a block, handle failures in rescue, and run cleanup in always. This catches most people off guard because they don’t realize Ansible has structured error handling at all.

- name: Error handling demo
  hosts: rocky_nodes
  tasks:
    - block:
        - name: Try to start a non-existent service
          service:
            name: nonexistent-service
            state: started
      rescue:
        - name: Handle the failure
          debug:
            msg: "Service failed to start. Running recovery steps."
        - name: Log the failure
          shell: echo "Service failure at $(date)" >> /tmp/ansible-errors.log
      always:
        - name: This always runs regardless of success or failure
          debug:
            msg: "Cleanup complete. Block finished."

Here’s what that looks like in practice:

TASK [Try to start a non-existent service] *************************************
fatal: [managed-rocky]: FAILED! => {"changed": false, "msg": "Could not find the requested service nonexistent-service: host"}

TASK [Handle the failure] ******************************************************
ok: [managed-rocky] => {
    "msg": "Service failed to start. Running recovery steps."
}

TASK [Log the failure] *********************************************************
changed: [managed-rocky]

TASK [This always runs regardless of success or failure] ***********************
ok: [managed-rocky] => {
    "msg": "Cleanup complete. Block finished."
}

PLAY RECAP *********************************************************************
managed-rocky              : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0

Notice rescued=1 in the play recap. That’s your signal the rescue block fired.

Other error control directives:

DirectivePurposeExample
ignore_errors: trueContinue even if task failsNon-critical cleanup tasks
failed_whenCustom failure conditionfailed_when: "'ERROR' in result.stderr"
changed_whenCustom change conditionchanged_when: "'created' in result.stdout"
any_errors_fatal: trueStop all hosts if one failsDatabase migrations (must succeed everywhere)

Jinja2 Filters Quick Reference

Filters transform data inside {{ }} expressions. Ansible ships with dozens; these are the ones that come up repeatedly in real playbooks. All outputs below are from actual runs on Rocky Linux 10.1.

FilterInputOutput
unique["nginx","redis","nginx","postgresql","redis"]["nginx", "redis", "postgresql"]
from_json'{"name":"web01","port":8080}'Server web01 on port 8080
defaultundefined_var | default("production")Environment is production
ternary(ansible_os_family == "RedHat") | ternary("RHEL-based","Debian-based")OS family is RHEL-based
sort + join[3,1,4,1,5,9,2,6] | sort | join(", ")1, 1, 2, 3, 4, 5, 6, 9
regex_replace"Hello World 1234" | regex_replace("\d+","XXXX")Hello World XXXX
combine{"a":1,"b":2} | combine({"b":3,"c":4}){"a": 1, "b": 3, "c": 4}
selectattrList of dicts filtered by active == trueActive users: ['alice', 'carol']

Here’s the selectattr filter in action, which is particularly useful for filtering lists of dictionaries:

- name: selectattr filter
  vars:
    users:
      - { name: alice, active: true }
      - { name: bob, active: false }
      - { name: carol, active: true }
  debug:
    msg: "Active users: {{ users | selectattr('active') | map(attribute='name') | list }}"

Result on managed-rocky:

ok: [managed-rocky] => { "msg": "Active users: ['alice', 'carol']" }

The full list of built-in filters is in the official Ansible filters documentation.

Lookup Plugins

Lookups pull data from external sources into your playbooks. They run on the control node, not the remote hosts.

LookupPurposeExample Usage
fileRead local file contents{{ lookup('file', '/etc/hostname') }}
envRead environment variables{{ lookup('env', 'HOME') }}
sequenceGenerate number sequences{{ query('sequence', 'start=8080 end=8085') }}
dictIterate over dictionaries{{ lookup('dict', port_map) }}
pipeRun a command and return stdout{{ lookup('pipe', 'date +%Y%m%d') }}
passwordGenerate or retrieve passwords{{ lookup('password', '/tmp/pw length=16') }}
templateRender a Jinja2 template{{ lookup('template', 'my.conf.j2') }}

Verified outputs from the test environment:

TASK [file lookup] *************************************************************
ok: [managed-rocky] => { "msg": "ansible.cfg contents (first line): [defaults]" }

TASK [env lookup] **************************************************************
ok: [managed-rocky] => { "msg": "Home directory: /home/rocky" }

TASK [sequence lookup] *********************************************************
ok: [managed-rocky] => { "msg": "Ports: 8080, 8081, 8082, 8083, 8084, 8085" }

TASK [dict lookup] *************************************************************
ok: [managed-rocky] => (item=http) => { "msg": "http=80" }
ok: [managed-rocky] => (item=https) => { "msg": "https=443" }
ok: [managed-rocky] => (item=ssh) => { "msg": "ssh=22" }

The dict lookup is especially handy for iterating over port maps, service configurations, or any key/value structure without converting it to a list first.

Vault Operations

Ansible Vault encrypts sensitive data (passwords, API keys, certificates) so you can safely commit them to version control. Our dedicated Ansible Vault reference guide covers advanced patterns; here’s the quick reference.

OperationCommand
Encrypt a fileansible-vault encrypt secrets.yml
Decrypt a fileansible-vault decrypt secrets.yml
View encrypted fileansible-vault view secrets.yml
Edit in-placeansible-vault edit secrets.yml
Change passwordansible-vault rekey secrets.yml
Encrypt a single stringansible-vault encrypt_string 'secret' --name 'db_pass'
Run playbook with vaultansible-playbook site.yml --ask-vault-pass
Use password fileansible-playbook site.yml --vault-password-file .vault_pass

Encrypt a secrets file:

ansible-vault encrypt secrets.yml

Output on success:

Encryption successful

The file now contains ciphertext instead of plaintext:

$ANSIBLE_VAULT;1.1;AES256
30353634656539386438653836393166...

View the contents without decrypting the file on disk:

ansible-vault view secrets.yml

Shows the original plaintext:

db_password: SuperSecret123

Encrypt a single variable inline for use in playbooks:

ansible-vault encrypt_string 'SuperSecret123' --name 'db_password'

Paste the output directly into your vars file. The playbook decrypts it at runtime when you pass --ask-vault-pass or --vault-password-file.

Async Tasks

Long-running tasks (backups, large package upgrades, database migrations) can block your playbook. The async pattern fires off the task, does other work, then checks back.

- name: Run a long task asynchronously
  shell: sleep 15 && echo "done"
  async: 60
  poll: 0
  register: async_task

- name: Do other work while waiting
  debug:
    msg: "Doing other work while async task runs..."

- name: Check async task status
  async_status:
    jid: "{{ async_task.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 10
  delay: 5

The key settings: async sets the maximum runtime in seconds, poll: 0 means “don’t wait, move on immediately.” Then async_status polls the job ID until it completes.

Real execution output showing the retry loop:

TASK [Run a long task asynchronously] ******************************************
changed: [managed-rocky]
changed: [managed-ubuntu]

TASK [Do other work while waiting] *********************************************
ok: [managed-rocky] => { "msg": "Doing other work while async task runs..." }

TASK [Check async task status] *************************************************
FAILED - RETRYING: [managed-rocky]: Check async task status (10 retries left).
FAILED - RETRYING: [managed-ubuntu]: Check async task status (10 retries left).
changed: [managed-ubuntu]
changed: [managed-rocky]

TASK [Show async result] *******************************************************
ok: [managed-rocky] => { "msg": "Task complete on managed-rocky" }

The FAILED - RETRYING messages are normal. They mean the task hasn’t finished yet and Ansible is polling again.

Performance Tuning

Default Ansible settings are conservative. On a fleet of 50+ hosts, these tweaks make a real difference. We benchmarked forks on our two-node test setup and the improvement scales with host count.

SettingDefaultRecommendedImpact
forks520-50Parallel host execution. More forks = more hosts at once
serialall hosts25% or batch countRolling deploys, limits blast radius
gather_factstruefalse (when not needed)Saves 2-5 seconds per host on every play
pipeliningfalsetrueReduces SSH operations per task
fact_cachingmemoryjsonfilePersist facts across runs, skip re-gathering
strategylinearfreeFast hosts don’t wait for slow ones

Add these to your ansible.cfg:

[defaults]
forks = 25
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600

[ssh_connection]
pipelining = True

Benchmark: forks impact on a 2-host inventory

ForksExecution TimeImprovement
12.57sbaseline
5 (default)1.65s36% faster

With only 2 hosts, the difference is already noticeable. On 50+ hosts, moving from forks=5 to forks=25 can cut execution time by 60-70%.

Enable pipelining to reduce SSH round trips per task:

ANSIBLE_PIPELINING=true ansible-playbook site.yml

One requirement: requiretty must be disabled in /etc/sudoers on the managed hosts. Rocky Linux 10 and Ubuntu 24.04 both have it disabled by default.

Useful Playbook Patterns

A few patterns that show up in almost every production environment.

Handler pattern (restart only when config changes):

tasks:
  - name: Deploy Nginx config
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx

handlers:
  - name: restart nginx
    service:
      name: nginx
      state: restarted

The handler fires only if the template task reports a change. No config change, no unnecessary restart.

Rolling update with serial:

- hosts: webservers
  serial: 2
  tasks:
    - name: Pull latest code
      git:
        repo: https://github.com/org/app.git
        dest: /var/www/app
    - name: Restart application
      service:
        name: app
        state: restarted

This deploys to 2 hosts at a time. If any batch fails, the remaining hosts stay untouched.

Delegate to a different host:

- name: Remove host from load balancer before maintenance
  uri:
    url: "http://10.0.1.10:8080/api/pool/remove/{{ inventory_hostname }}"
    method: POST
  delegate_to: 10.0.1.10

The task runs on the control node (or another host) but in the context of the current host. Handy for load balancer drain, DNS updates, or monitoring actions.

For automating SSL certificate generation across your fleet, see generating SSL certificates with Ansible. LVM and disk management playbooks are covered in LVM automation with Ansible.

Galaxy Roles and Collections

Don’t reinvent the wheel. Galaxy has community roles for nearly everything.

ansible-galaxy role install geerlingguy.docker

Install a collection (the modern replacement for standalone roles):

ansible-galaxy collection install community.general

Install all dependencies from a requirements file:

ansible-galaxy install -r requirements.yml

A typical requirements.yml:

---
collections:
  - name: community.general
    version: ">=12.0.0"
  - name: ansible.posix
roles:
  - name: geerlingguy.docker
  - name: geerlingguy.nginx

For container automation after installing Docker, check managing Docker containers with Ansible.

Troubleshooting Checklist

When a playbook fails and the error is unclear, work through this list. These cover the issues we run into most often in production.

SymptomCheckFix
SSH connection timeoutansible all -m ping -vvvvVerify SSH key, check firewall, confirm ansible_user
“No module named” erroransible_python_interpreter in inventorySet to /usr/bin/python3 explicitly
Become password requiredPrompt appears mid-runAdd --ask-become-pass or configure passwordless sudo
Stale factsFacts from a previous runDelete fact_caching_connection directory
Variable undefined-vvv to see variable resolutionCheck variable precedence (extra vars > inventory > defaults)
“Vault password required”Encrypted vars without vault passAdd --ask-vault-pass or --vault-password-file
SELinux denials on RHELausearch -m avc -ts recentAdd seboolean or semanage tasks to playbook

When all else fails, run with -vvvv and read the SSH negotiation output line by line. The answer is almost always there.

The Ansible configuration reference documents every setting available in ansible.cfg if you need to dig deeper into connection tuning or plugin configuration.

Related Articles

Automation Using Terraform Dynamic Blocks to Simplify Cloud Provisioning Ansible How To Manage PostgreSQL Database with Ansible Automation How To Install Foreman on Ubuntu 22.04 / 24.04 Automation Separate Jenkinsfiles from Sources and Prevent unwarranted editing

Leave a Comment

Press ESC to close