Ansible Check If Directory Exists: stat Module Guide
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
How to check if files and directories exist with Ansible stat module. Use stat results in conditionals, check permissions, size, and timestamps.

How to check if a directory 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 Pilot.See also: Ansible Create Directory: file Module with state=directory (Guide)
Ansible check directory exists
Today we’re talking about the Ansible module stat. 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 theansible.windows.win_stat module instead.
Main parameters and return values
•path _string_
The only mandatory parameter is “path” which is the filesystem full path of the object to check.
• stat _complex_
The module returns a complex object, the property that is interesting for us is “isdir”. This attribute is “true” if the object is a directory.
See also: Ansible Create Symlink: file Module with state=link (Guide)
Demo
Let’s jump in a real-life playbook to check if a directory exists with Ansible.code
• directory_check_exists.yml---
- name: check if the directory exists
hosts: all
become: false
vars:
directory: "/tmp"
tasks:
- name: Check if the directory exists
ansible.builtin.stat:
path: "{{ directory }}"
register: dir_to_check
- name: Directory found
ansible.builtin.debug:
msg: "Directory {{ directory }} present"
when: dir_to_check.stat.isdir is defined and dir_to_check.stat.isdir
Conclusion
Now you know better the Ansible module stat and you could use it successfully in your playbook.See also: Ansible Create File with Content: copy Module content Parameter
How It Works
The ansible.builtin.stat module retrieves file or directory metadata without modifying anything. Combined with register and when, you can create conditional logic based on filesystem state.
Complete Examples
Check directory and create if missing
---
- name: Ensure directory exists
hosts: all
tasks:
- name: Check if /opt/myapp exists
ansible.builtin.stat:
path: /opt/myapp
register: myapp_dir
- name: Create directory if not present
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: '0755'
become: true
when: not myapp_dir.stat.exists
- name: Show directory info
ansible.builtin.debug:
msg: "Directory exists: {{ myapp_dir.stat.exists }}, isdir: {{ myapp_dir.stat.isdir | default(false) }}"
Check multiple directories
- name: Verify required directories exist
ansible.builtin.stat:
path: "{{ item }}"
register: dir_checks
loop:
- /opt/myapp
- /var/log/myapp
- /etc/myapp
- name: Report missing directories
ansible.builtin.debug:
msg: "MISSING: {{ item.item }}"
loop: "{{ dir_checks.results }}"
when: not item.stat.exists
Check if path is a directory (not a file)
- name: Check path type
ansible.builtin.stat:
path: /opt/myapp
register: path_info
- name: Fail if path exists but is not a directory
ansible.builtin.fail:
msg: "/opt/myapp exists but is not a directory!"
when: path_info.stat.exists and not path_info.stat.isdir
Useful stat Properties
| Property | Type | Description |
|----------|------|-------------|
| stat.exists | bool | Path exists |
| stat.isdir | bool | Is a directory |
| stat.isreg | bool | Is a regular file |
| stat.islnk | bool | Is a symlink |
| stat.size | int | Size in bytes |
| stat.uid | int | Owner user ID |
| stat.gid | int | Owner group ID |
| stat.mode | string | Permissions (e.g., "0755") |
| stat.pw_name | string | Owner username |
| stat.gr_name | string | Owner group name |
| stat.mtime | float | Last modified timestamp |
FAQ
Why not just use file with state: directory?
file state=directory is idempotent — it creates the directory if missing. Use stat when you need to make decisions based on whether the directory exists (e.g., skip deployment, trigger different logic).
How do I check directory permissions?
- name: Check permissions
ansible.builtin.stat:
path: /opt/myapp
register: dir_info
- name: Fix permissions if wrong
ansible.builtin.file:
path: /opt/myapp
mode: '0755'
when: dir_info.stat.exists and dir_info.stat.mode != '0755'
become: true
Check Directory Exists
- name: Check if directory exists
ansible.builtin.stat:
path: /opt/myapp
register: dir_check
- name: Create only if missing
ansible.builtin.file:
path: /opt/myapp
state: directory
when: not dir_check.stat.exists
become: true
- name: Skip if already deployed
ansible.builtin.unarchive:
src: myapp.tar.gz
dest: /opt/myapp
when: not dir_check.stat.exists
Check File Exists
- stat:
path: /etc/myapp/config.yml
register: config_file
- template:
src: config.yml.j2
dest: /etc/myapp/config.yml
when: not config_file.stat.exists
Check File Type
- stat:
path: /opt/myapp
register: path_info
- debug:
msg:
- "Exists: {{ path_info.stat.exists }}"
- "Is directory: {{ path_info.stat.isdir | default(false) }}"
- "Is file: {{ path_info.stat.isreg | default(false) }}"
- "Is symlink: {{ path_info.stat.islnk | default(false) }}"
- "Size: {{ path_info.stat.size | default(0) }} bytes"
- "Owner: {{ path_info.stat.pw_name | default('N/A') }}"
- "Mode: {{ path_info.stat.mode | default('N/A') }}"
when: path_info.stat.exists
Common Patterns
Deploy if not already present
- stat: { path: /opt/myapp/app.jar }
register: app
- get_url:
url: "https://releases.example.com/app-{{ version }}.jar"
dest: /opt/myapp/app.jar
when: not app.stat.exists
Backup before overwriting
- stat: { path: /etc/nginx/nginx.conf }
register: nginx_conf
- copy:
src: /etc/nginx/nginx.conf
dest: /etc/nginx/nginx.conf.bak
remote_src: true
when: nginx_conf.stat.exists
Check file age
- stat: { path: /tmp/cache.json }
register: cache
- set_fact:
cache_age: "{{ (ansible_date_time.epoch | int) - (cache.stat.mtime | int) }}"
when: cache.stat.exists
- debug:
msg: "Cache is {{ cache_age }} seconds old"
when: cache.stat.exists and (cache_age | int) > 3600
Check file size
- stat: { path: /var/log/myapp.log }
register: log_file
- name: Rotate if > 100MB
command: logrotate -f /etc/logrotate.d/myapp
when: log_file.stat.exists and log_file.stat.size > 104857600
Stat Properties
| Property | Description |
|----------|-------------|
| exists | Path exists |
| isdir | Is directory |
| isreg | Is regular file |
| islnk | Is symlink |
| size | File size (bytes) |
| mode | Permission mode |
| pw_name | Owner username |
| gr_name | Group name |
| mtime | Modification time (epoch) |
| checksum | SHA1 hash |
| md5 | MD5 hash (if get_md5: true) |
FAQ
Why does stat not fail when file doesn't exist?
By design — stat always succeeds. Check result.stat.exists to determine if the path exists.
How do I check on Windows?
- ansible.windows.win_stat:
path: C:\Program Files\MyApp
register: win_dir
stat vs find module?
stat checks a single path. find searches directories for files matching patterns, size, age criteria.
Check Directory Exists
- ansible.builtin.stat:
path: /opt/myapp
register: app_dir
- debug:
msg: "/opt/myapp exists: {{ app_dir.stat.exists }}"
- file:
path: /opt/myapp
state: directory
when: not app_dir.stat.exists
become: true
Check File Exists
- stat:
path: /etc/myapp/config.yml
register: config
- template:
src: config.yml.j2
dest: /etc/myapp/config.yml
when: not config.stat.exists
become: true
Check File Type
- stat:
path: /opt/myapp
register: result
- debug:
msg: |
Exists: {{ result.stat.exists }}
Is directory: {{ result.stat.isdir | default(false) }}
Is file: {{ result.stat.isreg | default(false) }}
Is link: {{ result.stat.islnk | default(false) }}
Check Permissions
- stat:
path: /opt/scripts/deploy.sh
register: script
- file:
path: /opt/scripts/deploy.sh
mode: '0755'
when: script.stat.exists and script.stat.mode != '0755'
become: true
Check File Size
- stat:
path: /var/log/myapp/app.log
register: logfile
- name: Rotate large log
command: logrotate -f /etc/logrotate.d/myapp
when: logfile.stat.exists and logfile.stat.size > 104857600 # 100MB
become: true
Check Checksum
- stat:
path: /opt/myapp/app.jar
checksum_algorithm: sha256
register: current
- get_url:
url: "{{ download_url }}"
dest: /opt/myapp/app.jar
checksum: "sha256:{{ expected_checksum }}"
when: not current.stat.exists or current.stat.checksum != expected_checksum
Check Multiple Paths
- stat:
path: "{{ item }}"
loop:
- /opt/myapp/config.yml
- /opt/myapp/data
- /opt/myapp/logs
register: paths
- debug:
msg: "{{ item.item }}: {{ 'exists' if item.stat.exists else 'MISSING' }}"
loop: "{{ paths.results }}"
stat Attributes
| Attribute | Description |
|-----------|-------------|
| .exists | Path exists |
| .isdir | Is directory |
| .isreg | Is regular file |
| .islnk | Is symlink |
| .mode | Permissions (e.g., '0755') |
| .size | Size in bytes |
| .uid / .gid | Owner/group IDs |
| .pw_name / .gr_name | Owner/group names |
| .mtime | Modification time (epoch) |
| .checksum | File checksum |
| .lnk_source | Symlink target |
FAQ
Why use stat instead of just using creates?
creates only works with command/shell. stat works everywhere and gives you full file metadata for complex conditions.
How to handle "attribute not found" errors?
Use default() filter: result.stat.isdir | default(false). Attributes like isdir only exist when exists is true.
stat on remote vs local?
stat runs on the managed node. For local files, use delegate_to: localhost or lookup('file').
Related Articles
• conditional execution with Ansible when • how Ansible become works under the hood • Windows management with AnsibleCategory: troubleshooting
Watch the video: Ansible Check If Directory Exists: stat Module Guide — Video Tutorial