← Back to Blog
Ansible Interview Questions 2026: The Complete Guide
Ansible16 min read·Apr 20, 2026
By InterviewDrill Team

Ansible Interview Questions 2026: The Complete Guide

Ansible remains the dominant tool for configuration management and ad-hoc automation in 2026. Whether it's provisioning servers, deploying applications, or managing configuration drift, Ansible knowledge is tested heavily in senior DevOps interviews. Here are the 20 questions that matter most.


Section 1: Ansible Architecture & Fundamentals

1. Explain the Ansible architecture

Why they ask this: They want to confirm you understand the agentless model and its implications.

Ideal answer:

Ansible is agentless — no daemon runs on managed nodes. The control node (where Ansible runs) connects to managed nodes via SSH (Linux) or WinRM (Windows) and executes modules remotely.

Components:

  • Control node: Where Ansible is installed and run. Not a daemon — just the Ansible CLI tools.
  • Managed nodes (hosts): Target servers. Only need SSH + Python (2.7+ or 3.5+).
  • Inventory: Defines what nodes exist and how to group them.
  • Modules: Units of work (copy files, install packages, restart services). Each executes on the managed node and returns JSON.
  • Playbooks: YAML files defining what plays to run against which hosts.
  • Roles: Reusable, structured collections of tasks, variables, handlers, and templates.

Execution flow:

1. Ansible reads playbook and inventory

2. Connects to target hosts via SSH

3. Copies the module (Python script) to the target

4. Executes the module

5. Collects result (JSON), reports pass/fail/changed

6. Cleans up the temporary module file


2. What is idempotency in Ansible and why does it matter?

Why they ask this: Idempotency is the core design principle of Ansible. Not understanding it signals shallow experience.

Ideal answer:

An operation is idempotent if running it multiple times produces the same result as running it once — with no unintended side effects on subsequent runs.

In Ansible: Most built-in modules are designed to be idempotent. If the desired state already exists, the module does nothing and reports ok. If it needs to change something, it reports changed.

Example:

- name: Ensure nginx is installed
  apt:
    name: nginx
    state: present

Running this 10 times: First run installs nginx (changed). Subsequent runs detect nginx is already installed and do nothing (ok).

Why it matters:

  • Safe to re-run playbooks after failures without causing double-installs or config corruption
  • Enables declarative infrastructure management — describe the desired state, not the steps
  • Makes playbooks safe to schedule and run periodically

Non-idempotent pitfall: Using the shell or command module without creates or removes guards, or using lineinfile incorrectly, can cause duplicate entries on re-runs.


3. What is the difference between a playbook, a play, and a task?

Ideal answer:

Playbook: The top-level YAML file. Contains one or more plays.

Play: Maps a group of hosts to a list of tasks. Each play targets specific hosts and defines variables, roles, and tasks for those hosts.

Task: A single unit of work that calls one Ansible module with specific parameters.

# playbook.yml
---
- name: Configure web servers          # <-- PLAY
  hosts: webservers
  become: true
  tasks:
  - name: Install nginx                # <-- TASK
    apt:
      name: nginx
      state: present
  - name: Start nginx                  # <-- TASK
    service:
      name: nginx
      state: started
      enabled: true

- name: Configure databases            # <-- PLAY 2
  hosts: dbservers
  roles:
  - role: postgresql

Role: A reusable collection of tasks, templates, files, variables, and handlers organized into a standard directory structure. Referenced in plays via the roles: key.


4. How does Ansible inventory work?

Ideal answer:

Inventory defines the managed hosts — what they're called, how to reach them, and how they're grouped.

Static inventory (INI format):

[webservers]
web1.example.com
web2.example.com ansible_user=ubuntu ansible_port=2222

[dbservers]
db1.example.com
db2.example.com

[production:children]
webservers
dbservers

[all:vars]
ansible_python_interpreter=/usr/bin/python3

YAML format (preferred):

all:
  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
    dbservers:
      hosts:
        db1.example.com:

Dynamic inventory: A script or plugin that generates inventory on the fly from an external source (AWS EC2, GCP, Azure, Kubernetes). Ansible calls the script/plugin at runtime and receives JSON. Common plugins: amazon.aws.ec2, azure.azcollection.azure_rm.

Host variables: Defined inline (as above), in host_vars/.yml, or in group_vars files.


5. Explain Ansible variable precedence

Why they ask this: Variable precedence bugs are common in large playbooks. Knowing the order separates senior from junior.

Ideal answer:

Ansible has 22 levels of variable precedence. The most important (lowest to highest):

1. Role defaults (defaults/main.yml) — lowest, easily overridable

2. Inventory vars

3. Group vars (group_vars/all, then specific groups)

4. Host vars

5. Play vars

6. Role vars (vars/main.yml)

7. Task vars (vars: in a task)

8. set_fact / register

9. --extra-vars (-e) — highest, always wins

Practical rule:

  • Put defaults in defaults/main.yml — these are meant to be overridden by operators
  • Put non-overridable role config in vars/main.yml
  • Use --extra-vars for deployment-time values (version numbers, feature flags)

Common gotcha: group_vars/all has lower precedence than group_vars/webservers. So a more specific group's variables override the all group.


Section 2: Playbooks, Roles & Modules

6. What is an Ansible role and how do you structure one?

Ideal answer:

A role is a reusable, self-contained unit of automation with a standard directory structure:

roles/
└── nginx/
    ├── defaults/
    │   └── main.yml      # Default variables (low precedence, overridable)
    ├── vars/
    │   └── main.yml      # Role variables (high precedence)
    ├── tasks/
    │   └── main.yml      # Main task list
    ├── handlers/
    │   └── main.yml      # Handlers (triggered by notify)
    ├── templates/
    │   └── nginx.conf.j2 # Jinja2 templates
    ├── files/
    │   └── ssl.crt        # Static files
    ├── meta/
    │   └── main.yml      # Role metadata and dependencies
    └── README.md

Use a role when: The same set of tasks is needed across multiple playbooks or projects. Roles are shareable, testable units.

Galaxy: Ansible Galaxy is the public role repository. ansible-galaxy install geerlingguy.nginx downloads a community role. Collections are the modern packaging format (roles + modules + plugins).


7. What are Ansible handlers and when do you use them?

Ideal answer:

Handlers are tasks that run only when notified by another task — and only once at the end of the play, regardless of how many tasks notify them.

Use case: Restart a service when its configuration changes — but only once, even if multiple config files changed.

tasks:
- name: Update nginx config
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Restart nginx         # notify the handler

- name: Update nginx ssl cert
  copy:
    src: ssl.crt
    dest: /etc/nginx/ssl.crt
  notify: Restart nginx         # same handler, still runs only once

handlers:
- name: Restart nginx
  service:
    name: nginx
    state: restarted

Key behavior: Handlers only run if the notifying task reports changed. If nginx.conf didn't change (already correct), the handler won't run.

Force handler run: Use meta: flush_handlers to run pending handlers immediately rather than waiting for end of play.


8. When do you use the shell module vs command module?

Ideal answer:

command module: Runs a command without going through a shell. Safer — no shell expansion, no pipes, no redirects. Use for simple executable calls.

- command: /usr/bin/myapp --version

shell module: Runs command through /bin/sh. Supports pipes, redirects, shell variables, glob expansion. Needed for shell-specific operations.

- shell: ps aux | grep nginx | grep -v grep | wc -l
  register: nginx_process_count

When to use each:

  • Prefer command — safer, no injection risk
  • Use shell only when you need shell features
  • Consider purpose-built modules first: apt, systemd, copy, template are idempotent, shell/command are not

Making shell/command idempotent: Use creates (skip if file exists) or removes (skip if file doesn't exist) arguments, or wrap in a when condition.


9. What is Ansible Vault and how do you use it?

Ideal answer:

Ansible Vault encrypts sensitive data (passwords, API keys, certificates) in YAML files using AES-256 encryption.

Encrypt a file:

ansible-vault encrypt secrets.yml
ansible-vault edit secrets.yml
ansible-vault view secrets.yml

Encrypt a string (embed in variable files):

ansible-vault encrypt_string 'mysecretpassword' --name 'db_password'

Output embeds the encrypted value directly in your vars file.

Running playbooks with vault:

ansible-playbook site.yml --vault-password-file ~/.vault_pass
# or interactively:
ansible-playbook site.yml --ask-vault-pass

Multiple vault IDs: Ansible supports multiple vault passwords — useful when different teams own different secret files.

CI/CD integration: Store the vault password in your CI secrets, pass via --vault-password-file or ANSIBLE_VAULT_PASSWORD_FILE env variable.

Alternative for 2026: Consider HashiCorp Vault or cloud secret managers (AWS Secrets Manager, Azure Key Vault) with the community.hashi_vault collection for more sophisticated secret rotation and auditing.


10. How do you handle errors and failures in Ansible playbooks?

Ideal answer:

ignore_errors: true: Task failure doesn't stop the play. Use sparingly — can hide real failures.

failed_when: Define custom failure conditions.

- command: myapp --check
  register: result
  failed_when: result.rc != 0 and "already configured" not in result.stdout

block / rescue / always: Ansible's try-catch equivalent.

- block:
    - name: Risky task
      command: /usr/bin/risky
  rescue:
    - name: Handle failure
      debug:
        msg: "Risky task failed, running recovery"
  always:
    - name: Always runs
      debug:
        msg: "Cleanup"

any_errors_fatal: true: Stop all hosts immediately if any host fails — useful when partial deployments are worse than no deployment.

max_fail_percentage: Allow a percentage of hosts to fail before aborting the play. Useful for rolling deployments.


Section 3: Advanced Ansible

11. How do you write a dynamic inventory for AWS EC2?

Ideal answer:

The amazon.aws.ec2 inventory plugin connects to AWS and dynamically builds inventory from EC2 instances.

Install collection: ansible-galaxy collection install amazon.aws

Inventory config file (aws_ec2.yml):

plugin: amazon.aws.ec2
regions:
  - us-east-1
  - us-west-2
filters:
  instance-state-name: running
  tag:Environment: production
keyed_groups:
  - key: tags.Role
    prefix: role
  - key: placement.availability_zone
hostnames:
  - private-ip-address

Usage:

ansible-inventory -i aws_ec2.yml --list
ansible-playbook -i aws_ec2.yml site.yml

Result: Instances with tag Role=webserver are automatically grouped under role_webserver. No manual inventory maintenance needed.


12. How do you use Ansible with containers and Kubernetes?

Ideal answer:

Ansible can manage both Docker containers and Kubernetes resources.

Docker (community.docker collection):

- name: Run application container
  community.docker.docker_container:
    name: myapp
    image: myapp:1.2.3
    state: started
    ports:
    - "8080:8080"
    env:
      DATABASE_URL: "{{ db_url }}"

Kubernetes (kubernetes.core collection):

- name: Apply Kubernetes deployment
  kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: myapp
        namespace: production

When Ansible vs Helm for K8s: Ansible is useful for orchestrating multi-step workflows that combine Kubernetes operations with external systems (DNS, load balancer config, database provisioning). For pure Kubernetes application deployment, Helm or Kustomize is more appropriate.


13. What is AWX / Ansible Automation Platform?

Ideal answer:

AWX is the open-source upstream project for Ansible Automation Platform (Red Hat's commercial offering). It provides a web UI, REST API, and execution management layer for Ansible.

What it adds over raw Ansible:

  • Web UI: Run playbooks, manage inventory, view job history without CLI
  • Role-based access control: Team-level permissions for running specific playbooks against specific inventories
  • Credential management: Centralized, encrypted credential storage (SSH keys, cloud credentials, Vault passwords)
  • Job scheduling: Run playbooks on a schedule (like cron, but with history and notifications)
  • Notifications: Slack/email alerts on job success/failure
  • REST API: Trigger playbook runs from CI/CD pipelines or external systems
  • Workflows: Chain multiple playbooks into complex workflows with conditional branching

When to use AWX: When multiple teams need to run Ansible automation, when you need audit trails, or when you need to give non-engineers a safe interface to trigger specific automation.


14. How do you use `delegate_to` and `run_once` in Ansible?

Ideal answer:

delegate_to: Run a task on a different host than the current play host. Useful for tasks that need to run on a specific machine (database host, load balancer) during a playbook targeting other hosts.

- name: Remove host from load balancer
  command: /usr/bin/haproxy-remove {{ inventory_hostname }}
  delegate_to: loadbalancer.example.com

- name: Update application
  apt:
    name: myapp
    state: latest

- name: Add host back to load balancer
  command: /usr/bin/haproxy-add {{ inventory_hostname }}
  delegate_to: loadbalancer.example.com

run_once: true: Run a task only once, on the first host in the group, even though the play targets multiple hosts. Use for tasks that should happen once per cluster (e.g., database schema migration during a rolling deploy).

- name: Run database migration (once per deployment)
  command: rails db:migrate
  run_once: true
  delegate_to: "{{ groups['appservers'][0] }}"

15. How do you test Ansible roles with Molecule?

Ideal answer:

Molecule is the standard testing framework for Ansible roles. It provisions ephemeral instances, runs the role, verifies the result, and tears down.

Initialize a role with Molecule:

molecule init role my-role --driver-name docker

Molecule directory structure:

molecule/
└── default/
    ├── molecule.yml   # Driver config (Docker, Vagrant, EC2...)
    ├── converge.yml   # Playbook that applies the role
    ├── verify.yml     # Verification playbook (or use Testinfra/Goss)
    └── prepare.yml    # Optional pre-role setup

Test lifecycle:

molecule create     # Spin up test instances
molecule converge   # Apply the role
molecule verify     # Run verifications
molecule destroy    # Tear down
molecule test       # All steps in sequence (CI-friendly)

Verification options: Use Ansible tasks in verify.yml (simple), or testinfra (Python-based, more expressive for integration testing).


16. Ansible vs Terraform vs Chef/Puppet: when do you use each?

Ideal answer:

Terraform: Infrastructure provisioning. Creates/destroys cloud resources (VMs, networks, databases, DNS). Declarative, stateful (tracks what it created). Not designed for configuration management inside servers.

Ansible: Configuration management and application deployment. Connects to existing servers and configures them. Agentless, procedural (runs tasks in order), stateless (no central state file). Also good for cloud resource management but less powerful than Terraform for complex IaC.

Chef/Puppet: Agent-based configuration management. Agents run on managed nodes on a schedule, pull config from a central server, enforce desired state continuously. Better for large-scale drift correction but higher operational overhead.

How they complement each other:

  • Terraform provisions the server → Ansible configures it (most common)
  • Terraform manages infrastructure → Kubernetes + Helm manages applications
  • Chef/Puppet for large fleets with strict compliance requirements

2026 reality: Terraform + Ansible is the dominant pairing for most DevOps teams. Chef/Puppet usage has declined significantly except in large enterprise environments.


17. How do you optimize Ansible playbook performance?

Ideal answer:

forks: By default, Ansible runs against 5 hosts in parallel. Increase with -f 20 or forks = 20 in ansible.cfg. Set based on control node capacity.

pipelining: Reduces SSH connections by sending multiple module operations over a single connection.

# ansible.cfg
[connection]
pipelining = True

Requires requiretty disabled in sudoers on managed nodes.

gather_facts: false: Fact gathering (the setup module) adds ~1-2 seconds per host. Disable when facts aren't needed.

--limit: Target only specific hosts or groups rather than running against all.

Strategy plugins:

  • linear (default): All hosts run task N before any host runs task N+1
  • free: Each host runs through the playbook as fast as possible, independently
  • mitogen: Drop-in strategy plugin that dramatically speeds up Ansible (3-7x) by replacing the SSH transport with a Python-based connection framework

Caching: Enable fact caching (Redis or JSON file) so facts don't need to be gathered every run.


18. How do you use Ansible tags?

Ideal answer:

Tags let you selectively run or skip parts of a playbook without maintaining multiple playbooks.

Applying tags:

- name: Install packages
  apt:
    name: "{{ item }}"
  loop: "{{ packages }}"
  tags: [install, packages]

- name: Configure application
  template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  tags: [configure, app]

Running with tags:

# Run only tasks tagged with 'configure'
ansible-playbook site.yml --tags configure

# Skip tasks tagged with 'install'
ansible-playbook site.yml --skip-tags install

# Run everything
ansible-playbook site.yml

Special tags: always (always runs even when other tags are specified), never (only runs when explicitly requested).

CI/CD use: Tag deployment tasks separately from installation tasks. During routine deploys, skip install to only run configure and deploy tasks — much faster.


19. How do you integrate Ansible into a CI/CD pipeline?

Ideal answer:

GitHub Actions example:

- name: Deploy with Ansible
  runs-on: ubuntu-latest
  steps:
  - uses: actions/checkout@v4
  - name: Install Ansible
    run: pip install ansible boto3
  - name: Configure SSH
    run: |
      mkdir -p ~/.ssh
      echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa
      chmod 600 ~/.ssh/id_rsa
  - name: Run playbook
    run: |
      ansible-playbook -i aws_ec2.yml site.yml         --extra-vars "version=${{ github.sha }}"         --vault-password-file <(echo "${{ secrets.VAULT_PASS }}")

AWX REST API trigger (from any CI):

curl -X POST https://awx.example.com/api/v2/job_templates/42/launch/   -H "Authorization: Bearer $AWX_TOKEN"   -d '{"extra_vars": {"version": "'"$GIT_SHA"'"}}'

Best practices:

  • Store vault password in CI secrets, never in the repository
  • Use dynamic inventory — never hardcode IPs
  • Run ansible-lint in CI before execution
  • Use --check (dry-run) + --diff on PR review to show what would change

20. Walk me through debugging a failing Ansible playbook

Why they ask this: Real-world troubleshooting skill. They want systematic thinking, not guesswork.

Step-by-step:

Step 1: Increase verbosity

ansible-playbook site.yml -v     # basic output
ansible-playbook site.yml -vvvv  # max verbosity (SSH debug, raw output)

Step 2: Check the error message carefully

FAILED! => {"msg": "..."} output tells you the module, the host, and the error. Read it fully before guessing.

Step 3: Test connectivity

ansible all -i inventory -m ping
ansible webservers -m setup  # gather facts to test connectivity + Python

Step 4: Run on a single host

ansible-playbook site.yml --limit web1.example.com

Step 5: Use --check + --diff

Dry run showing what would change — often reveals the issue without making changes.

Step 6: Debug with debug module

- debug:
    var: my_variable
- debug:
    msg: "{{ some_complex_expression }}"

Step 7: Check ansible-lint

ansible-lint site.yml

Catches common issues: deprecated modules, unsafe practices, YAML errors.

Common root causes: SSH key not trusted, Python not found on managed node, privilege escalation config, missing variable, module not available (collection not installed).

Reading helps. Practicing wins interviews.

Practice these exact questions with an AI interviewer that pushes back. First session completely free.

Start Practicing Free →