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
| Parameter | Required | Default | Description |
|---|---|---|---|
command | Yes | — | The command to execute |
responses | Yes | — | Dict of prompt regex → response mappings |
timeout | No | 30 | Seconds to wait for each prompt |
echo | No | false | Whether to echo the command output |
chdir | No | — | Directory to run the command in |
creates | No | — | Skip if this file exists |
removes | No | — | 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
| Scenario | Best Module |
|---|---|
| Package installation | apt, yum, dnf |
| File transfers | copy, fetch, synchronize |
| Password changes | user module with password parameter |
| Network devices | ansible.netcommon collection |
| Database setup | community.mysql, community.postgresql |
| Everything else with prompts | expect |
Use expect as a last resort when no dedicated module exists for your use case.
Related Articles
- Ansible Shell vs Command Module
- Ansible Error Handling Guide
- Securing Ansible: Managing Sudo Passwords
- Ansible no_log: Hide Sensitive Output
- Ansible Vault Guide
- Ansible Best Practices Guide
- Ansible Debug Module Guide
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.