Ansible interview questions show up in DevOps, Linux admin, platform, and RHCE-style loops wherever teams automate configuration at scale. Interviewers probe inventory and facts, idempotent modules versus raw shell, variable precedence, handlers and notify, roles and collections, and how you debug a failed play—not whether you can spell YAML.
Below are 34 collapsible questions grouped by topic. Pair this guide with the Ansible tutorial, install Ansible, and lesson articles on variables, handlers, and Vault.
Interview context and how to prepare
What do Ansible interview questions actually test?
Ansible interviews check whether you can automate configuration safely across many hosts—repeatable, idempotent, and readable by the next engineer.
| Area | What interviewers probe |
|---|---|
| Architecture | Agentless SSH, control node vs managed nodes |
| Inventory | Groups, host vars, dynamic inventory |
| Execution | Ad-hoc vs playbooks, modules, check mode |
| Variables | Precedence, facts, templates |
| Control flow | Conditionals, loops, handlers |
| Structure | Roles, collections, project layout |
| Security | Vault, least privilege, become |
| Operations | Delegation, serial, forks, troubleshooting |
| Role | Emphasis |
|---|---|
| Linux admin | Ad-hoc package/service tasks, become when needed |
| DevOps | Playbooks, roles, CI, idempotency |
| Senior / architect | Collections, scalability, content signing |
A strong answer is:
"Ansible interviews usually test whether I can automate systems safely and repeatably: organize hosts with inventory, write idempotent playbooks, manage variables and roles, protect secrets, and troubleshoot failures instead of just running shell commands over SSH."
How do interviewers compare Ansible to Terraform or shell scripts?
| Tool | Model | Interview angle |
|---|---|---|
| Ansible | Task-based configuration and orchestration | Configure OS, apps, middleware on existing hosts |
| Terraform | Declarative cloud resource lifecycle | Create VPCs, instances, IAM |
| Shell scripts | Procedural commands | You build state detection yourself |
| Salt / Puppet | Often agent-based | Ansible's agentless SSH is a selling point |
A strong answer is:
"Terraform is mainly for provisioning and managing infrastructure resources, while Ansible is commonly used to configure operating systems, applications, and services. For configuration work I prefer Ansible modules because many understand current state and can make changes only when necessary; I use shell or command when there isn't a suitable module."
What is a realistic 2–3 week Ansible prep plan?
| Week | Focus | Hands-on drill |
|---|---|---|
| 1 | Inventory, ad-hoc, YAML | Ansible inventory files and ad-hoc commands |
| 1 | Playbooks, modules | Playbook structure with package and service tasks |
| 2 | Variables, facts, templates | Variable precedence and Jinja templates |
| 2 | Roles, handlers, Vault | Roles and Vault with group_vars |
| 3 | Troubleshooting, EX294 scenarios | Debug playbook and timed mock tasks |
Follow RHCE EX294 exam objectives if the loop targets Red Hat certification.
A strong answer is:
"Week one inventory and playbooks on the lab. Week two variables through Vault. Week three I'd run EX294-style scenarios—create users, firewalld, roles—and practice explaining failed tasks from verbose output."
How do beginner and advanced Ansible expectations differ?
| Topic | Beginner | Advanced |
|---|---|---|
| Tasks | apt / dnf install |
Blocks, rescue, throttle, delegate_to |
| Variables | Inline vars: |
Role defaults, Vault, precedence debugging |
| Modules | command/shell | Prefer dedicated modules; custom modules/collections |
| Scale | 10 hosts | Forks, serial, rolling updates, dynamic inventory |
| Testing | Manual run | Molecule, check mode, diff mode in CI |
| Content | Single playbook file | Roles, collections, execution environments |
A strong answer is:
"Junior answers run a playbook. Senior answers explain precedence, when handlers beat inline tasks, how serial protects databases, and why command module is a last resort."
Architecture and inventory
Why is Ansible called agentless?
What interviewers are testing: Whether you understand that agentless means no permanently running Ansible daemon on managed hosts—and what the control node actually needs on targets to execute modules.
The control node is the machine where Ansible is installed and playbooks are executed. Managed nodes are the servers or devices Ansible connects to and configures.
Ansible does not require a permanently running Ansible agent on Linux managed nodes. The control node connects—commonly through SSH—transfers or invokes the required module logic, receives the result, and closes the operation. Most normal Python-based modules require Python on the managed Linux host, while some modules such as raw do not. become (often sudo) applies only to tasks that need elevated privileges—not to every connection.
Flow:
- Resolve inventory and gather facts as needed
- Transfer and execute the module on the target
- Return JSON results to the control node
- Clean up temporary files
No agent to patch on every server—the trade-off is SSH scalability tuning (forks, pipelining).
A strong answer is:
"Agentless means Ansible doesn't require a permanently running Ansible daemon on each managed host. For Linux it normally connects over SSH, executes the required module, collects the result, and disconnects. Most standard modules need Python on the target, and privilege escalation is used only when the task requires it."
How do you structure Ansible inventory?
What interviewers are testing: Whether you can organize hosts and variables so inventory stays maintainable as environments grow, rather than maintaining one flat list of servers.
Static INI or YAML lists hosts and groups:
[web]
web01.example.com
web02.example.com
[db]
db01.example.com
[production:children]
web
dbGroup vars and host vars live in group_vars/ and host_vars/ directories (group_vars host_vars patterns). Dynamic inventory scripts or plugins pull from cloud APIs at runtime.
A strong answer is:
"I group by function—web, db—and layer vars in group_vars. Production children groups let me target
[production]for shared settings without repeating hostnames."
How do host patterns limit which machines run?
What interviewers are testing: Whether you can target the intended hosts precisely and reduce blast radius during testing, troubleshooting, and production changes.
Patterns on ansible or ansible-playbook CLI:
| Pattern | Targets |
|---|---|
web |
All hosts in group web |
web:db |
Union of groups |
web:&production |
Intersection |
web[0:1] |
Slice of group |
*.example.com |
Wildcard (use carefully) |
Limit flag overrides for debugging:
ansible-playbook site.yml --limit web01.example.comA strong answer is:
"Patterns are my blast-radius control. I test plays with --limit on one host before rolling to the whole group, and I use intersections when only production web should get a change."
Ad-hoc commands and playbooks
When do you use ad-hoc commands versus playbooks?
What interviewers are testing: Whether you choose the right automation format—ad-hoc for one-off work versus version-controlled playbooks for repeatable operations.
Ad-hoc — one module, one shot: ping, uptime, quick package check.
ansible web -m ping
ansible web -m ansible.builtin.dnf -a "name=httpd state=present" --becomePlaybooks — ordered tasks, roles, handlers, reusable automation committed to git. See ansible ad-hoc commands.
A strong answer is:
"I use ad-hoc commands for one-off checks or simple administrative actions. If the task needs to be repeated, reviewed, tested, or shared with the team, I put it in a playbook under version control."
What belongs in a well-structured playbook?
What interviewers are testing: Whether you structure playbooks for readability, explicit scope, and maintainability as automation grows.
Typical layout:
- name: Configure web tier
hosts: web
become: true
vars:
http_port: 80
tasks:
- name: Install httpd
ansible.builtin.package:
name: httpd
state: present
- name: Start and enable httpd
ansible.builtin.service:
name: httpd
state: started
enabled: trueUse name: on every task—output and logs become readable. Structure guide: ansible playbook structure.
A strong answer is:
"Every task has a name, hosts and become are explicit, and modules declare state—not bare shell. Handlers and roles come in when the playbook grows beyond a few tasks."
Why prefer Ansible modules over command or shell?
What interviewers are testing: Whether you know when purpose-built modules are preferable to arbitrary command execution—and why state detection matters.
Purpose-built Ansible modules understand the resource they manage and compare current state with desired state. The command and shell modules run arbitrary strings, so you may need safeguards such as creates, removes, or explicit state checks.
| Approach | Risk |
|---|---|
shell: curl install.sh | bash |
Runs every time, opaque changes |
package + template + service |
State-aware, predictable changed status |
Comparison: ansible command vs shell vs raw.
A strong answer is:
"I prefer purpose-built modules because they understand the resource's state and usually change it only when necessary. I use command or shell when no suitable module exists, then add safeguards such as creates or removes and set changed_when when I need accurate reporting."
Idempotency and variables
What is idempotency in Ansible?
What interviewers are testing: Whether you understand Ansible's change-driven execution model—especially why automation should be safe to re-run without unintended side effects.
An operation is idempotent when repeating it does not change the system further—second run reports ok instead of changed.
state: present on a package installs it once; later runs verify that the desired state already exists. By contrast, a command task normally executes each time unless you add state checks such as creates or removes. changed_when can correct change reporting, but it does not by itself make a modifying command idempotent.
Lesson: ansible idempotency.
A strong answer is:
"Idempotent means safe to rerun. I design plays so the second run is a no-op—critical for CI and recovery when a play fails halfway through."
Explain Ansible variable precedence at interview depth.
What interviewers are testing: Whether you know why the same variable can have different values depending on where it is defined—and whether you can debug the final value instead of guessing.
Higher wins (simplified mental model—not the complete precedence table):
| Source | Example |
|---|---|
Extra vars -e |
Highest variable precedence |
| Task vars | Inline on one task |
| Block / play vars | Increasingly local context |
| host_vars / group_vars | Environment and host data |
| Role defaults | Deliberately easy to override |
When a value is wrong, dump merged vars:
ansible-inventory --host web01.example.com --yamlFull order: ansible variable precedence.
A strong answer is:
"Ansible can load the same variable from several places, and the higher-precedence definition wins. I use role defaults for values consumers should be able to override, inventory variables for environment or host data, and extra vars only for deliberate runtime overrides. If a value surprises me, I inspect the resolved inventory rather than guessing."
What are Ansible facts and how do you use them?
What interviewers are testing: Whether you distinguish discovered host facts from play-time variables—and use both appropriately in templates and conditionals.
Facts are variables Ansible gathers from each host—OS version, IP addresses, mount points—via the setup module (often automatic).
Use facts in templates and conditionals:
- ansible.builtin.debug:
msg: "Running on {{ ansible_distribution }} {{ ansible_distribution_version }}"You can provide local facts through /etc/ansible/facts.d/; set_fact creates variables during play execution—they are related but not the same mechanism. Guide: ansible facts and custom facts.
A strong answer is:
"Facts are discovered variables—I branch on ansible_os_family or address ansible_default_ipv4.address. Custom facts extend setup when app-specific metadata must live on the host."
Conditionals, loops, and handlers
How do when conditions control tasks?
What interviewers are testing: Whether you use conditionals to keep one playbook adaptable across hosts, OS families, and prior task results.
when skips tasks based on facts, vars, or register results:
- name: Install firewalld rules on RHEL family
ansible.builtin.include_tasks: firewalld.yml
when: ansible_os_family == "RedHat"Combine with block/rescue for structured error handling (ansible conditional statements, block rescue always).
A strong answer is:
"I use when to make a task run only when its facts, variables, or previous task results match the required condition—for example, including RHEL-specific firewall tasks only when ansible_os_family is RedHat."
How do loops work with with_items and loop?
What interviewers are testing: Whether you understand how loop repeats a task over a list and when register is needed for later inspection.
Modern style uses loop plus {{ item }}:
- name: Add admin users
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: alice, groups: wheel }
- { name: bob, groups: wheel }loop repeats the same task with each item; {{ item }} holds the current value. loop_control can improve labels or rename the loop variable. register stores the combined result when later tasks need to inspect individual iterations. When a looping task notifies a handler, if an iteration reports a change, the handler is notified—you do not need register for that (ansible loop).
A strong answer is:
"loop lets one task operate on a list of values through item. I use loop_control when I need clearer labels or a custom loop variable, and register only when later tasks need the per-item results."
What are handlers and when do they run?
What interviewers are testing: Whether you understand Ansible's change-driven execution model—especially why service restarts should happen only when a dependent task actually changes something.
Handlers run when notified and are de-duplicated, so repeated notifications normally cause one execution at the next handler flush—typical for service restarts after config changes.
Without a handler, three changed configuration tasks could restart the same service three times. Multiple notifications to the same handler are normally consolidated into one restart at the next handler flush.
tasks:
- name: Deploy httpd config
ansible.builtin.template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
notify: Restart httpd
handlers:
- name: Restart httpd
ansible.builtin.service:
name: httpd
state: restartedAnsible automatically flushes handlers at defined points in the play, and meta: flush_handlers can run pending handlers earlier. Lesson: ansible handlers.
A strong answer is:
"If several changed tasks notify the same handler, Ansible normally runs that handler once at the next handler flush. I use handlers so services restart only when a dependent task actually changes something, instead of restarting on every playbook run."
Templates, roles, and collections
How do Jinja2 templates fit into Ansible?
What interviewers are testing: Whether you know when to generate configuration from variables instead of copying a static file, and how to avoid deploying invalid configuration.
The template module renders .j2 files with variables and logic—loops, if, filters—then copies result to managed nodes.
Listen {{ http_port }}
ServerName {{ ansible_fqdn }}Use validate to run nginx -t or httpd -t before replacing live config (ansible template module jinja2).
A strong answer is:
"Templates turn data into config files. I keep logic minimal—heavy logic belongs in vars or filters—and validate before swap when the daemon supports it."
Why use Ansible roles?
What interviewers are testing: Whether you organize reusable automation into roles instead of monolithic playbooks that are hard to test and share.
Roles package tasks, defaults, vars, handlers, templates, files under one directory tree—reusable across playbooks and shareable on Galaxy.
- hosts: web
roles:
- role: geerlingguy.php
- role: company.webserverRole defaults sit low in precedence; roles/ layout is standard for teams. Guide: ansible roles and project directory structure.
A strong answer is:
"Roles are my unit of reuse—webserver role called from staging and prod playbooks with different group_vars. I avoid monolithic site.yml files that nobody can test."
What are Ansible collections?
What interviewers are testing: Whether you understand collections as the versioned distribution format for Ansible modules, plugins, and roles.
Collections provide a namespaced, versionable distribution format for Ansible content such as modules, plugins, and roles—namespace dotted names like ansible.builtin.package or community.general.
Install:
ansible-galaxy collection install community.generalPin versions in requirements.yml; execution environments bundle them for AWX/AAP. Lesson: ansible modules ansible-doc collections.
A strong answer is:
"A collection is a versioned, namespaced package of Ansible content such as modules, plugins, and roles. I pin collection versions in requirements.yml so development, CI, and automation controllers use the same dependencies."
Vault, delegation, and execution control
How does Ansible Vault protect secrets?
What interviewers are testing: Whether you can keep secrets out of plaintext Git while managing vault passwords or keys separately.
ansible-vault encrypts strings or files—passwords, API keys—so encrypted secret data can live alongside automation. The vault password or identity must be managed separately.
ansible-vault encrypt_string --name 'db_password' 'secret' --encrypt-vault-id defaultPatterns with group_vars: ansible vault group_vars host_vars and vault example encrypt string playbook.
A strong answer is:
"Ansible Vault lets me keep encrypted secret data alongside the automation while managing the vault password or identity separately. In CI, I retrieve that credential from the platform's secret store so the repository contains encrypted data rather than plaintext secrets."
When do you use delegate_to?
What interviewers are testing: Whether you understand that delegate_to runs a task on another host while the play still iterates over the original inventory hosts.
delegate_to runs a task on a different host than the current hosts: line—useful when web01 triggers an action on the load balancer or localhost.
| Setting | Where the task runs |
|---|---|
hosts: web |
Normally on web01, web02, … |
delegate_to: lb01 |
That particular task runs on lb01 |
Delegation does not inherently make a task run once. If the play targets ten web hosts, a delegated task can still execute ten times—once per host in the batch—unless you control that with run_once, serial, or similar.
- name: Remove node from LB before deploy
community.general.haproxy:
state: disabled
host: "{{ inventory_hostname }}"
delegate_to: lb01.example.comA strong answer is:
"delegate_to changes where a particular task executes. For example, while deploying web01, I can delegate a task to the load balancer to remove web01 from rotation. It does not automatically mean the task runs only once."
What do serial, strategy, and forks control?
What interviewers are testing: Whether you tune parallelism (forks) vs serial execution and pick the right strategy for rolling updates—not default settings on every playbook.
Ansible separates three parallelism controls:
- forks — maximum worker parallelism across hosts
- serial — how many target hosts belong to each rollout batch
- strategy — how tasks progress across hosts, such as
linear(default) orfree
| Setting | Effect |
|---|---|
| forks (default 5) | How many hosts Ansible can work on concurrently overall |
| serial | Divides the play's target hosts into batches for rolling execution |
| strategy | linear (default) or free execution order |
forks controls parallelism; serial controls batch size. They are not interchangeable. With 20 web servers and serial: 5, Ansible processes five hosts at a time even if forks is higher.
Database patching often uses serial: 1 or serial: "25%" to avoid taking all replicas down.
A strong answer is:
"forks parallelize for speed; serial throttles for safety. I raise forks for stateless web tiers and set serial on clustered databases so one node updates at a time."
Troubleshooting and scenarios
A play failed mid-run—what is your checklist?
What interviewers are testing: Whether you troubleshoot from evidence in a safe order instead of immediately editing the playbook or rerunning it blindly.
- Read the failed task — host, module, message
- Increase verbosity —
-vvvwhen more detail is needed - Connectivity / privilege — SSH, remote user, become
- Variables / facts — confirm the inputs used by the failing task
- Module prerequisites — Python, required package/library, path
- Syntax / check mode — useful when validating the corrected play before rerunning
Guide: debug ansible playbook and ansible troubleshooting.
A strong answer is:
"I start with the failed host, task, module result, and error message, then increase verbosity if needed. I check connectivity, privilege escalation, variables, facts, and module prerequisites before changing the playbook. Once I understand the failure, I prefer a safe idempotent rerun; --start-at-task is useful only when skipping earlier work is safe."
Scenario: task always shows changed but should be idempotent.
What interviewers are testing: Whether you distinguish actual system changes from inaccurate changed reporting—and fix command/shell tasks appropriately.
Common causes are command or shell tasks without creates, removes, or appropriate changed_when, or custom tasks that report changed unconditionally. changed_when: false can suppress change reporting for a read-only command, but it does not make a modifying command idempotent.
Fix patterns:
- name: Install application only if binary is missing
ansible.builtin.command: /tmp/install-myapp.sh
args:
creates: /usr/local/bin/myapp
- name: Ensure line in config
ansible.builtin.lineinfile:
path: /etc/sysctl.d/99-custom.conf
line: "vm.swappiness=10"
create: trueOr set explicit changed_when based on register output.
A strong answer is:
"First I determine whether the task is actually changing the system or only reporting changed. For command or shell I prefer a state-aware module when possible, otherwise I add creates/removes or explicit state checks. I use changed_when to make the reported status accurately reflect what happened."
Scenario: config updated but service did not restart.
What interviewers are testing: Whether you know handlers run only after a notifying task reports changed—and that play failure can block handler execution.
Common causes:
| Cause | Fix |
|---|---|
Task reported ok not changed |
Template/module must detect diff |
Forgot notify |
Add notify on config task |
| Handler notification mismatch | notify must match the handler name or a configured listen topic |
| Play failed before flush | Fix earlier task; meta: flush_handlers if needed |
| Handler in wrong play | Handlers scoped to play that defines them |
If a later task fails on the host, pending handlers normally may not run for that host unless you deliberately use mechanisms such as forced handlers.
A strong answer is:
"I first confirm the configuration task reported changed, then verify its notify target matches the handler name or a listen topic. I also check whether the play failed before handlers were flushed."
Scenario: FAILED — permission denied on /etc/nginx/nginx.conf.
What interviewers are testing: Whether you trace permission failures through remote user, become, filesystem permissions, and—when needed—SELinux.
Work through likely causes in order:
- Remote user — which account is Ansible connecting as?
- Privilege escalation — does this path require
become: true? - Filesystem permissions — owner, group, mode on destination and parent directories
- SELinux — investigate only if normal UNIX permissions do not explain the failure (ansible selinux file context)
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
become: trueA strong answer is:
"I first confirm which remote user Ansible is using and whether that account needs become for the destination. Then I check normal file and directory permissions. If those are correct on an SELinux system, I investigate the SELinux context."
Scenario: deploy new app version without downtime on a web cluster.
What interviewers are testing: Whether you understand rolling deployment safety—controlling blast radius, removing hosts from traffic, validating health, and defining failure/rollback behavior.
Narrative interviewers want:
- Drain one host or small batch from the load balancer
- serial: 1 or small batch on
hosts: web - Deploy — copy artifact or pull image, template config
- Restart/reload with handlers only when something changed
- Health check —
urimodule against local port - Re-enable on LB only after health check passes
- Next batch — repeat for remaining nodes
- On failure — keep node drained or roll back; re-enable only after rollback and health verification succeed
A strong answer is:
"I use serial to update a small batch, drain each node from the load balancer, deploy and restart only when needed, verify health with uri, and only then return the node to service. If deployment or health checks fail, I keep the node drained or roll it back instead of automatically returning a broken instance to traffic."
Scenario: EC2 fleet scales daily—how does inventory stay current?
What interviewers are testing: Whether you understand why static inventory becomes stale in ephemeral infrastructure and how inventory plugins solve it.
Use a dynamic inventory plugin, such as amazon.aws.aws_ec2, so instances are discovered from the cloud API rather than maintained manually in a static file. Use ansible-inventory --list to inspect the resulting inventory, and enable inventory caching when appropriate to reduce repeated API calls.
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
keyed_groups:
- key: tags.Role
prefix: roleThe AWS EC2 inventory plugin comes from the amazon.aws collection (not ansible-core). Configuration files should end in aws_ec2.yml or aws_ec2.yaml per current Ansible documentation.
Run the playbook against the generated role_web group; if inventory caching is enabled, choose a cache timeout appropriate for how quickly the fleet changes.
A strong answer is:
"Static inventory does not scale for autoscaling groups. I use the cloud inventory plugin, keyed groups from tags, and refresh inventory at the start of each pipeline."
How do you use check mode in CI?
What interviewers are testing: Whether you treat check mode as useful validation rather than proof that an apply will succeed.
ansible-playbook site.yml --check --diffCheck mode simulates changes—modules report would change without applying. Pair with diff for config file previews. Not all modules support check mode—verify with ansible-doc supports_check_mode. Registered-variable-dependent conditions can behave differently because preceding actions were not actually performed. --diff may expose sensitive values in CI logs—use selectively.
CI pipeline:
- Lint — ansible-lint, yamllint
- Syntax check
- Check/diff where supported
- Test against disposable or staging infrastructure
- Controlled production rollout
A strong answer is:
"I use check mode as one CI signal, not as proof that the playbook will succeed. I lint and syntax-check first, run --check and selectively --diff where modules support them, and test unsupported or state-dependent tasks against staging before production."
Scenario: run task only if previous command output contains a string.
What interviewers are testing: Whether you understand how one task's structured result can drive later control flow, and whether you avoid marking read-only checks as changes.
Use register to save result, then when on stdout or json fields:
- name: Check if port 8080 is in use
ansible.builtin.command: ss -tln
register: ss_out
changed_when: false
- name: Fail if port already bound
ansible.builtin.fail:
msg: "Port 8080 already in use"
when: "'8080' in ss_out.stdout"Magic variables like hostvars, groups, and inventory_hostname appear in templates—see register and magic variables.
A strong answer is:
"register captures structured results—I chain when conditions on stdout or rc instead of parsing shell in Jinja blindly. changed_when: false on read-only checks keeps output honest."
Advanced playbook control
What is the difference between include_tasks and import_tasks?
What interviewers are testing: Whether you understand static versus dynamic task inclusion—and when loops and conditionals apply to the include versus imported tasks.
| import_tasks | include_tasks | |
|---|---|---|
| Processing | Static | Dynamic |
| When resolved | During playbook parsing | During execution |
| when behavior | Applied to imported tasks | Can determine whether include itself happens |
| Looping the include | Not the normal model | Supported |
| Best use | Fixed task structure | Runtime-selected or repeated task files |
Core distinction:
- import_tasks — static; tasks exist at parse time
- include_tasks — dynamic; evaluated while the play runs
This difference affects loops, conditionals, tags, variable evaluation, --list-tasks, and other execution behavior.
A strong answer is:
"import_tasks is static reuse—the tasks are brought into the play during parsing. include_tasks is dynamic and evaluated during execution, so I use it when the task file itself needs runtime loops or conditional selection."
What is the difference between run_once and delegate_to?
What interviewers are testing: Whether you distinguish run_once from delegate_to—and understand how serial affects both.
These concepts are frequently confused:
| Keyword | What it controls |
|---|---|
| run_once | Execute for only the first host in the current batch |
| delegate_to | Execute the task on a different host than the inventory host |
| run_once + delegate_to | Execute once per batch, on a specific delegated host |
delegate_to changes where the task runs; run_once changes how many times it runs in the batch. With serial, run_once means once per current batch—not necessarily once for the entire play across all batches.
- name: Run database migration once
ansible.builtin.command: /opt/app/migrate
run_once: true
delegate_to: app01.example.comIf you truly need exactly once across the entire play regardless of batches, use an explicit condition such as inventory_hostname == ansible_play_hosts_all[0] or restructure the workflow.
A strong answer is:
"delegate_to chooses where the task runs; run_once chooses how many times it runs in the batch. I use run_once for migrations or global setup—not for draining every web node, which needs a loop or serial strategy per host."
What are Ansible tags and when do you use --tags or --skip-tags?
What interviewers are testing: Whether you use tags to run selective slices of a playbook without maintaining duplicate playbooks.
Tags select which tasks run when you invoke a playbook—they are execution selectors, not conditions like when. Tags are a standard playbook keyword on plays, tasks, blocks, and roles.
- name: Configure web tier
hosts: web
tags: web
tasks:
- name: Install packages
ansible.builtin.package:
name: nginx
state: present
tags: packagesRun only tagged work:
ansible-playbook site.yml --tags packages
ansible-playbook site.yml --skip-tags deployWithout --tags, applicable tasks run normally. With --tags packages, Ansible restricts execution to tasks selected by that tag, subject to special tags such as always. Use --skip-tags to exclude selected tagged work.
Tag caveat with dynamic includes: tags on an include_tasks statement do not automatically propagate to every task inside the included file. Put tags on the included tasks themselves, or use apply: with tags when you need inheritance.
A strong answer is:
"Tags let me run deploy-only or config-only slices without maintaining separate playbooks. I tag at role boundaries and verify with --list-tasks before using --tags in production."
What is an Ansible execution environment?
What interviewers are testing: Whether you understand how teams make the Ansible controller runtime reproducible across developer machines, CI, and automation platforms.
An execution environment (EE) is a container image containing ansible-core, required collections, and controller-side Python and system dependencies. It gives CI, AWX/AAP, and developers a consistent Ansible runtime instead of whatever happens to be installed on a laptop.
Tools like ansible-navigator can run playbooks inside an EE image locally, but the interview point is reproducibility—not building a full EE workflow from scratch.
A strong answer is:
"An execution environment makes the Ansible control runtime reproducible. Instead of depending on whatever Python packages and collections happen to be installed on a laptop, CI and AAP run a pinned container image."

