Ansible Check if File Exists: stat Module with when Conditional (Guide)
By Luca Berton · Published 2024-01-01 · Category: installation
How to check if a file exists in Ansible with the stat module and when conditional. Verify file existence, check directories, test before actions.

How to check if a file exists in Ansible?
I'm going to show you a live Playbook and some simple Ansible code. I'm Luca Berton and welcome to today's episode of Ansible PilotSee also: Ansible Check If Directory Exists: stat Module Guide
Ansible check file exists
Today we're talking about the Ansible modulestat.
The full name is ansible.builtin.stat, which means that is part of the collection of modules "builtin" with ansible and shipped with it.
It's a module pretty stable and out for years.
It works in a different variety of operating systems.
It retrieves a file entry or a file system status.
For Windows target use the ansible.windows.win_stat module instead.
Mandatory Parameters
• path stringSee also: Ansible win_stat: Check if File or Directory Exists on Windows (Examples)
Main Return Values
• stat complex - existsThe only mandatory parameter is "path" which is the filesystem full path of the object to check. The module returns a complex object, the property that is interesting for us is "exists". This attribute is "true" if the object exists.
## Playbook Let's jump in a real-life playbook to check if a file exists with Ansible.
code
• file_exist.yml---
- name: check if a file exist
hosts: all
become: false
vars:
myfile: /home/devops/test.txt
tasks:
- name: check if a file exists
ansible.builtin.stat:
path: "{{ myfile }}"
register: file_data
- name: report file exists
ansible.builtin.debug:
msg: "The file {{ myfile }} exist"
when: file_data.stat.exists
- name: report file not exists
ansible.builtin.debug:
msg: "The file {{ myfile }} doesn't exist"
when: not file_data.stat.exists
Conclusion
Now you know how to check if a file exists with Ansible.See also: Ansible Set File Permissions 755: chmod with file Module Guide
Advanced File Checks
Check file and take action
- name: Check if config exists
ansible.builtin.stat:
path: /etc/myapp/config.yml
register: config_file
- name: Copy default config if missing
ansible.builtin.copy:
src: files/default-config.yml
dest: /etc/myapp/config.yml
mode: '0644'
when: not config_file.stat.exists
- name: Read existing config
ansible.builtin.slurp:
src: /etc/myapp/config.yml
register: config_content
when: config_file.stat.exists
Verify file checksum (integrity check)
- name: Get file checksum
ansible.builtin.stat:
path: /opt/myapp/binary
checksum_algorithm: sha256
register: binary_info
- name: Fail if binary is corrupted
ansible.builtin.fail:
msg: "Binary checksum mismatch! Expected {{ expected_checksum }}"
when:
- binary_info.stat.exists
- binary_info.stat.checksum != expected_checksum
Check file age
- name: Get file info
ansible.builtin.stat:
path: /var/log/myapp/app.log
register: log_file
- name: Rotate if file is larger than 100MB
ansible.builtin.command: logrotate -f /etc/logrotate.d/myapp
when:
- log_file.stat.exists
- log_file.stat.size > 104857600
become: true
Check multiple files exist
- name: Check required files
ansible.builtin.stat:
path: "{{ item }}"
register: required_files
loop:
- /etc/ssl/certs/myapp.crt
- /etc/ssl/private/myapp.key
- /etc/myapp/config.yml
- name: Fail if any required file is missing
ansible.builtin.fail:
msg: "Missing required file: {{ item.item }}"
loop: "{{ required_files.results }}"
when: not item.stat.exists
Check if file is a symlink
- name: Check path type
ansible.builtin.stat:
path: /usr/bin/python
register: python_path
- name: Show Python path details
ansible.builtin.debug:
msg: >
Path: /usr/bin/python
Exists: {{ python_path.stat.exists }}
Is symlink: {{ python_path.stat.islnk | default(false) }}
Links to: {{ python_path.stat.lnk_source | default('N/A') }}
stat Module vs Other Approaches
| Approach | Use Case |
|----------|----------|
| stat + when | Check before acting (most flexible) |
| copy with force: false | Copy file only if dest doesn't exist |
| file with state: file | Fails if file doesn't exist |
| lineinfile with create: true | Create file if missing, add line |
| template | Always overwrites with template output |
FAQ
How do I check if a file exists on Windows?
- name: Check file on Windows
ansible.windows.win_stat:
path: C:\Program Files\MyApp\config.ini
register: win_file
- name: File status
ansible.builtin.debug:
msg: "File exists: {{ win_file.stat.exists }}"
Why does stat fail with "Permission denied"?
The Ansible connection user needs read access to the parent directory. Use become: true to check files in restricted directories.
How do I get the file's content along with stat?
stat doesn't read content. Use slurp (for small files) or fetch (to download):
- name: Read file content
ansible.builtin.slurp:
src: /etc/myapp/config.yml
register: file_content
when: config_file.stat.exists
- name: Decode content
ansible.builtin.debug:
msg: "{{ file_content.content | b64decode }}"
Check File Exists
- ansible.builtin.stat:
path: /etc/myapp/config.yml
register: config_file
- name: Create config only if missing
ansible.builtin.template:
src: config.yml.j2
dest: /etc/myapp/config.yml
when: not config_file.stat.exists
become: true
Check Multiple Files
- stat: { path: "{{ item }}" }
loop:
- /etc/nginx/nginx.conf
- /etc/myapp/config.yml
- /opt/myapp/app.jar
register: file_checks
- debug:
msg: "{{ item.item }} exists: {{ item.stat.exists }}"
loop: "{{ file_checks.results }}"
File Type Checks
- stat: { path: /opt/myapp }
register: path
- debug:
msg:
- "Is file: {{ path.stat.isreg | default(false) }}"
- "Is directory: {{ path.stat.isdir | default(false) }}"
- "Is symlink: {{ path.stat.islnk | default(false) }}"
- "Is socket: {{ path.stat.issock | default(false) }}"
File Properties
- stat: { path: /opt/myapp/deploy.sh }
register: script
- debug:
msg:
- "Size: {{ script.stat.size }} bytes"
- "Mode: {{ script.stat.mode }}"
- "Owner: {{ script.stat.pw_name }}"
- "Group: {{ script.stat.gr_name }}"
- "Modified: {{ script.stat.mtime }}"
when: script.stat.exists
Checksum Verification
- stat:
path: /opt/myapp/app.jar
checksum_algorithm: sha256
register: app_file
- debug:
msg: "SHA256: {{ app_file.stat.checksum }}"
when: app_file.stat.exists
# Compare checksums
- fail:
msg: "File integrity check failed!"
when:
- app_file.stat.exists
- app_file.stat.checksum != expected_checksum
Deployment Patterns
Skip if already deployed
- stat: { path: /opt/myapp-{{ version }}/app.jar }
register: deployed
- block:
- get_url:
url: "https://releases.example.com/app-{{ version }}.jar"
dest: /opt/myapp-{{ version }}/app.jar
- service: name=myapp state=restarted
when: not deployed.stat.exists
become: true
Backup before overwrite
- stat: { path: /etc/nginx/nginx.conf }
register: nginx_conf
- copy:
src: /etc/nginx/nginx.conf
dest: "/etc/nginx/nginx.conf.bak.{{ ansible_date_time.epoch }}"
remote_src: true
when: nginx_conf.stat.exists
become: true
File age check
- stat: { path: /tmp/cache.json }
register: cache
- set_fact:
cache_expired: "{{ (ansible_date_time.epoch | int) - (cache.stat.mtime | int) > 3600 }}"
when: cache.stat.exists
- command: /opt/refresh-cache.sh
when: not cache.stat.exists or cache_expired | default(true)
Windows Equivalent
- ansible.windows.win_stat:
path: C:\Program Files\MyApp\config.xml
register: win_file
- debug: msg="Config exists"
when: win_file.stat.exists
FAQ
Does stat fail if file doesn't exist?
No — stat always succeeds. Check result.stat.exists for existence.
stat vs find?
stat checks a single known path. find searches for files matching patterns, size, or age criteria.
How do I check a remote file from the controller?
stat runs on the remote host. For controller files, use delegate_to: localhost or lookup('file').
Check File Exists
- ansible.builtin.stat:
path: /etc/myapp/config.yml
register: config_file
- debug:
msg: "Config {{ 'exists' if config_file.stat.exists else 'missing' }}"
Conditional Task
- stat:
path: /opt/myapp/.installed
register: installed
- name: Run installer only if not already installed
command: /opt/myapp/install.sh
when: not installed.stat.exists
become: true
Check Multiple Files
- stat:
path: "{{ item }}"
loop:
- /etc/myapp/config.yml
- /opt/myapp/app.jar
- /var/log/myapp
register: file_checks
- debug:
msg: "MISSING: {{ item.item }}"
loop: "{{ file_checks.results }}"
when: not item.stat.exists
Check File Properties
- stat:
path: /opt/scripts/deploy.sh
register: script
- debug:
msg: |
Size: {{ script.stat.size }} bytes
Mode: {{ script.stat.mode }}
Owner: {{ script.stat.pw_name }}
Modified: {{ script.stat.mtime }}
Is executable: {{ script.stat.executable }}
when: script.stat.exists
Create If Missing
- stat:
path: /etc/myapp/config.yml
register: config
- template:
src: default-config.yml.j2
dest: /etc/myapp/config.yml
when: not config.stat.exists
become: true
Check If File Is Empty
- stat:
path: /var/log/myapp/error.log
register: error_log
- debug:
msg: "Error log has content!"
when: error_log.stat.exists and error_log.stat.size > 0
Check Symlink
- stat:
path: /opt/myapp/current
register: current_link
- debug:
msg: "Points to: {{ current_link.stat.lnk_source }}"
when: current_link.stat.exists and current_link.stat.islnk
Verify Checksum
- stat:
path: /opt/myapp/app.jar
checksum_algorithm: sha256
register: current_file
- debug:
msg: "File needs updating"
when: not current_file.stat.exists or current_file.stat.checksum != expected_checksum
FAQ
stat vs creates/removes?
creates/removes only work with command/shell modules. stat works everywhere and returns full metadata.
Why check before creating?
For idempotency when using command/shell. Modules like file, copy, and template are already idempotent — they check internally.
How to check on the controller (local)?
- stat: { path: /local/file }
delegate_to: localhost
register: local_file
Related Articles
• Ansible conditional patterns • the Ansible become reference • the Ansible roles overview • Windows DSC and AnsibleCategory: installation
Watch the video: Ansible Check if File Exists: stat Module with when Conditional (Guide) — Video Tutorial