Prepare for Ansible interview questions grouped by experience level.
Ansible Interview Question & Answers
0-2 Years
Ansible is a configuration management and automation tool, letting you actually define a server's desired configuration through simple, human-readable YAML files, applied consistently across genuinely many servers at once. It solves the genuine problem of manually configuring servers by hand, which is genuinely slow, error-prone, and hard to reliably reproduce.
Ansible doesn't genuinely require any special software installed on a target server ahead of time, communicating instead over standard SSH (or WinRM for Windows). This is genuinely different from a tool like Chef or Puppet, which typically requires a genuine agent running on every single managed server.
Ansible uses genuinely simple YAML playbooks and requires genuinely no agent installed on target servers. Chef and Puppet both genuinely require an agent installed on each managed server and use their own genuinely specific configuration language, generally offering more genuinely powerful capability at the cost of a steeper genuine learning curve.
Check mode (--check) genuinely simulates a playbook run without actually making any real change, reporting what would happen instead. Diff mode (--diff) genuinely shows the actual before-and-after content of a file being changed. Combined together, --check --diff genuinely lets you preview both whether a change would happen and exactly what that change would actually look like, without applying it.
Ansible is genuinely focused on configuring software and settings on an already-existing server, applying a defined configuration state. Terraform is genuinely focused specifically on provisioning infrastructure itself, servers, networks, databases, and the two genuinely, commonly get used together in the exact same overall pipeline.
An idempotent operation produces the exact same genuine result no matter how many times it's actually run. It matters for Ansible because a playbook is often genuinely run repeatedly against the exact same servers, and it should genuinely converge them to the desired state without causing an unintended, unexpected side effect on a genuinely repeated run.
The control node is the genuine machine where Ansible itself is actually installed and run from. A managed node is a genuine target server that Ansible actually connects to and configures, typically over SSH, with genuinely no Ansible software installed on the managed node itself.
An inventory is a genuine list of servers (and groups of servers) that Ansible can actually target, letting a playbook be genuinely applied to a specific, defined set of machines, like all web servers or genuinely just the production database servers.
By default, Ansible genuinely looks for an inventory file at /etc/ansible/hosts, written in a genuinely simple INI-style format, listing hostnames or IP addresses, optionally organized into genuinely named groups using bracketed headers.
[webservers] web1.example.com web2.example.com genuinely defines a group named webservers containing those two specific hosts, letting a playbook genuinely target that entire group at once by referencing its own group name.
Using a [group_name:children] header lets you genuinely define a group composed of other, already-defined groups, letting you actually target a genuinely broader category, like all servers, composed of the webservers and dbservers groups together.
A host variable assigns a genuinely specific value to just one particular host, like web1.example.com ansible_user=deploy, letting that one specific host use a genuinely different connection setting or configuration value than the rest of its group.
A static inventory is a genuinely fixed, manually maintained text file listing hosts. A dynamic inventory genuinely queries an external source, like a cloud provider's own API, at runtime to automatically generate the genuinely current, actual list of hosts, which is genuinely more practical when servers are frequently created and destroyed.
A playbook is a genuinely YAML file defining a set of tasks to actually be applied to a group of servers, describing the genuinely desired state, like ensuring a specific package is installed or a specific service is genuinely running, rather than the exact step-by-step commands to actually get there.
A play maps a genuine set of tasks to a specific group of hosts, and a genuinely single playbook file can contain multiple plays, each genuinely targeting a different group of hosts or accomplishing a genuinely different overall goal.
A task represents one genuinely single action to actually perform, like installing a package or copying a file, typically genuinely calling a specific Ansible module with the genuine parameters needed to actually perform that action.
ansible-playbook playbook.yml genuinely executes every play and task defined in the specified playbook file, against the hosts genuinely defined in your default inventory (or a genuinely specific inventory passed with the -i flag).
YAML genuinely uses indentation to represent structure and nesting, similar to how Python uses indentation for code blocks. An incorrect indentation level in a playbook can genuinely change its actual meaning entirely, or cause a genuine parsing error preventing the playbook from actually running at all.
hosts: webservers genuinely specifies which group (or specific host) from the inventory that particular play's tasks should actually be applied to, and it can also genuinely be set to all to target every single host in the inventory.
A module is a genuinely small, reusable unit of code performing one genuinely specific action, like installing a package or copying a file. A task genuinely calls a specific module along with the actual parameters that module needs to actually perform its genuine job.
The apt module genuinely installs, updates, or removes a package on a Debian-based system, similar to running apt install manually, but done genuinely declaratively and idempotently through Ansible instead.
The copy module genuinely transfers a file from the control node to a managed node, letting you actually place a genuinely specific configuration file or script onto a target server as part of a playbook's own execution.
The service module genuinely manages a service's state on a managed node, like ensuring a specific service is genuinely started, stopped, or enabled to actually start automatically on boot.
The file module genuinely manages file and directory properties, like creating a directory, setting genuinely specific permissions, or removing a file, giving you actual, direct control over a file system object's own state.
ansible-doc module_name genuinely displays that specific module's own full documentation directly in the terminal, including every available parameter and a genuinely practical usage example, without needing to actually leave the command line to look it up separately.
Under a vars key at the play level, like vars: package_name: nginx, defines a genuine variable that can then actually be referenced elsewhere in that play using the genuine {{ package_name }} syntax.
{{ variable_name }} is Jinja2 templating syntax, and Ansible genuinely uses it throughout playbooks to actually substitute a variable's real value into a task's parameter or a genuinely generated file at runtime.
ansible-playbook playbook.yml --extra-vars 'package_name=nginx' genuinely passes that variable's value directly at runtime, letting you actually override a genuine default without needing to edit the playbook file itself.
A file placed under group_vars/group_name.yml genuinely defines variables that automatically apply to every host in that specific group, letting you actually organize environment-specific or group-specific configuration cleanly, genuinely separate from the playbook's own logic.
A group_vars file, under group_vars/group_name.yml, genuinely applies to every host in that named group. A host_vars file, under host_vars/hostname.yml, genuinely applies to just that one specific individual host, overriding a genuine group-level value when both actually exist.
A variable lets the exact same playbook genuinely work across different environments or use cases, like development versus production, simply by actually supplying a genuinely different variable value, rather than needing to maintain several genuinely separate, near-identical playbooks for each specific case.
An ad hoc command runs a genuinely single Ansible module directly from the command line, without needing to actually write a playbook file first. It's genuinely useful for a quick, one-off task, while a playbook is genuinely better suited for a repeatable, more complex, multi-step process.
ansible all -m ping genuinely runs the ping module against every host defined in the inventory, quickly confirming whether Ansible can actually establish an SSH connection and communicate correctly with each one.
ansible webservers -a 'df -h' genuinely runs that raw shell command against every host in the webservers group, using the -a flag to actually pass the command module's own argument directly.
-m genuinely specifies which module to actually use, like ping or apt. -a genuinely supplies that module's own required arguments, like the specific package name for the apt module, or the raw command string for the genuine shell or command module.
A genuinely specific module like apt is idempotent and understands the actual desired end state, checking whether the package is already genuinely installed before doing anything. The command module simply genuinely runs a raw shell command every single time, with genuinely no built-in awareness of whether that action actually needs to happen at all.
3-6 Years
A handler is a genuinely special kind of task that only actually runs when explicitly notified by another task, and even then, only genuinely once at the very end of a play, regardless of how genuinely many tasks actually notified it. It solves the genuine problem of needing to restart a service only if its configuration file actually changed, rather than genuinely restarting it unconditionally on every single playbook run.
Adding notify: restart nginx to a task genuinely triggers the handler named restart nginx if that specific task actually reports a changed state, and the handler itself is genuinely defined separately under the play's own handlers section.
A tag lets you genuinely label a specific task or a set of tasks, and later actually run only the tasks with a specific tag, using --tags at the command line, rather than needing to genuinely run the entire playbook every single time.
Adding when: ansible_os_family == 'Debian' to a task genuinely runs it only if that specific condition actually evaluates to true, letting a genuinely single playbook adapt its behavior based on a fact or a variable's own actual current value.
A fact is a genuine piece of information Ansible automatically gathers about a managed node, like its operating system or IP address, at the genuinely start of a playbook run, without you needing to actually define it manually the way you would a genuinely regular variable.
Modern Ansible genuinely nests gathered facts under an ansible_facts dictionary, like ansible_facts.os_family, rather than the older style of a plain top-level variable like ansible_os_family. The older style still genuinely works for backward compatibility, but the namespaced style is genuinely recommended since it avoids a potential naming collision with a user-defined variable sharing the same name.
A role is a genuinely structured, reusable collection of tasks, handlers, variables, and templates, organized in a genuinely standard directory layout. It solves the genuine problem of a large, monolithic playbook becoming hard to actually organize and reuse, letting you genuinely package a specific piece of configuration logic once and reuse it across multiple, genuinely different playbooks.
A role typically genuinely contains tasks/, handlers/, templates/, files/, vars/, and defaults/ subdirectories, each genuinely holding a specific type of content, with main.yml inside tasks/ genuinely serving as the role's own primary entry point.
roles: - role_name under a play genuinely applies that role's own tasks, handlers, and default variables to the hosts targeted by that specific play, letting you actually reuse a genuinely well-defined, packaged piece of configuration.
Variables in defaults/main.yml have the genuinely lowest precedence and are meant to actually be easily overridden by whoever uses the role. Variables in vars/main.yml have genuinely higher precedence, intended to actually stay more fixed, representing the role's own genuinely internal, less commonly overridden configuration.
Ansible Galaxy is a genuine public repository of community-contributed roles, letting you actually download and use a well-maintained, genuinely pre-built role for a common task, like configuring nginx or PostgreSQL, rather than genuinely writing that same configuration logic entirely from scratch yourself.
A template file, typically ending in .j2, contains genuine static text combined with Jinja2 expressions, like {{ variable_name }}. The template module genuinely renders that file, substituting real variable values, and copies the actual, resulting output onto the managed node.
The copy module genuinely transfers a file exactly as it is, with no genuine variable substitution performed at all. The template module genuinely processes the file through Jinja2 first, substituting variables and evaluating any genuine logic, before actually placing the rendered result onto the managed node.
{% if some_variable %}some text{% endif %} genuinely includes that text in the rendered output only if some_variable actually evaluates to true, letting a genuinely single template adapt its own generated content based on a variable's real, current value.
{% for item in some_list %}{{ item }}{% endfor %} genuinely iterates over the list, rendering the enclosed content once for each individual item, letting you actually generate a genuinely repeated block of configuration, like a list of allowed IP addresses in a firewall config file.
A filter transforms a genuine value within a Jinja2 expression, applied using the pipe symbol, like {{ variable_name | upper }}, which genuinely converts the variable's value to uppercase before actually inserting it into the rendered output.
loop: - item1 - item2 on a task genuinely runs that same task once for each item in the given list, with each item genuinely accessible inside the task through the special item variable.
with_items is Ansible's genuinely older looping syntax, largely superseded by the genuinely simpler, more consistent loop keyword, which Ansible's own documentation now genuinely recommends using for new playbooks going forward.
Both loop and when can genuinely be applied to the exact same task together, and the when condition is genuinely evaluated separately for each individual item in the loop, letting you actually skip a specific item that doesn't genuinely satisfy that condition, while still processing every other item that actually does.
register: result genuinely captures a task's own output into a variable named result, letting a genuinely later task in the same play reference that captured output, commonly used together with a when condition to actually make a decision based on a genuinely previous task's own result.
Variable precedence defines the genuine order Ansible follows when the exact same variable name is genuinely defined in multiple different places, like a role default versus a command-line extra-var. It matters because understanding it explains exactly which genuine value actually wins when a genuine conflict occurs.
Facts are genuinely automatically discovered pieces of information about a managed node, like its operating system and network interfaces. ansible hostname -m setup genuinely displays every fact Ansible has actually gathered about that specific host.
Setting gather_facts: false at the top of a play genuinely skips the fact-gathering step entirely. You might genuinely want to for a genuinely simple playbook that doesn't actually need any facts, since skipping that step meaningfully speeds up the playbook's overall execution time.
A custom fact lets you actually define genuinely additional, application-specific information about a managed node beyond what Ansible automatically gathers by default, typically placed in a genuinely specific file on the managed node itself under /etc/ansible/facts.d/.
6-8 Years
A block groups genuinely several tasks together, letting you actually apply a shared when condition or error handling to every task within it at once, rather than genuinely repeating that same condition or handling on each individual task separately.
A block contains the genuine primary tasks to actually attempt. rescue genuinely runs only if a task within that block actually fails, letting you actually handle the failure gracefully. always genuinely runs regardless of whether the block genuinely succeeded or failed, similar in spirit to a try-catch-finally structure in a genuinely traditional programming language.
ignore_errors: true genuinely lets the playbook continue running even if that specific task actually fails. It should genuinely be used sparingly because a genuinely failing task often indicates a real problem that a playbook shouldn't simply gloss over and continue past silently.
delegate_to: localhost genuinely runs a specific task on a genuinely different host than the one the overall play is actually targeting. A genuinely practical use case is updating a load balancer's own configuration from the control node itself, rather than from each individual web server being actually configured in that play.
serial: 2 genuinely limits how many hosts a play processes at once, running the play against just 2 hosts at a time before actually moving on to the next batch. It solves the genuine problem of wanting a genuinely rolling update, avoiding taking every server offline for an update simultaneously.
max_fail_percentage sets a threshold on how many hosts within a batch can genuinely fail before Ansible stops the entire play early, rather than continuing to roll the update out to every remaining batch despite a genuinely significant number of failures already having occurred. It solves the genuine problem of a bad update continuing to spread across an entire fleet before anyone notices the failures piling up.
run_once: true on a task genuinely ensures it actually runs only on the first host in the group being targeted, useful for a genuinely one-time action, like a database migration, that shouldn't genuinely be repeated once per host if the play targets several genuinely identical servers.
Ansible Vault genuinely encrypts a sensitive file, or a specific sensitive value within a file, like a password or an API key, letting you actually store that sensitive data safely in version control without exposing it in genuinely plain, readable text.
ansible-vault encrypt secrets.yml genuinely encrypts the specified file in place, prompting you to actually set a vault password used both to actually encrypt it now and to actually decrypt it later when the playbook is genuinely run.
ansible-playbook playbook.yml --ask-vault-pass genuinely prompts for the vault password at runtime, decrypting the referenced encrypted content in memory just long enough to actually use it, without ever writing the genuinely decrypted content back to disk.
A vault password file stores the genuine vault password in a file (ideally with genuinely restricted permissions), letting an automated pipeline actually supply it with --vault-password-file rather than genuinely requiring an interactive prompt, which wouldn't genuinely work in a fully automated, non-interactive context.
ansible-vault encrypt_string 'secret_value' --name 'variable_name' genuinely produces an encrypted block you can actually paste directly into a regular, otherwise-unencrypted YAML file, letting only that genuinely specific sensitive value stay encrypted while the rest of the file remains genuinely readable.
8-10 Years
Using the aws_ec2 dynamic inventory plugin, configured through a genuine YAML file specifying filters and grouping rules, automatically queries AWS's own API at runtime to build the actual current inventory, rather than needing to genuinely, manually maintain a static list of hosts as instances are created and destroyed.
Tower/AWX provides a genuine web-based interface for actually managing playbook execution, scheduling, role-based access control, and centralized logging, capabilities that running ansible-playbook directly from a genuinely plain command line simply doesn't provide on its own.
Increasing the forks setting genuinely lets Ansible process more hosts in parallel at once, and using an efficient connection method like ControlPersist (SSH connection reuse) meaningfully reduces the genuine overhead of repeatedly establishing a genuinely new SSH connection for every single task.
Mitogen is a genuinely alternative execution strategy that meaningfully speeds up Ansible by reducing the overhead of genuinely repeatedly copying and executing Python code over SSH for every single task, instead genuinely keeping a persistent connection and reusing it more efficiently across multiple tasks.
Organize configuration using genuinely well-defined roles for each reusable piece of functionality, environment-specific inventory files and group_vars for genuine environment differences, and a genuinely clear, consistent naming convention, rather than one single, enormous, monolithic playbook covering genuinely everything.
I'd weigh whether the task is genuinely about provisioning infrastructure itself, which fits Terraform's own declarative provisioning model well, versus configuring software and settings on an already-existing server, which fits Ansible's own genuine strength more naturally.
Using serial to genuinely process a limited batch of servers at a time, combined with pre and post tasks that genuinely remove a server from the load balancer before updating it and genuinely add it back once verified healthy, achieves a genuine rolling update without ever taking the entire fleet offline at once.
The pipeline genuinely runs ansible-playbook automatically as a deployment step, typically after building and testing an application's own code, using a genuinely specific inventory and vault credentials configured securely within the CI/CD platform's own settings.
Molecule provides a genuine framework for actually testing an Ansible role in an isolated environment, like a Docker container, verifying it applies correctly and genuinely remains idempotent on a genuinely second run, catching a real regression before the role is actually used against real, production infrastructure.
Running the exact same playbook a genuinely second time immediately after the first run should report genuinely zero changed tasks, since every task should already have actually achieved its own desired state on that first run, confirming the playbook doesn't genuinely, unnecessarily reapply the same change repeatedly.
The --check flag genuinely runs the playbook in a dry-run mode, showing what would actually change without genuinely making any real changes at all, letting you actually review a proposed change before genuinely committing to it on real, live production servers.
Molecule tests genuinely verifying the role applies correctly and remains idempotent across multiple genuinely different target operating systems, combined with a genuine linting step, like ansible-lint, catching a genuinely common style or correctness issue before the role update is ever actually merged.
Integrating Ansible with an external secrets manager, like HashiCorp Vault, through a genuine lookup plugin lets a playbook actually retrieve a secret dynamically at runtime, rather than storing even an Ansible Vault-encrypted secret genuinely, statically within the actual codebase itself.
10+ Years
I'd weigh Ansible's genuinely agentless, simpler learning curve against the real, specific capability another tool might offer that genuinely better fits the organization's own existing infrastructure or team expertise, rather than assuming one tool is genuinely, universally better in every possible situation.
I'd migrate incrementally, starting with the genuinely highest-value, most frequently-touched servers to build organizational confidence and expertise, running Ansible's --check mode against existing servers first to actually understand the real gap between current state and the genuinely intended configuration before applying anything.
I check whether it's genuinely idempotent, whether it handles a genuine failure gracefully rather than leaving a server in a genuinely inconsistent, half-configured state, and whether it's genuinely consistent with roles already established elsewhere in the organization's own codebase.
Automate what can genuinely be automated, ansible-lint enforced directly in CI, so standards aren't purely a matter of individual opinion during manual review. For architectural conventions that genuinely resist full automation, I'd document the handful of decisions that actually matter most.
I'd weigh the genuine time saved by centralized scheduling, role-based access control, and audit logging against the real, ongoing cost of running and maintaining that genuinely additional infrastructure, and whether the organization's own current pain, inconsistent manual playbook execution, genuinely justifies that specific investment.
I'd check for a genuine environmental difference on the specific failing servers, a genuinely different OS version, an existing configuration state the playbook didn't genuinely anticipate, since an intermittent, host-specific failure often traces back to a genuine assumption the playbook made that doesn't actually hold true everywhere.
A scheduled, automated ansible-playbook --check run, checked for whether it genuinely reports any actual proposed change, can alert a team if real server configuration has genuinely drifted from what the playbook expects, catching a manual, out-of-band change before it actually causes a genuine, real problem.
Treat the role's actual input variables and its own genuine behavior as a contract with every consuming playbook. Adding a genuinely new optional variable is generally safe. Changing an existing variable's meaning or an existing default value needs a documented, communicated transition rather than a silent, breaking change.
I'd first genuinely identify exactly which specific servers actually completed successfully versus which ones didn't, using Ansible's own output or a limit flag to actually target just the remaining, un-updated servers on a genuine retry, rather than genuinely re-running the entire playbook against every server indiscriminately.
I'd tune the forks setting and connection strategy to genuinely improve parallelism as the fleet grows, and break a genuinely monolithic playbook into smaller, more targeted playbooks where reasonable, since a single, enormous playbook's execution time can genuinely become impractically slow against a genuinely very large fleet.
This is a judgment question interviewers use to see how you reason under genuine uncertainty, not to test a specific textbook fact. A strong answer names the actual constraint that forced the decision, the realistic options that were genuinely on the table, why you picked one knowing it wasn't guaranteed to be right, and what you'd do differently with what you know now.
I'd walk through an actual, real scenario together, running their playbook a genuinely second time and showing concretely how it reapplies an action unnecessarily, rather than explaining idempotency as an abstract concept in isolation. Seeing that genuinely real, unnecessary reapplication tends to build the habit of reaching for a genuinely proper, idempotent module far more effectively.
I wouldn't lead with automation as an abstract best practice. I'd point to a specific, real, already-experienced incident caused by an inconsistency between two genuinely, supposedly identical, manually configured servers, and show concretely how Ansible's own consistent, repeatable configuration would have genuinely prevented that exact same specific problem.
I'd bring the actual, concrete question of how often that exact same configuration genuinely repeats across multiple different playbooks or projects into the discussion, rather than a general, abstract preference for modularity. Grounding the discussion in the specific, real usage pattern resolves it faster than an abstract debate.
I'd translate the investment into terms leadership already tracks: the hours spent on repetitive manual configuration each month, and the cost of a specific past incident caused by inconsistency between servers that should have been identical. Framed as recovered engineering time and reduced incident risk, it competes far better for prioritization than framed as a general tooling upgrade.




