🅰️ Ansible Quick Reference

Ansible cheat sheet / survival guide

Video: Ansible Cheat Sheet (sysadmin video)
Prefer to watch? This video runs the guide's main commands on a real Ubuntu 24.04 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube. It is part of the Automation and Configuration Management playlist.
ansible [pattern] -m module -a "args"
ansible-playbook [options] playbook.yml

Ansible configures servers over SSH. You list your machines in an inventory and describe
the state you want in YAML playbooks. No agent runs on the servers: Ansible connects,
does the work, and leaves. Good playbooks are idempotent: running one twice changes
nothing the second time.

The main tools

ansible              Run a single module on hosts (ad-hoc commands)
ansible-playbook     Run a playbook
ansible-inventory    Show and check inventory
ansible-galaxy       Install roles and collections
ansible-vault        Encrypt secrets
ansible-doc          Read module documentation offline
ansible-config       View and dump configuration
ansible-lint         Check playbooks for problems (separate install)

Install

sudo apt install ansible              # Debian/Ubuntu
sudo dnf install ansible-core         # Fedora/RHEL family
pipx install --include-deps ansible   # any system, isolated from the OS Python
ansible --version                     # shows version, config file and module paths
The machine you run Ansible from is the "control node" (Linux/macOS/WSL).
Managed hosts need SSH and Python. Windows targets use WinRM instead.

Inventory (your list of hosts)

# inventory.ini
[web]
web1.example.com
web2.example.com ansible_port=2222

[db]
db1.example.com ansible_user=admin

[prod:children]
web
db

[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_ed25519

Or in YAML (inventory.yml):
all:
  children:
    web:
      hosts:
        web1.example.com:
        web2.example.com:
ansible-inventory -i inventory.ini --list      # see how Ansible reads it
ansible-inventory -i inventory.ini --graph     # tree view
Always defined: "all" (every host) and "ungrouped".

Ad-hoc commands (one-off jobs)

ansible all -m ping                            # can I connect?
ansible web -m command -a "uptime"             # run a command
ansible web -m shell -a "df -h | head"         # shell features (pipes) need -m shell
ansible all -m setup                           # gather facts about the hosts
ansible all -m setup -a "filter=ansible_distribution*"
ansible web -b -m apt -a "name=nginx state=present update_cache=yes"
ansible web -b -m service -a "name=nginx state=restarted"
ansible web -m copy -a "src=app.conf dest=/etc/app.conf" -b
ansible web -m file -a "path=/tmp/x state=absent"
ansible web -m user -a "name=alice groups=sudo append=yes" -b
ansible web -m fetch -a "src=/var/log/syslog dest=./logs/"
ansible web -b -a "reboot" -f 1                # one host at a time (-f = forks)

Common options

-i FILE        Inventory file (or directory)
-m MODULE      Module to run (default: command)
-a "ARGS"      Module arguments
-b             Become root (sudo)          --become-user USER   become another user
-K             Ask for the sudo password   -k   ask for the SSH password
-u USER        SSH user
--private-key  Key file to use
-l PATTERN     Limit to a host or group (--limit)
-f N           Forks: how many hosts in parallel (default 5)
-v / -vvv      More detail (up to -vvvv for connection debugging)
-e "k=v"       Extra variable (also -e @vars.yml)
-C             Check mode: dry run, change nothing
-D             Diff mode: show file changes
-t TAG         Only tasks with this tag        --skip-tags TAG   skip them
--list-hosts   Show which hosts would be used
--syntax-check Parse the playbook only

Host patterns

all                   Every host
web                   A group
web:db                Web OR db
web:&prod             Web AND prod
web:!staging          Web but NOT staging
web*                  Wildcard on names
web[0:2]              A slice of a group

A minimal playbook (site.yml)

---
- name: Configure web servers
  hosts: web
  become: true
  vars:
    app_port: 8080
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Deploy the config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        mode: "0644"
      notify: Restart nginx

    - name: Make sure nginx is running and enabled
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Run it

ansible-playbook -i inventory.ini site.yml
ansible-playbook site.yml -C -D                # dry run with a diff (do this first)
ansible-playbook site.yml -l web1.example.com  # one host only
ansible-playbook site.yml --tags config
ansible-playbook site.yml -e "app_port=9090"
ansible-playbook site.yml --start-at-task "Deploy the config"
ansible-playbook site.yml --step               # confirm each task
ansible-playbook site.yml -K                   # ask for the sudo password
ansible-playbook site.yml --list-tasks

Handy modules

apt / dnf / package        Install packages
service / systemd          Start, stop, enable services
copy                       Copy a file or literal content to the host
template                   Render a Jinja2 template to the host
file                       Files, directories, links, permissions, delete
lineinfile / blockinfile   Edit lines or blocks in a file
user / group               Manage accounts
authorized_key             Manage SSH keys
git                        Check out a repository
get_url / uri              Download a file / call a web API
command / shell / script   Run commands (use a real module when one exists)
cron                       Manage cron jobs
ufw / firewalld            Firewall rules
mount, lvol, filesystem    Storage
docker_container           Manage containers (community.docker collection)
debug                      Print a message or variable
assert / fail              Stop when a condition is not met
reboot                     Reboot and wait for the host to return
wait_for                   Wait for a port, file or string
Use the full name (ansible.builtin.apt). Read docs offline: ansible-doc ansible.builtin.apt

Variables

- Playbook:      vars: at play or task level
- Files:         vars_files: [vars/main.yml]
- Inventory:     group_vars/web.yml and host_vars/web1.yml (auto-loaded folders)
- Command line:  -e "key=value" (highest priority)
- Facts:         ansible_hostname, ansible_distribution, ansible_default_ipv4.address
- Registered:    register: result   then result.stdout, result.rc, result.changed
Use them with Jinja2: "{{ app_port }}" (quote the whole string when it starts with {{).

Loops, conditions and results

- name: Install several packages
  ansible.builtin.apt:
    name: "{{ item }}"
    state: present
  loop: [git, curl, htop]

- name: Only on Debian family
  ansible.builtin.apt:
    name: apache2
  when: ansible_os_family == "Debian"

- name: Check a service
  ansible.builtin.command: systemctl is-active nginx
  register: nginx_state
  changed_when: false
  failed_when: nginx_state.rc not in [0, 3]

- ansible.builtin.debug:
    var: nginx_state.stdout

Handling errors and control

ignore_errors: true                # keep going if this task fails
block / rescue / always            # try/catch/finally for tasks
serial: 2                          # roll out two hosts at a time
max_fail_percentage: 20            # abort if too many hosts fail
run_once: true                     # do it on one host only
delegate_to: localhost             # run the task somewhere else
tags: [config, nginx]              # select with --tags
no_log: true                       # hide secrets from output
check_mode: false                  # task runs even under -C
throttle: 1                        # limit parallelism for one task

Templates (Jinja2)

# templates/nginx.conf.j2
server {
    listen {{ app_port }};
    server_name {{ inventory_hostname }};
{% for site in sites %}
    include /etc/nginx/sites/{{ site }}.conf;
{% endfor %}
}
Filters: {{ name | upper }}  {{ list | join(",") }}  {{ var | default("x") }}  {{ path | basename }}
{{ items | length }}  {{ value | int }}  {{ data | to_json }}  {{ pass | password_hash("sha512") }}

Roles (reusable structure)

ansible-galaxy init roles/webserver
roles/webserver/
  tasks/main.yml         Tasks
  handlers/main.yml      Handlers
  templates/             Jinja2 files
  files/                 Static files
  defaults/main.yml      Default variables (lowest priority)
  vars/main.yml          Role variables (higher priority)
  meta/main.yml          Dependencies
Use in a play:  roles: [webserver]   or   roles: [{ role: webserver, app_port: 9090 }]

Collections and Galaxy

ansible-galaxy collection install community.general
ansible-galaxy collection install -r requirements.yml
ansible-galaxy role install geerlingguy.nginx
ansible-galaxy collection list

Secrets with Vault

ansible-vault create secrets.yml               # new encrypted file
ansible-vault edit secrets.yml
ansible-vault encrypt vars/db.yml
ansible-vault decrypt vars/db.yml
ansible-vault view secrets.yml
ansible-vault encrypt_string 'S3cret!' --name 'db_password'   # encrypt one value
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Put the vault password file outside the repo and out of git.

ansible.cfg (project settings)
[defaults]
inventory = ./inventory.ini
remote_user = ubuntu
host_key_checking = True
forks = 10
stdout_callback = yaml
retry_files_enabled = False

[privilege_escalation]
become = True
Ansible reads ./ansible.cfg first, then ~/.ansible.cfg, then /etc/ansible/ansible.cfg.
ansible-config dump --only-changed             # show what differs from the defaults

Practical one-liners

# Update every server (apt) and reboot if required
ansible all -b -m apt -a "upgrade=dist update_cache=yes"
ansible all -b -m reboot

# Free disk space on every host
ansible all -m shell -a "df -h /"

# Who is logged in everywhere?
ansible all -m command -a "who"

# Copy your SSH key to all hosts
ansible all -m authorized_key -a "user=ubuntu key='{{ lookup('file','~/.ssh/id_ed25519.pub') }}'"

# Restart a service on one group, one host at a time
ansible web -b -m service -a "name=nginx state=restarted" -f 1

# Find which hosts a playbook would touch
ansible-playbook site.yml --list-hosts

# Lint before you commit
ansible-lint site.yml

# Run a role against a single host, quickly
ansible-playbook -i "web1.example.com," -u ubuntu -b -e "target=web1" site.yml

Common errors

UNREACHABLE! Permission denied (publickey)    Wrong user or key: check -u and --private-key
Missing sudo password                        Add -K or set up passwordless sudo
"msg": "Failed to connect to the host"       Firewall, wrong port or host is down
Host key verification failed                 Accept the key with ssh first, or manage known_hosts
Python interpreter not found                 Install python3 on the target or set ansible_python_interpreter
Syntax error in YAML                         Check indentation (spaces, never tabs) and quote {{ }} values
'variable' is undefined                      Typo or missing var: check group_vars and -e
Module not found                             Install the collection with ansible-galaxy

Tips

    Run with -C -D (check and diff) before the real run, on staging before production.

    Prefer a proper module over command or shell. Modules are idempotent and report changes correctly.

    YAML is strict about indentation: two spaces, no tabs. Quote any value starting with {{.

    Keep playbooks and roles in git, and keep secrets in Ansible Vault (or a secrets manager), never in plain text.

    Use groups and group_vars, not lots of per-host settings, so adding a server is one line.

    Give every task a clear name so the output reads like a report.

    Use handlers for restarts so a service restarts once, and only when something actually changed.

    Set serial when rolling changes across a fleet, so one bad task cannot take everything down at once.

    Use SSH keys with an agent, not passwords, and limit who can use become.

    Pin collections in requirements.yml so a playbook behaves the same next month.