Ad-hoc Commands in Depth
An ad-hoc command runs exactly one module against a host pattern in a single shot, no playbook involved: ansible web -m service -a "name=nginx state=restarted" --become. It picks the hosts, the module, and the module's arguments straight from the command line and reports per host, which makes it the fastest way to touch the fleet for a one-off.
It is the right tool for a single operation across many machines — restart a service, install a package once, read a fact, check uptime everywhere — and the wrong tool for anything you will run twice. Nothing about an ad-hoc command is version-controlled, reviewed, or repeatable; it lives in your shell history and nowhere else.
The Anatomy of a Run
The shape is ansible <pattern> -m <module> -a "<args>": the pattern selects hosts, -m names the module, and -a carries its arguments. The pattern resolves through the same inventory a playbook uses, so ansible web hits web1.larkspur.io and web2.larkspur.io together and prints a result block per host.
That shared resolution is the point. The groups you built in Chapter 2 — web, db, all — mean the same thing here as in a play, so an ad-hoc command is a play's single task lifted onto the command line. Learn the pattern syntax once and it serves both.
ansible <pattern> -m <module> -a "<args>" [--become]ok / changed / failedThe Argument String
Module arguments are key=value pairs inside one quoted string: -a "name=nginx state=restarted". These are the exact arguments the module takes in a playbook task, flattened onto the command line, which is why running a module ad-hoc is the quickest way to learn its options without writing any YAML.
The quoting carries weight. The whole string is one argument to ansible, so the shell must not eat the spaces or the equals signs — wrap it in double quotes and let the module parse the pairs. A mis-quoted string reaches the module garbled and fails with a parse error that looks nothing like the real problem.
Privilege with --become
Most real operations need root: installing a package, restarting a system service, editing a file under /etc. The --become flag runs the module under sudo on the Ubuntu nodes, escalating after the SSH login. Without it the command connects as your unprivileged login user and the operation is refused.
Forgetting --become is the difference between a clean run and a permission error that reads like a broken module. The apt module is not failing — it simply cannot write to the package database as a normal user. The fix is the flag, not the module.
Fleet Diagnosis
The read-only half of why ad-hoc exists is answering "what does the fleet look like right now?" across every host at once. ansible all -m command -a "uptime" reports load on all of them, ansible db -m setup -a "filter=ansible_mounts" shows the database hosts' mounts, and ansible web -m service_facts lists which units are running on the web tier.
These gather information and change nothing, so they are safe to fire at all without a second thought. Using -m setup and -m service_facts to explore a host's facts before you reference them in a template or conditional beats guessing at fact names from memory.
One-off Convergence and the Firm Line
Ad-hoc can also change state once: ansible web -m apt -a "name=htop state=present" --become installs a package idempotently without writing a play. That is legitimate for break-glass and exploration — and it is exactly the thing a role exists to replace the moment the change becomes permanent.
The firm line is this: ad-hoc commands leave no record, get no review, and re-running a remembered chain of them by hand is how a server becomes a snowflake again. The moment a change belongs in the configuration, it belongs in a playbook, not your shell history. Keep ad-hoc for diagnosis and the genuinely one-time fix.
- Running
ansible all -m service -a "name=postgresql state=restarted" --becometo "restart one thing" and bouncing PostgreSQL ondb1.larkspur.ioalong with every other service-bearing host, becauseallwas the pattern and the blast radius was never checked. - Forgetting
--becomeonansible web -m apt -a "name=nginx state=present"and reading the permission error that follows as a brokenaptmodule instead of a missing privilege escalation. - Building a server's configuration out of a remembered sequence of ad-hoc commands, so there is no record of what was done and the next person rebuilds
web2by guesswork. - Quoting the
-astring wrong so the shell eats the spaces or the equals signs, and the module receives garbled arguments and fails with a confusing parse error rather than the intended action. - Reaching for
-m shell -a "systemctl restart nginx"when-m service -a "name=nginx state=restarted"exists, throwing away idempotency and honest change reporting for a one-off that did not need the shell.
- Scope the pattern tightly —
web1.larkspur.io, notall— for any ad-hoc command that changes state, since it runs immediately with no plan to review and no undo. - Keep ad-hoc for diagnosis and break-glass: gather a fact, restart a stuck service, check uptime. Promote anything you would run twice into a playbook.
- Pass
--becomewhenever the operation touches root-owned resources, and reach for the real module (apt,service,copy) overcommand/shelleven in a one-off. - Use
-m setupand-m service_factsad-hoc to explore a host's facts before you reference them in templates or conditionals, rather than guessing at fact names. - Double-quote the
-aargument string so the shell hands the module one intact set ofkey=valuepairs, not a shredded one.
Knowledge Check
When is an ad-hoc command the right tool rather than a playbook?
- For a genuine one-off — gathering a fact, restarting a stuck service — that will not run again
- For the standard production deployment that the whole team runs by hand on every single release
- For any production change that has to be code-reviewed and signed off well before it ever ships
- For building a brand-new server's full configuration from scratch, one ordered step at a time
What does --become change about an ad-hoc run?
- It runs the module under sudo on the node, so root-owned operations succeed instead of failing
- It makes the whole command idempotent by checking the live host's state before it acts on it
- It widens the host pattern to include every reachable host listed across the whole inventory
- It records the full command to a central server log so that the entire run becomes auditable later
Why is all the dangerous pattern for a state-changing ad-hoc command?
- It fires immediately on every host with no plan to review, so "restart one thing" hits the whole fleet
- It is much slower than a narrow pattern because Ansible has to open a connection to far more hosts
- It silently skips over every host that happens to be unreachable, quietly hiding those failures from you
- It disables privilege escalation for the entire run, so the command fails on any root-owned resource
Why prefer -m service -a "name=nginx state=restarted" over -m shell -a "systemctl restart nginx" even for a one-off?
- The real module is idempotent and reports change honestly, while the shell command discards both for no gain
- The shell module is simply incapable of restarting a system service like nginx at all
- The service module restarts the unit cleanly without ever needing
--becomeat all - The shell module ignores the targeting host pattern entirely and runs the command on the control node instead
You got correct