Introduction
Checking whether a file or directory exists before performing an action is one of the most common patterns in Ansible automation. The ansible.builtin.stat module retrieves file system status — existence, type, permissions, size, timestamps, and checksums — without modifying anything. Combined with register and when, it enables powerful conditional logic in your playbooks.
Module Reference
Full name: ansible.builtin.stat
Collection: ansible.builtin
Key Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Full filesystem path to check |
follow | bool | No | Follow symlinks (default: false) |
get_checksum | bool | No | Calculate file checksum (default: true) |
checksum_algorithm | string | No | md5, sha1 (default), sha256, sha384, sha512 |
get_mime | bool | No | Get MIME type (default: true) |
get_attributes | bool | No | Get file attributes (default: true) |
Key Return Values
| Property | Type | Description |
|---|---|---|
stat.exists | bool | Whether the path exists |
stat.isdir | bool | True if path is a directory |
stat.isreg | bool | True if path is a regular file |
stat.islnk | bool | True if path is a symbolic link |
stat.mode | string | File permissions (e.g., "0755") |
stat.size | int | File size in bytes |
stat.uid | int | Owner user ID |
stat.gid | int | Owner group ID |
stat.pw_name | string | Owner username |
stat.gr_name | string | Owner group name |
stat.mtime | float | Last modification time (epoch) |
stat.checksum | string | File checksum |
Check If a Directory Exists
The most common use case:
---
- name: Check if directory exists
hosts: all
vars:
directory: "/tmp"
tasks:
- name: Check if the directory exists
ansible.builtin.stat:
path: "{{ directory }}"
register: dir_check
- name: Directory found
ansible.builtin.debug:
msg: "Directory {{ directory }} is present"
when: dir_check.stat.isdir is defined and dir_check.stat.isdir
- name: Directory not found
ansible.builtin.debug:
msg: "Directory {{ directory }} does NOT exist"
when: not dir_check.stat.exists
Check If a File Exists
- name: Check if configuration file exists
ansible.builtin.stat:
path: /etc/myapp/config.yml
register: config_file
- name: Create default config if missing
ansible.builtin.template:
src: config.yml.j2
dest: /etc/myapp/config.yml
when: not config_file.stat.exists
Conditional Task Execution
Run Task Only If File Exists
- name: Check for migration script
ansible.builtin.stat:
path: /opt/app/migrate.sh
register: migration_script
- name: Run migration
ansible.builtin.command: /opt/app/migrate.sh
when: migration_script.stat.exists
Create Directory Only If Missing
- name: Check for data directory
ansible.builtin.stat:
path: /var/data/myapp
register: data_dir
- name: Create data directory with specific ownership
ansible.builtin.file:
path: /var/data/myapp
state: directory
owner: appuser
group: appgroup
mode: '0750'
when: not data_dir.stat.exists
Skip Deployment If Already Deployed
- name: Check deployment marker
ansible.builtin.stat:
path: "/opt/app/releases/{{ version }}/.deployed"
register: deploy_marker
- name: Deploy application
ansible.builtin.include_tasks: deploy.yml
when: not deploy_marker.stat.exists
Check File Permissions
- name: Verify file permissions
ansible.builtin.stat:
path: /etc/shadow
register: shadow_file
- name: Alert if permissions are wrong
ansible.builtin.fail:
msg: "/etc/shadow has incorrect permissions: {{ shadow_file.stat.mode }}"
when: shadow_file.stat.mode != '0640' and shadow_file.stat.mode != '0000'
Check File Age
- name: Check cache file age
ansible.builtin.stat:
path: /tmp/api-cache.json
register: cache_file
- name: Refresh cache if older than 1 hour
ansible.builtin.command: /opt/refresh-cache.sh
when: >
not cache_file.stat.exists or
(ansible_date_time.epoch | int - cache_file.stat.mtime | int) > 3600
Check File Size
- name: Check log file size
ansible.builtin.stat:
path: /var/log/myapp/app.log
register: log_file
- name: Rotate log if larger than 100MB
ansible.builtin.command: /opt/rotate-logs.sh
when: log_file.stat.exists and log_file.stat.size > 104857600
Verify File Checksums
- name: Get checksum of deployed binary
ansible.builtin.stat:
path: /opt/app/bin/myapp
checksum_algorithm: sha256
register: current_binary
- name: Update binary if checksum doesn't match
ansible.builtin.copy:
src: files/myapp
dest: /opt/app/bin/myapp
mode: '0755'
when: current_binary.stat.checksum | default('') != expected_checksum
Check Multiple Paths
- name: Verify required directories exist
ansible.builtin.stat:
path: "{{ item }}"
register: required_dirs
loop:
- /opt/myapp/config
- /opt/myapp/data
- /opt/myapp/logs
- /var/run/myapp
- name: Create missing directories
ansible.builtin.file:
path: "{{ item.item }}"
state: directory
mode: '0755'
loop: "{{ required_dirs.results }}"
when: not item.stat.exists
Distinguish Between Files, Directories, and Links
- name: Check path type
ansible.builtin.stat:
path: /opt/app/current
register: path_info
- name: Report path type
ansible.builtin.debug:
msg: >-
/opt/app/current is
{{ 'a directory' if path_info.stat.isdir else '' }}
{{ 'a regular file' if path_info.stat.isreg else '' }}
{{ 'a symbolic link' if path_info.stat.islnk else '' }}
{{ 'not found' if not path_info.stat.exists else '' }}
Windows Equivalent
For Windows hosts, use ansible.windows.win_stat:
- name: Check if directory exists on Windows
ansible.windows.win_stat:
path: C:\Program Files\MyApp
register: win_dir
- name: Directory status
ansible.builtin.debug:
msg: "Directory exists: {{ win_dir.stat.exists }}"
Common Patterns
Idempotent First-Run Setup
- name: Check if initial setup was done
ansible.builtin.stat:
path: /opt/app/.setup-complete
register: setup_done
- name: Run initial setup
block:
- name: Initialize database
ansible.builtin.command: /opt/app/init-db.sh
- name: Load seed data
ansible.builtin.command: /opt/app/seed-data.sh
- name: Mark setup complete
ansible.builtin.file:
path: /opt/app/.setup-complete
state: touch
mode: '0444'
when: not setup_done.stat.exists
Backup Before Modify
- name: Check if config exists
ansible.builtin.stat:
path: /etc/nginx/nginx.conf
register: nginx_conf
- name: Backup existing config
ansible.builtin.copy:
src: /etc/nginx/nginx.conf
dest: "/etc/nginx/nginx.conf.bak.{{ ansible_date_time.iso8601_basic_short }}"
remote_src: true
when: nginx_conf.stat.exists
- name: Deploy new config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
Related Articles
- Create an Empty File: Ansible file Module
- Remove a File: Ansible file Module
- Check Directory Exists on Windows: win_stat
- Ansible File Module: Manage File Properties
- Copy Files to Remote Hosts
- Ansible Fetch Module: Download Files
- Ansible Debug Module Guide
Conclusion
The ansible.builtin.stat module is the foundation of conditional automation in Ansible. By checking file existence, type, permissions, age, and checksums before acting, you build truly idempotent playbooks that behave correctly whether running for the first time or the hundredth time. The pattern is always the same: stat → register → when — simple, reliable, and essential for production-quality automation.