Ansible Ad Hoc Commands Explained with Examples

Learn Ansible ad hoc command syntax with ping, command, shell, package, service, file, copy, user, become, --limit, check mode, troubleshooting, and playbook migration examples.

Published

Updated

Read time 14 min read

Reviewed byDeepak Prasad

Ansible ad hoc commands on Rocky Linux 10 with ping, command, and package modules

An Ansible ad hoc command runs one module against a host pattern from the CLI—no playbook file required. You use it to test SSH, check uptime, install a package, or restart a service before you commit the same task to YAML.

This guide walks through syntax, the modules you reach for daily, privilege escalation, host targeting, and how to read Ansible output. I run commands from ~/ansible-project on rocky1 against rocky2 in the EX294 lab. You need inventory basics and the project layout first.

Tested on: Rocky Linux 10.2 (Red Quartz); kernel 6.12.0-211.16.1.el10_2.0.1.x86_64; ansible-core 2.16.16.

NOTE
This chapter is part of the GoLinuxCloud Ansible tutorial (RHCE EX294). Follow along from ~/ansible-project, inventory group lab, and playbooks in playbooks/. Use your own host names and paths if yours differ.

What are Ansible Ad Hoc Commands?

An ad hoc command is ansible plus a host pattern, a module (-m), and optional module arguments (-a). Ansible connects over SSH, runs the module once per matched host, and prints JSON (or a one-line summary) for each result.

Playbooks chain many tasks with variables, handlers, and roles. Ad hoc commands are the fast path: one task, one command line, immediate feedback.

text
ansible <pattern> -m <module> -a "<module arguments>"

The default module is command, so ansible lab -a "uptime" runs the command module even when you omit -m.


When Should You Use Ad Hoc Commands?

Reach for ad hoc commands when you need speed or a sanity check:

  • Verify SSH and Python after lab setup (ping)
  • Inspect one host before a change (uptime, df, systemctl status)
  • Apply a one-time fix on a small set of machines
  • Learn what a module does before you embed it in a playbook

Move to playbooks and the playbook structure guide when the same steps repeat, multiple tasks must run in order, you need idempotent roles, or teammates must review changes in Git. Ad hoc output is not a substitute for version-controlled automation.


Basic Ansible Ad Hoc Command Syntax

Every ad hoc command has the same skeleton:

Part Flag / position Example
Host pattern First argument lab, rocky2, all
Module -m -m ping
Module args -a -a "name=tmux state=present"
Inventory -i (if not in ansible.cfg) -i inventory/hosts
Remote user -u -u ansible
Privilege escalation -b / --become -b
Limit subset --limit --limit rocky2
Dry run -C / --check -C
Verbosity -v, -vv, -vvv -v

List matched hosts without running a module:

bash
ansible lab --list-hosts

Sample output:

output
hosts (1):
    rocky2

Find Module Options with ansible-doc

Before running an unfamiliar module, check its options with ansible-doc—see modules, ansible-doc and collections for FQCN naming, return values, and collection install paths.

bash
ansible-doc ansible.builtin.dnf

Sample output:

output
> ANSIBLE.BUILTIN.DNF    (/usr/lib/python3.12/site-packages/ansible/modules/dnf.py)

        Installs, upgrade, removes, and lists packages and groups with
        the `dnf' package manager.

Search for modules by keyword:

bash
ansible-doc -l | grep -i service

Sample output:

output
ansible.builtin.service                Manage services
ansible.builtin.systemd_service        Manage systemd units

Show the full option list and examples for one module:

bash
ansible-doc ansible.builtin.copy

Use ansible-doc when you are not sure which arguments a module accepts—it is safer than guessing -a values from memory.


Test Connectivity with ping Module

Start with ping. It confirms Ansible can connect and run Python on the managed node—it is not ICMP.

bash
ansible lab -m ping

Sample output:

output
rocky2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

pong means SSH and the module pipeline work. If this fails, fix inventory, keys, or ansible_user before trying package or service modules.

Target one host by inventory name:

bash
ansible rocky2 -m ping

Gather Facts with setup Module

Use setup to inspect facts Ansible collects from a managed node—useful before writing playbook conditions. The facts and custom facts guide covers gather_facts, filters, and ansible_local in depth.

bash
ansible rocky2 -m setup -a "filter=ansible_distribution*"

Sample output:

output
rocky2 | SUCCESS => {
    "ansible_facts": {
        "ansible_distribution": "Rocky",
        "ansible_distribution_major_version": "10",
        "ansible_distribution_version": "10.2"
    },
    "changed": false
}

Omit the filter to dump all facts—verbose on a large host. Use setup for quick OS and network checks; move repeated fact gathering into playbooks.


Run Commands with command Module

The command module runs a program directly—no shell features like pipes, redirects, globbing, chaining, or shell builtins. Use shell only when you need shell syntax.

bash
ansible lab -m command -a "uptime"

Sample output:

output
rocky2 | CHANGED | rc=0 >>
 15:38:25 up 17:41,  1 user,  load average: 0.25, 0.06, 0.02

CHANGED here only means the command ran successfully; command does not track configuration state the way package or file do.


Run Shell Commands with shell Module

Use shell when you need shell syntax—pipes, &&, or variable expansion.

bash
ansible lab -m shell -a 'echo $SHELL && hostname -s'

Sample output:

output
rocky2 | CHANGED | rc=0 >>
/bin/bash
rocky2

Prefer command when a plain executable is enough. Reserve shell for pipelines and shell builtins.


command vs shell Module

Feature command shell
Runs through /bin/sh No Yes
Pipes (|) No Yes
Redirects (>, >>) No Yes
Shell metacharacters No Yes
Shell builtins / chaining No Yes
Safer default Yes Use only when needed

Some simple variable expansion may work with command, but do not rely on shell behavior there. Use shell only when you need shell syntax such as pipes, redirects, &&, or shell builtins.

Official guidance: use command unless shell metacharacters like <, >, or | are required—parsing them through a shell can create security surprises. Treat shell -a like user input. For a deeper comparison including raw, argv, and idempotency patterns, see command vs shell vs raw.


Make command and shell Safer with creates or removes

command and shell usually report CHANGED whenever they run. Add creates or removes when a command should run only if a file does not exist or already exists.

Run only when the marker file does not already exist:

bash
ansible lab -m command -a "touch /tmp/ad-hoc-marker creates=/tmp/ad-hoc-marker" -b

Sample output:

output
rocky2 | CHANGED | rc=0 >>

Run the same command again and Ansible skips it:

output
rocky2 | SUCCESS | rc=0 >>
skipped, since /tmp/ad-hoc-marker exists

Run only when the file exists:

bash
ansible lab -m command -a "rm /tmp/ad-hoc-marker removes=/tmp/ad-hoc-marker" -b

Sample output:

output
rocky2 | CHANGED | rc=0 >>

This does not make arbitrary shell workflows fully idempotent, but it prevents repeated runs for simple file-based commands.


Manage Packages with package, dnf and apt Modules

NOTE
In this lab, ansible.cfg enables become = True, so examples below work without extra flags. The commands include -b anyway so you can copy them into projects that do not set become by default.

package (cross-platform)

package picks the OS package manager for you—handy in mixed inventories.

bash
ansible lab -m package -a "name=tmux state=present" -b

Sample output:

output
rocky2 | CHANGED => {
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: tmux-3.3a-13.20250207gitb202a2f.el10.x86_64"
    ]
}

changed: true means Ansible installed a package. A second run with state=present reports changed: false when the package is already there.

dnf (Rocky Linux / RHEL)

On Rocky Linux 10, dnf is explicit and matches what you would run locally:

bash
ansible lab -m dnf -a "name=tmux state=present" -b

Sample output:

output
rocky2 | SUCCESS => {
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": []
}

apt (Debian / Ubuntu)

On Debian-family hosts, the same idea uses apt:

bash
ansible web -m apt -a "name=git state=present" -b

Use state=absent to remove a package. Package modules need privilege escalation on most systems—add -b unless ansible.cfg already sets become.


Manage Services with service and systemd_service Modules

service

service works across init systems; on Rocky Linux 10 it drives systemd units.

bash
ansible lab -m service -a "name=sshd state=started" -b

Sample output:

output
rocky2 | SUCCESS => {
    "changed": false,
    "name": "sshd",
    "state": "started"
}

Other useful state values: stopped, restarted, reloaded.

systemd_service

systemd_service exposes more systemd-specific options (enabled, daemon_reload, unit drop-ins). For a simple start check, output matches service:

bash
ansible lab -m systemd_service -a "name=sshd state=started" -b

Sample output:

output
rocky2 | SUCCESS => {
    "changed": false,
    "name": "sshd",
    "state": "started"
}

Use systemd_service when you need unit-file details; use service for portable start/stop/restart tasks.


Manage Users and Groups

Create a local user:

bash
ansible lab -m user -a "name=demo_ansible state=present shell=/bin/bash" -b

Sample output:

output
rocky2 | CHANGED => {
    "changed": true,
    "group": 1003,
    "home": "/home/demo_ansible",
    "name": "demo_ansible",
    "shell": "/bin/bash",
    "state": "present",
    "uid": 1002
}

Create a group:

bash
ansible lab -m group -a "name=demo_grp state=present" -b

Sample output:

output
rocky2 | CHANGED => {
    "changed": true,
    "gid": 1002,
    "name": "demo_grp",
    "state": "present"
}

Use state=absent to remove users or groups. Set passwords through playbooks and Vault—avoid plain-text password hashes on the command line in shared history.


Manage Files and Directories

The file module creates directories, sets permissions, and removes paths.

bash
ansible lab -m file -a "path=/tmp/ansible-demo state=directory mode=0755" -b

Sample output:

output
rocky2 | CHANGED => {
    "changed": true,
    "gid": 0,
    "mode": "0755",
    "owner": "root",
    "path": "/tmp/ansible-demo",
    "state": "directory"
}

Run the same command again and Ansible reports changed: false when the directory already matches.

bash
ansible lab -m file -a "path=/tmp/ansible-demo state=directory mode=0755" -b

Sample output:

output
rocky2 | SUCCESS => {
    "changed": false,
    "path": "/tmp/ansible-demo",
    "state": "directory"
}

Use state=absent to delete a file or directory, state=touch for an empty file, or state=link for symlinks.


Copy Files to Managed Nodes

The copy module pushes a file from the control node to managed hosts.

On rocky1, create a small source file:

bash
echo "# demo marker" > /tmp/demo-copy.txt

Copy it to rocky2:

bash
ansible lab -m copy -a "src=/tmp/demo-copy.txt dest=/tmp/ansible-demo/marker.txt mode=0644" -b

Sample output:

output
rocky2 | CHANGED => {
    "changed": true,
    "checksum": "13fc6d78c1029585c0b3980b74cca1318148e17b",
    "dest": "/tmp/ansible-demo/marker.txt",
    "mode": "0644",
    "size": 14
}

src must exist on the control node. For content generated from templates, use playbooks and the template module.


Use Privilege Escalation with become

Many modules need root. Pass -b or --become, or set become = True in ansible.cfg as this lab does.

Run whoami as root:

bash
ansible lab -m command -a "whoami" -b

Sample output:

output
rocky2 | CHANGED | rc=0 >>
root

Disable become for one command when your project defaults it on:

bash
ansible lab -m command -a "whoami" -e ansible_become=false

Sample output:

output
rocky2 | CHANGED | rc=0 >>
ansible

-K / --ask-become-pass prompts for the sudo password when NOPASSWD is not configured. In the EX294 lab, passwordless sudo for ansible is already set up on rocky2.


Target Hosts and Groups in Ad Hoc Commands

The first argument is an inventory pattern. Examples:

Goal Command
All hosts ansible all -m ping
One group ansible lab -m ping
One host ansible rocky2 -m ping
Multiple groups (OR) ansible web:db -m ping
Intersection (AND) ansible 'web:&prod' -m ping
Exclude hosts ansible 'all:!db' -m ping

Always confirm the matched set before destructive modules:

bash
ansible lab --list-hosts

See the patterns guide for wildcards, regex (~pattern), and safe --limit usage.


Use --limit with Ad Hoc Commands

--limit narrows the pattern at runtime without editing inventory:

bash
ansible all --limit rocky2 -m ping

List hosts that would run:

bash
ansible all --limit rocky2 --list-hosts

Sample output:

output
hosts (1):
    rocky2

--limit intersects with the pattern. ansible lab --limit rocky2 and ansible all --limit rocky2 both hit rocky2 when it is in inventory. Quote patterns that contain ! so the shell does not interpret them.


Use Check Mode with Ad Hoc Commands

Check mode (-C / --check) tries to show what would change without applying the change:

bash
ansible lab -m dnf -a "name=tree state=present" -b -C

Sample output:

output
rocky2 | CHANGED => {
    "changed": true,
    "msg": "Check mode: No changes made, but would have if not in check mode",
    "results": [
        "Installed: tree-2.1.0-8.el10.x86_64"
    ]
}

Useful before package, file, user, and service changes. Not every module can predict changes in check mode—modules such as command and shell are poor dry-run tools because Ansible cannot know what an arbitrary command would alter.

For file-related modules, add --diff when supported:

bash
ansible lab -m copy -a "src=/tmp/demo-copy.txt dest=/tmp/ansible-demo/marker.txt" -b --diff -C

Sample output:

output
--- before
+++ after: /tmp/demo-copy.txt
@@ -0,0 +1 @@
+# demo marker

rocky2 | CHANGED => {
    "changed": true
}

Treat check mode as a safety check, not a guarantee.


Check Command Output and Changed Status

Ansible prints a status color and JSON per host:

Line prefix Meaning
SUCCESS Module finished; often changed: false
CHANGED Module altered the system (or command/shell ran)
FAILED Non-zero rc or module error
UNREACHABLE SSH or connection failure

Read changed for idempotent modules (package, file, user). command and shell usually show CHANGED whenever they run, even if they only print text.

Add -v for more detail:

bash
ansible lab -m command -a "uname -r" -v

Sample output:

output
Using /home/ansible/ansible-project/ansible.cfg as config file
rocky2 | CHANGED | rc=0 >>
6.12.0-211.16.1.el10_2.0.1.x86_64

Use -vvv when debugging connection or become issues.


Common Ad Hoc Command Examples

Task Command
Ping all lab hosts ansible lab -m ping
Check uptime ansible lab -m command -a "uptime"
Install package (Rocky) ansible lab -m dnf -a "name=tmux state=present" -b
Ensure service running ansible lab -m service -a "name=sshd state=started" -b
Create directory ansible lab -m file -a "path=/opt/app state=directory mode=0755" -b
Copy file to host ansible lab -m copy -a "src=./files/app.conf dest=/etc/app/app.conf" -b
Add user ansible lab -m user -a "name=deploy state=present" -b
Run on one host ansible rocky2 -m ping
Dry run ansible lab -m dnf -a "name=httpd state=present" -b -C
List targets ansible lab --list-hosts
Gather facts ansible rocky2 -m setup -a "filter=ansible_distribution*"
Check module docs ansible-doc ansible.builtin.dnf

Other Useful Ad Hoc Modules

Module Example use
setup Gather facts from a host
stat Check whether a file exists without changing it
lineinfile Edit one line in a file
fetch Copy a file from managed node to control node
reboot Reboot a host safely

Check a file without modifying it:

bash
ansible lab -m stat -a "path=/etc/ssh/sshd_config" -b

Sample output:

output
rocky2 | SUCCESS => {
    "changed": false,
    "stat": {
        "exists": true,
        "path": "/etc/ssh/sshd_config",
        "mode": "0600",
        "size": 3674
    }
}

Use these for quick checks or emergencies. For repeated config changes—especially lineinfile—convert the task to a playbook so the edit is version-controlled.


Troubleshoot Common Ad Hoc Command Errors

Error / symptom Meaning What to check
No hosts matched Pattern or inventory path is wrong Run ansible --version, check config file, then ansible all --list-hosts
UNREACHABLE SSH connection failed Check ansible_host, SSH key, username, firewall, and host-only IP
FAILED! => ... Permission denied Module needed root Add -b or configure become
Missing sudo password sudo requires a password Use -K or configure passwordless sudo in lab
MODULE FAILURE Module ran but failed on target Add -vvv, check Python, package name, or module args
Could not find or access src Local file path is wrong Remember copy src= is on the control node by default
No package matching ... Package name is wrong for that distro Try dnf search on the target or use distro-specific names

Start with connectivity:

bash
ansible all --list-hosts
bash
ansible rocky2 -m ping -vvv

Then test privilege escalation:

bash
ansible rocky2 -m command -a "whoami" -b

Common Mistakes with Ansible Ad Hoc Commands

Mistake Result Fix
Using shell when command is enough Unnecessary shell injection risk Prefer command
Quoting -a wrong in the shell Module args parsed incorrectly Use double quotes outside, single inside: -a 'echo $SHELL'
Forgetting -b for package/file tasks Permission denied on managed node Add -b or set become in ansible.cfg
Wrong inventory path No hosts matched Run from project root or pass -i inventory/hosts
Expecting ICMP from ping Confusion when firewall blocks ICMP ping module tests Ansible connectivity
Piping in command Module fails or splits args wrong Use shell or pass one executable
Secrets on the CLI Passwords in shell history Use playbooks, Vault, or --ask-pass
No --list-hosts before destructive change Wrong machines targeted ansible PATTERN --list-hosts first
Re-running complex workflows ad hoc Untracked, unreproducible changes Convert to a playbook

When to Move from Ad Hoc Commands to Playbooks

Stay on ad hoc while exploring. Switch to playbooks when you notice:

  • The same -m / -a line in your shell history more than twice
  • Multiple modules that must run in order (install, configure, restart)
  • Need for handlers, conditionals, or roles
  • Team review, CI, or scheduled runs
  • Variables from group_vars that belong with the task definition

A practical path: prove the module ad hoc, then paste the working task into playbooks/site.yml and use group_vars and patterns for targeting and variables.


What comes next

  1. Ansible playbook examples — multi-task YAML workflows
  2. group_vars, host_vars and inventory patterns — variables and targeting
  3. ansible.cfg — defaults for inventory, become, and SSH

References


Summary

Ansible ad hoc commands run one module against an inventory pattern: ansible <pattern> -m <module> -a "<args>". Use ping and setup to test hosts and gather facts, command or shell to run programs, and package, dnf, service, file, copy, user, and group for common administration. Add -b when tasks need root, confirm targets with --list-hosts, use -C for dry runs where modules support it, and read changed plus FAILED/UNREACHABLE in the output. Check unfamiliar modules with ansible-doc. When tasks repeat or grow, move them into playbooks.


Frequently Asked Questions

1. What is an Ansible ad hoc command?

A one-line ansible invocation that runs a single module against an inventory pattern—ansible lab -m ping—without writing a playbook file.

2. When should I use ad hoc commands instead of playbooks?

Use ad hoc for quick checks, one-off fixes, and learning modules. Move repeated or multi-step work into playbooks so you can version, review, and reuse it.

3. What is the difference between the command and shell modules?

command runs without a shell—no pipes, redirects, or shell metacharacters. shell runs through /bin/sh when you need pipes, &&, or shell builtins.

4. Does ansible -m ping use ICMP?

No. The ping module checks SSH connectivity and Python on the managed node. It returns pong in JSON—it is not an ICMP ping.

5. How do I limit an ad hoc command to one host?

Use the host name or group in the pattern—ansible rocky2 -m ping—or narrow a broader pattern with --limit rocky2.

6. Can I use check mode with Ansible ad hoc commands?

Yes. Use -C or --check, but only modules that support check mode can predict changes. It works better with modules such as dnf, file, copy, and user than with arbitrary command or shell tasks.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …