Introduction

The Ansible file module (ansible.builtin.file) manages file system objects — create directories, set ownership and permissions, create symlinks, and remove files. It doesn't write file content (use copy, template, or lineinfile for that).

Create a Directory

- name: Create app directory
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: deploy
    group: deploy
    mode: "0755"

# Create nested directories (like mkdir -p)
- name: Create nested structure
  ansible.builtin.file:
    path: /opt/myapp/logs/archive
    state: directory
    owner: deploy
    group: deploy
    mode: "0755"

Set File Permissions

# Change permissions on existing file
- name: Set script executable
  ansible.builtin.file:
    path: /opt/myapp/start.sh
    mode: "0755"

# Change ownership
- name: Set file ownership
  ansible.builtin.file:
    path: /opt/myapp/config.yml
    owner: deploy
    group: deploy
    mode: "0640"

# Recursive permissions on directory
- name: Set directory permissions recursively
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: deploy
    group: deploy
    recurse: true
# Create a symlink
- name: Link current release
  ansible.builtin.file:
    src: /opt/myapp/releases/v2.1.0
    dest: /opt/myapp/current
    state: link
    owner: deploy
    group: deploy

# Link config file
- name: Link nginx config
  ansible.builtin.file:
    src: /opt/myapp/nginx.conf
    dest: /etc/nginx/sites-enabled/myapp
    state: link
  notify: Reload nginx

# Force overwrite existing file/link
- name: Force symlink
  ansible.builtin.file:
    src: /opt/myapp/releases/v2.2.0
    dest: /opt/myapp/current
    state: link
    force: true

Remove Files and Directories

# Remove a file
- name: Remove old config
  ansible.builtin.file:
    path: /etc/myapp/old-config.yml
    state: absent

# Remove a directory (recursive)
- name: Remove old release
  ansible.builtin.file:
    path: /opt/myapp/releases/v1.0.0
    state: absent

Create Empty File

# Create file if it doesn't exist (touch)
- name: Create log file
  ansible.builtin.file:
    path: /var/log/myapp/app.log
    state: touch
    owner: deploy
    group: deploy
    mode: "0644"

# Touch also updates modification time on existing files
- name: Update timestamp
  ansible.builtin.file:
    path: /opt/myapp/.last-deploy
    state: touch
    modification_time: preserve    # Keep original mtime
    access_time: preserve

Parameters

ParameterDescription
path / destFile/directory path (required)
statefile, directory, link, hard, touch, absent
srcSource for symlinks
ownerFile owner
groupFile group
modePermissions (e.g., "0755", "u+rwx,g+rx,o+rx")
recurseApply owner/group/mode recursively (directories only)
forceForce symlink creation even if dest exists
followFollow symlinks (default: true for file, false for link)
modification_timeSet mtime (preserve, now, or timestamp)
access_timeSet atime (preserve, now, or timestamp)
selevel, setype, seuserSELinux context

State Values

StateDescription
fileModify existing file (fails if doesn't exist)
directoryCreate directory (and parents)
linkCreate symbolic link
hardCreate hard link
touchCreate empty file or update timestamp
absentRemove file or directory

Practical Patterns

Application Directory Structure

- name: Create app directory structure
  ansible.builtin.file:
    path: "{{ item.path }}"
    state: directory
    owner: "{{ app_user }}"
    group: "{{ app_group }}"
    mode: "{{ item.mode | default('0755') }}"
  loop:
    - { path: "/opt/{{ app_name }}" }
    - { path: "/opt/{{ app_name }}/releases" }
    - { path: "/opt/{{ app_name }}/shared" }
    - { path: "/opt/{{ app_name }}/shared/config" }
    - { path: "/opt/{{ app_name }}/shared/log" }
    - { path: "/var/log/{{ app_name }}", mode: "0750" }
    - { path: "/var/run/{{ app_name }}", mode: "0755" }
- name: Create release directory
  ansible.builtin.file:
    path: "/opt/myapp/releases/{{ release_version }}"
    state: directory
    owner: deploy
    group: deploy

# ... deploy app files ...

- name: Switch to new release
  ansible.builtin.file:
    src: "/opt/myapp/releases/{{ release_version }}"
    dest: /opt/myapp/current
    state: link
    force: true
  notify: Restart application

- name: Clean old releases (keep last 5)
  ansible.builtin.shell: |
    ls -1dt /opt/myapp/releases/*/ | tail -n +6 | xargs rm -rf
  changed_when: false

Secure File Permissions

# SSH directory and keys
- name: SSH directory
  ansible.builtin.file:
    path: "~{{ user }}/.ssh"
    state: directory
    owner: "{{ user }}"
    group: "{{ user }}"
    mode: "0700"

- name: SSH authorized_keys permissions
  ansible.builtin.file:
    path: "~{{ user }}/.ssh/authorized_keys"
    owner: "{{ user }}"
    group: "{{ user }}"
    mode: "0600"

# SSL certificates
- name: SSL directory
  ansible.builtin.file:
    path: /etc/ssl/myapp
    state: directory
    owner: root
    group: root
    mode: "0700"

- name: Private key permissions
  ansible.builtin.file:
    path: /etc/ssl/myapp/server.key
    owner: root
    group: root
    mode: "0600"

Check Before Modify

- name: Check if file exists
  ansible.builtin.stat:
    path: /opt/myapp/config.yml
  register: config_file

- name: Set permissions only if exists
  ansible.builtin.file:
    path: /opt/myapp/config.yml
    owner: deploy
    mode: "0640"
  when: config_file.stat.exists

Mode Format

# Octal notation (quote it!)
mode: "0755"     # rwxr-xr-x
mode: "0644"     # rw-r--r--
mode: "0600"     # rw-------
mode: "0700"     # rwx------

# Symbolic notation
mode: "u=rwx,g=rx,o=rx"    # 0755
mode: "u+x"                 # Add execute for owner
mode: "g-w"                  # Remove write for group
mode: "a+r"                  # Add read for all

Always quote octal modes — without quotes, 0644 is interpreted as the integer 420.

Troubleshooting

"file not found" with state: file

state: file modifies existing files only — it doesn't create:

# WRONG — fails if file doesn't exist
- file:
    path: /tmp/newfile.txt
    state: file
    mode: "0644"

# RIGHT — create first with touch or copy
- file:
    path: /tmp/newfile.txt
    state: touch
    mode: "0644"

Permissions Not Applied Recursively

recurse only works with state: directory:

# Apply to directory and all contents
- file:
    path: /opt/myapp
    state: directory
    owner: deploy
    group: deploy
    recurse: true    # Sets owner/group on ALL files inside

Use force: true to overwrite existing symlinks or files:

- file:
    src: /opt/myapp/releases/v2.0
    dest: /opt/myapp/current
    state: link
    force: true

Conclusion

ansible.builtin.file manages file system objects: create directories (state: directory), set permissions (mode, owner, group), create symlinks (state: link), touch files (state: touch), and remove (state: absent). Always quote octal modes. Use recurse: true for directory-wide ownership changes. Combine with copy/template for content and file for permissions. For deployment patterns, symlinks (state: link + force: true) enable instant rollbacks.