AnsibleVII · Ad-Hoc ExecutionAd-hoc execution
What an ad-hoc change leaves behind
What you'll learn
- Enumerate what an ad-hoc run does and does not record by default
- Configure a controller log path and result capture as compensating controls
- Explain why shell history is not an audit trail
- State the course position on ad-hoc inspection versus ad-hoc change
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
Three weeks after the change, someone asks: who reconfigured the web tier on the 14th, what exactly did they change, and was it reviewed?
If the change was a playbook, the answer is a commit hash. If it was an ad-hoc command, the honest answer is that nobody knows, and the reconstruction that follows will take a day and end in a guess.
This lesson establishes the position the rest of the course takes: an ad-hoc inspection is fine forever. An ad-hoc change is a debt, and the interest is paid during the incident review.
What is actually recorded
By default, an ad-hoc run writes nothing anywhere. Enumerate the places you might look:
| Where you might look | What is there |
|---|---|
| A log on the controller | Nothing, unless log_path is set. It is unset by default. |
| A log on the managed node | Nothing from Ansible. sudo logs the escalation if -b was used. |
| Version control | Nothing. There was no file. |
| A change ticket | Only if a human wrote one. |
| The terminal | Everything, until the terminal closes. |
| Shell history | The command line, on one workstation, for one user. |
The last row is the one people lean on, and it is worth being precise about why it does not work.
The four questions a change record must answer
An incident review asks these. Test any record you have against all four.
- Who ran it?
- What did it do — not what was typed, but what changed on each host?
- Where — which hosts, exactly, and which of them actually changed?
- What was the previous state, so it can be restored?
A playbook in Git answers 1 and 2 from the commit, 3 from the recap and the inventory at that revision, and 4 from the diff plus whatever the change replaced.
Shell history answers a corrupted version of 2 and nothing else. Note
particularly that question 4 is unanswerable by any ad-hoc mechanism.
No module you ran recorded the prior value; lineinfile replaced a line
and did not keep the old one unless you passed backup=yes, which nobody
does at a prompt.
The two compensating controls
If you are going to run ad-hoc changes — and every estate does, sometimes — these are the mechanisms that make it partially defensible. Neither is on by default.
log_path: the controller writes a log
$ ANSIBLE_LOG_PATH=./adhoc.log ansible localhost -m ansible.builtin.ping$ cat ./adhoc.log2026-08-11 21:01:05,787 p=4037136 u=opsuser n=ansible WARNING| [WARNING]: No inventory was parsed, only implicit localhost is available
2026-08-11 21:01:05,989 p=4037136 u=opsuser n=ansible INFO| localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}Illustrative output
Set it permanently in the project ansible.cfg so every operator on the
controller gets it without thinking:
[defaults]
log_path = /var/log/ansible/ansible.log
$ ansible-config dump | grep DEFAULT_LOG_PATHDEFAULT_LOG_PATH(/home/opsuser/estate/ansible.cfg) = /var/log/ansible/ansible.logIllustrative output
What this buys: attribution and timing, for runs made on that controller
by that config. It records u= — the local user on the controller — and
the per-host results.
What it does not buy: anything about a run made from a laptop, from a
different directory with a different ansible.cfg, or with
ANSIBLE_LOG_PATH unset in the environment. It is a controller-local
convention, not an enforcement mechanism, and an operator who bypasses it
leaves no trace of having done so.
--tree: the results become files
$ ansible production -i inventory.ini -m ansible.builtin.setup -a "filter=ansible_kernel" --tree ./run-2026-08-11This is the only ad-hoc mechanism that captures the outcome rather
than the intent. One file per host that answered, containing exactly what
the module returned — including changed, so you can tell which hosts
actually moved.
It answers question 3 properly, which shell history cannot touch. It still says nothing about question 4.
What the auditor is actually asking
Compliance regimes phrase it differently, but the question is the same one an incident review asks, and it has a shape:
Demonstrate that changes to production systems are authorised, recorded, and attributable to an individual.
Take that clause apart against an ad-hoc change:
- Authorised — by whom? There was no review step. The authorisation and the execution were the same keystroke.
- Recorded — where? See the table at the top of this lesson.
- Attributable — to the shell history of a workstation, if it still exists, if the account was not shared, and if the timestamps were enabled.
None of the three survives. And the failure is not that the change was wrong. It may have been exactly right. The failure is that you cannot demonstrate anything about it, which under most regimes is treated identically to not having controlled it at all.
Knowledge check
Knowledge check · 4 questions
Q1. An ad-hoc change was made with `-b` three weeks ago. Which record, present by default, comes closest to attributing it?
Q2. Setting log_path in the project ansible.cfg means every ad-hoc change against this estate will be logged.
Q3. Which questions can --tree output answer that shell history cannot? Select all that apply.
Q4. Why does the course treat ad-hoc inspection as permanently acceptable while treating ad-hoc change as a debt?
Passing score: 75%. Answers are checked in this browser.