Introduction

Managing file permissions, ownership, and security contexts is fundamental to Linux system automation. The ansible.builtin.file module handles everything from simple chmod/chown operations to SELinux contexts and extended ACLs — all idempotently.

For Windows, use ansible.windows.win_file and ansible.windows.win_acl.

Module Parameters for Permissions

ParameterTypeRequiredDescription
pathstringYesFile or directory path
modestringNoPermissions — octal ('0644') or symbolic ('u=rw,g=r,o=r')
ownerstringNoUser that owns the file
groupstringNoGroup that owns the file
statestringNofile, directory, link, hard, touch, absent
recurseboolNoApply recursively to directory contents
setypestringNoSELinux type context
seuserstringNoSELinux user context
selevelstringNoSELinux level/range

Important: Always quote octal modes as strings ('0644', not 0644). Without quotes, YAML interprets 0644 as the integer 420.

Basic Examples

Set File Permissions

- name: Set file to 644
  ansible.builtin.file:
    path: /etc/myapp/config.yml
    owner: root
    group: root
    mode: '0644'

Set Directory Permissions

- name: Create directory with permissions
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: appuser
    group: appgroup
    mode: '0755'

Change Ownership Only

- name: Change owner
  ansible.builtin.file:
    path: /var/log/myapp
    owner: appuser
    group: appgroup

Permission Formats

Octal Notation

# Common permission patterns
mode: '0644'  # rw-r--r-- (config files)
mode: '0755'  # rwxr-xr-x (directories, scripts)
mode: '0600'  # rw------- (private keys, passwords)
mode: '0700'  # rwx------ (private directories)
mode: '0775'  # rwxrwxr-x (shared directories)
mode: '0444'  # r--r--r-- (read-only files)
mode: '1777'  # rwxrwxrwt (sticky bit, like /tmp)
mode: '2755'  # rwxr-sr-x (setgid directory)
mode: '4755'  # rwsr-xr-x (setuid executable)

Symbolic Notation

# Equivalent to 0644
mode: 'u=rw,g=r,o=r'

# Add execute for owner only
mode: 'u+x'

# Remove write for group and others
mode: 'go-w'

# Set exact permissions
mode: 'u=rwx,g=rx,o=rx'  # 0755

Practical Examples

Secure SSH Keys

- name: Set SSH directory permissions
  ansible.builtin.file:
    path: "/home/{{ ansible_user }}/.ssh"
    state: directory
    owner: "{{ ansible_user }}"
    group: "{{ ansible_user }}"
    mode: '0700'

- name: Set private key permissions
  ansible.builtin.file:
    path: "/home/{{ ansible_user }}/.ssh/id_rsa"
    owner: "{{ ansible_user }}"
    group: "{{ ansible_user }}"
    mode: '0600'

- name: Set authorized_keys permissions
  ansible.builtin.file:
    path: "/home/{{ ansible_user }}/.ssh/authorized_keys"
    owner: "{{ ansible_user }}"
    group: "{{ ansible_user }}"
    mode: '0644'

Web Server Document Root

- name: Set up web root
  ansible.builtin.file:
    path: /var/www/mysite
    state: directory
    owner: www-data
    group: www-data
    mode: '0755'
    recurse: true  # Apply to all contents

Recursive Permissions

- name: Fix permissions on application directory
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: appuser
    group: appgroup
    mode: '0755'
    recurse: true

Note: recurse: true sets the same mode on ALL files and directories. To set different permissions for files vs directories, use find + file:

- name: Set directory permissions to 755
  ansible.builtin.shell: find /opt/myapp -type d -exec chmod 755 {} \;

- name: Set file permissions to 644
  ansible.builtin.shell: find /opt/myapp -type f -exec chmod 644 {} \;

- name: Make scripts executable
  ansible.builtin.shell: find /opt/myapp/bin -type f -exec chmod 755 {} \;

Or with the find module:

- name: Find all directories
  ansible.builtin.find:
    paths: /opt/myapp
    file_type: directory
    recurse: true
  register: app_dirs

- name: Set directory permissions
  ansible.builtin.file:
    path: "{{ item.path }}"
    mode: '0755'
  loop: "{{ app_dirs.files }}"

Application Deployment Permissions

- name: Deploy application with correct permissions
  hosts: app_servers
  become: true
  vars:
    app_user: appuser
    app_group: appgroup
    app_dir: /opt/myapp

  tasks:
    - name: Application directory
      ansible.builtin.file:
        path: "{{ app_dir }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0755'

    - name: Config directory (restricted)
      ansible.builtin.file:
        path: "{{ app_dir }}/config"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0750'

    - name: Log directory
      ansible.builtin.file:
        path: /var/log/myapp
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0755'

    - name: PID directory
      ansible.builtin.file:
        path: /var/run/myapp
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0755'

    - name: Secrets file
      ansible.builtin.copy:
        src: secrets.yml
        dest: "{{ app_dir }}/config/secrets.yml"
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0600'

SELinux Context

- name: Set SELinux context for web content
  ansible.builtin.file:
    path: /var/www/mysite
    state: directory
    setype: httpd_sys_content_t
    recurse: true

- name: Set SELinux context for writable directory
  ansible.builtin.file:
    path: /var/www/mysite/uploads
    state: directory
    setype: httpd_sys_rw_content_t

ACL Permissions

For more granular control, use the ansible.posix.acl module:

- name: Grant read access to monitoring group
  ansible.posix.acl:
    path: /var/log/myapp
    entity: monitoring
    etype: group
    permissions: rx
    state: present
    recursive: true

Common Mistakes

Unquoted Octal Mode

# WRONG — YAML interprets 0644 as integer 420
mode: 0644

# CORRECT — always quote octal modes
mode: '0644'

Missing Leading Zero

# WRONG — 644 is not the same as 0644 in all contexts
mode: '644'

# CORRECT — include the leading zero
mode: '0644'

recurse on Files

# WRONG — recurse only works with state: directory
- ansible.builtin.file:
    path: /etc/myapp/config.yml
    mode: '0644'
    recurse: true  # Ignored or error — this is a file, not a directory

Conclusion

The ansible.builtin.file module is your primary tool for managing Linux file permissions, ownership, and security contexts. Always quote octal modes as strings, use recurse: true for directories, and combine with find when files and directories need different permissions. For sensitive files like SSH keys and secrets, enforce 0600; for web content under SELinux, set the appropriate setype. The module is idempotent — running it repeatedly only makes changes when the current state doesn't match the desired state.