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

ParameterTypeRequiredDescription
pathstringYesFull filesystem path to check
followboolNoFollow symlinks (default: false)
get_checksumboolNoCalculate file checksum (default: true)
checksum_algorithmstringNomd5, sha1 (default), sha256, sha384, sha512
get_mimeboolNoGet MIME type (default: true)
get_attributesboolNoGet file attributes (default: true)

Key Return Values

PropertyTypeDescription
stat.existsboolWhether the path exists
stat.isdirboolTrue if path is a directory
stat.isregboolTrue if path is a regular file
stat.islnkboolTrue if path is a symbolic link
stat.modestringFile permissions (e.g., "0755")
stat.sizeintFile size in bytes
stat.uidintOwner user ID
stat.gidintOwner group ID
stat.pw_namestringOwner username
stat.gr_namestringOwner group name
stat.mtimefloatLast modification time (epoch)
stat.checksumstringFile 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
- 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

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.