Introduction

Some commands require interactive user input — password prompts, confirmation dialogs, setup wizards. Ansible's expect module automates these interactions by sending predefined responses to expected prompts, eliminating the need for manual intervention during playbook execution.

This guide covers everything from basic setup to advanced patterns including multiple prompts, timeout handling, and security considerations.

Prerequisites

The expect module requires the pexpect Python library (version 3.3+) on the control node:

pip install pexpect

Or install it as part of your playbook:

- name: Ensure pexpect is installed
  ansible.builtin.pip:
    name: pexpect
    state: present
    version: ">=3.3"

Verify the installation:

python3 -c "import pexpect; print(pexpect.__version__)"

Basic Usage

The expect module takes a command to run and a dictionary of responses mapping expected prompts (as regex patterns) to the text to send:

- name: Change user password interactively
  ansible.builtin.expect:
    command: passwd johndoe
    responses:
      "New password:": "SecureP@ss123"
      "Retype new password:": "SecureP@ss123"

Module Parameters

ParameterRequiredDefaultDescription
commandYes—The command to execute
responsesYes—Dict of prompt regex → response mappings
timeoutNo30Seconds to wait for each prompt
echoNofalseWhether to echo the command output
chdirNo—Directory to run the command in
createsNo—Skip if this file exists
removesNo—Skip unless this file exists

Practical Examples

Automating SSH Key Generation

- name: Generate SSH key pair non-interactively
  ansible.builtin.expect:
    command: ssh-keygen -t ed25519 -C "ansible@example.com"
    responses:
      "Enter file in which to save the key": "/home/ansible/.ssh/id_ed25519"
      "Enter passphrase": ""
      "Enter same passphrase again": ""
    creates: /home/ansible/.ssh/id_ed25519

Database Setup Wizard

- name: Run MySQL secure installation
  ansible.builtin.expect:
    command: mysql_secure_installation
    responses:
      "Enter password for user root:": "{{ mysql_root_password }}"
      "Press y\\|Y for Yes, any other key for No": "y"
      "New password:": "{{ mysql_new_root_password }}"
      "Re-enter new password:": "{{ mysql_new_root_password }}"
      "Remove anonymous users\\?": "y"
      "Disallow root login remotely\\?": "y"
      "Remove test database and access to it\\?": "y"
      "Reload privilege tables now\\?": "y"
    timeout: 60

Interactive Package Installation

- name: Accept EULA during package install
  ansible.builtin.expect:
    command: /opt/software/install.sh
    responses:
      "Do you accept the license agreement\\? \\(yes/no\\)": "yes"
      "Installation directory \\[/opt/software\\]:": "/opt/myapp"
      "Enter license key:": "{{ license_key }}"
      "Proceed with installation\\? \\(Y/n\\)": "Y"
    timeout: 120
    chdir: /opt/software

Multiple Responses to the Same Prompt

When a prompt appears multiple times, provide a list of responses:

- name: Handle repeated prompts
  ansible.builtin.expect:
    command: /opt/setup-wizard.sh
    responses:
      "Enter value:":
        - "first_value"
        - "second_value"
        - "third_value"

Each occurrence of "Enter value:" receives the next response from the list.

Network Device Configuration

- name: Configure Cisco router
  ansible.builtin.expect:
    command: "ssh admin@{{ inventory_hostname }}"
    responses:
      "Password:": "{{ network_password }}"
      "Router>": "enable"
      "Password:": "{{ enable_password }}"
      "Router#": |
        configure terminal
        interface GigabitEthernet0/1
        ip address 192.168.1.1 255.255.255.0
        no shutdown
        end
        write memory
    timeout: 30
  delegate_to: localhost

Note: For network automation, consider using ansible.netcommon collection modules instead of expect — they provide better error handling and idempotency.

Timeout Handling

The default timeout is 30 seconds per prompt. For slow operations, increase it:

- name: Long-running interactive process
  ansible.builtin.expect:
    command: /opt/backup/full-backup.sh
    responses:
      "Confirm full backup \\(yes/no\\):": "yes"
      "Enter encryption password:": "{{ backup_password }}"
    timeout: 600  # 10 minutes for large backups

If a prompt isn't received within the timeout, the task fails with a timeout error. Handle this with Ansible's ignore_errors or failed_when:

- name: Attempt interactive setup with timeout handling
  ansible.builtin.expect:
    command: /opt/app/setup.sh
    responses:
      "Continue\\?": "yes"
    timeout: 60
  register: setup_result
  failed_when:
    - setup_result.rc != 0
    - "'timeout' not in setup_result.msg | default('')"

Regex Patterns in Prompts

The responses keys are Python regular expressions. Use regex features for flexible matching:

responses:
  # Case-insensitive match
  "(?i)password:": "{{ my_password }}"
  
  # Match variations
  "(Y/n|y/N|yes/no)": "yes"
  
  # Match prompt with variable content
  "Enter password for user \\w+:": "{{ db_password }}"
  
  # Escape special characters
  "\\[sudo\\] password for \\w+:": "{{ sudo_password }}"

Security Best Practices

Use ansible-vault for Passwords

Never hardcode passwords in playbooks:

# vars/vault.yml (encrypted with ansible-vault)
mysql_root_password: "encrypted_value_here"
- name: Secure interactive setup
  ansible.builtin.expect:
    command: mysql_secure_installation
    responses:
      "Enter password:": "{{ mysql_root_password }}"
  no_log: true  # Prevents password from appearing in logs

Always Use no_log

The expect module can expose sensitive data in Ansible's output. Always add no_log: true when dealing with passwords:

- name: Change password securely
  ansible.builtin.expect:
    command: passwd {{ username }}
    responses:
      "New password:": "{{ user_password }}"
      "Retype new password:": "{{ user_password }}"
  no_log: true
  changed_when: true

Prefer Non-Interactive Alternatives

Before using expect, check if the command supports non-interactive flags:

# Instead of expect with ssh-keygen prompts:
- name: Generate SSH key non-interactively (no expect needed)
  ansible.builtin.command:
    cmd: ssh-keygen -t ed25519 -f /home/user/.ssh/id_ed25519 -N ""
    creates: /home/user/.ssh/id_ed25519

# Instead of expect with apt:
- name: Accept EULA non-interactively
  ansible.builtin.debconf:
    name: ttf-mscorefonts-installer
    question: msttcorefonts/accepted-mscorefonts-eula
    value: "true"
    vtype: boolean

Common Errors and Solutions

"The pexpect python module is required"

fatal: [host]: FAILED! => {"msg": "The pexpect python module is required"}

Fix: Install pexpect on the control node: pip install pexpect>=3.3

Prompt Not Matched

If a prompt regex doesn't match, the task times out. Debug by running with increased verbosity:

ansible-playbook playbook.yml -vvv

Check for:

  • Extra whitespace in prompts
  • ANSI color codes in terminal output
  • Case sensitivity mismatches

Command Exits Before All Prompts

If the command exits early, expect may hang waiting for the next prompt. Use timeout to limit the wait and check the return code:

- name: Handle early exit
  ansible.builtin.expect:
    command: /opt/app/configure.sh
    responses:
      "Continue\\?": "yes"
      "Password:": "{{ app_password }}"
    timeout: 30
  register: result
  failed_when: result.rc not in [0, 1]

When to Use expect vs Other Modules

ScenarioBest Module
Package installationapt, yum, dnf
File transferscopy, fetch, synchronize
Password changesuser module with password parameter
Network devicesansible.netcommon collection
Database setupcommunity.mysql, community.postgresql
Everything else with promptsexpect

Use expect as a last resort when no dedicated module exists for your use case.

Conclusion

The expect module fills an important gap in Ansible's automation capabilities — handling interactive CLI prompts that other modules can't manage. The key principles are: install pexpect first, use regex patterns for flexible prompt matching, always protect sensitive data with no_log: true and ansible-vault, and prefer dedicated modules when they exist. For the edge cases where no other module works, expect is an invaluable tool.