ansible.builtin.file Module: Manage Files, Directories & Symlinks (Complete Guide)
By Luca Berton · Published 2026-04-03 · Category: troubleshooting
Complete guide to ansible.builtin.file module. Create, delete, modify files and directories, set permissions, ownership, symlinks, SELinux context.
The ansible.builtin.file module is one of the most frequently used Ansible modules. It manages files, directories, and symlinks on remote hosts — creating, deleting, and setting permissions.
Basic Syntax
- name: Manage a file
ansible.builtin.file:
path: /path/to/file
state: <file|directory|link|hard|touch|absent>
owner: username
group: groupname
mode: '0755'
See also: Ansible Write to File: 5 Methods with Practical Examples (2026)
Create a Directory
- name: Create application directory
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: '0755'
Create nested directories (like mkdir -p)
- name: Create nested directories
ansible.builtin.file:
path: /opt/myapp/config/ssl
state: directory
recurse: true
Create an Empty File (touch)
- name: Create empty log file
ansible.builtin.file:
path: /var/log/myapp.log
state: touch
owner: appuser
group: appuser
mode: '0644'
See also: Ansible file Module: 20 Practical Examples Cookbook (Create, Delete, Permissions)
Create a Symbolic Link
- name: Create symlink
ansible.builtin.file:
src: /opt/myapp/current/config.yml
dest: /etc/myapp/config.yml
state: link
Create a hard link
- name: Create hard link
ansible.builtin.file:
src: /opt/myapp/app.jar
dest: /opt/myapp/app-latest.jar
state: hard
Delete Files and Directories
- name: Remove a file
ansible.builtin.file:
path: /tmp/old-config.yml
state: absent
- name: Remove a directory recursively
ansible.builtin.file:
path: /opt/myapp/old-release
state: absent
See also: Ansible Delete File & Remove File: file Module absent State Guide
Set File Permissions
- name: Set permissions with numeric mode
ansible.builtin.file:
path: /opt/myapp/deploy.sh
mode: '0755'
- name: Set permissions with symbolic mode
ansible.builtin.file:
path: /opt/myapp/deploy.sh
mode: 'u+rwx,g+rx,o+rx'
- name: Add execute permission
ansible.builtin.file:
path: /opt/myapp/script.sh
mode: 'a+x'
Set Owner and Group
- name: Change file ownership
ansible.builtin.file:
path: /opt/myapp
owner: appuser
group: appgroup
recurse: true
Set SELinux Context
- name: Set SELinux context for web content
ansible.builtin.file:
path: /var/www/html
setype: httpd_sys_content_t
recurse: true
Common Parameters Reference
| Parameter | Description | Required |
|-----------|-------------|----------|
| path | Path to the file/directory | Yes |
| state | file, directory, link, hard, touch, absent | No (default: file) |
| owner | Owner of the file | No |
| group | Group of the file | No |
| mode | Permission mode (octal or symbolic) | No |
| recurse | Recursively set attributes (directories only) | No |
| src | Source for link/hard state | For links |
| force | Force creation of symlinks | No |
| follow | Follow symlinks | No |
| access_time | Set access time | No |
| modification_time | Set modification time | No |
Practical Examples
Ensure application directory structure exists
- name: Create app directory structure
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: appuser
group: appuser
mode: '0755'
loop:
- /opt/myapp
- /opt/myapp/config
- /opt/myapp/logs
- /opt/myapp/data
Conditional file management
- name: Create debug log only in development
ansible.builtin.file:
path: /var/log/myapp-debug.log
state: touch
mode: '0666'
when: environment == 'development'
FAQ
What's the difference between state: file and state: touch?
state: file verifies a file exists and sets attributes but does NOT create it. state: touch creates the file if it doesn't exist (like the touch command).
How do I create a file with content?
Use ansible.builtin.copy with content parameter instead. The file module only manages file attributes, not content.
Can I use ansible.builtin.file on Windows?
No, use ansible.windows.win_file for Windows hosts.
Create Directory
- name: Create application directory
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: appuser
group: appgroup
mode: '0755'
become: true
Create Empty File
- name: Create log file
ansible.builtin.file:
path: /var/log/myapp/app.log
state: touch
owner: appuser
mode: '0644'
become: true
Create Symlink
- name: Link current version
ansible.builtin.file:
src: /opt/myapp-2.5.0
dest: /opt/myapp
state: link
become: true
Set Permissions Recursively
- name: Fix permissions
ansible.builtin.file:
path: /opt/myapp
owner: appuser
group: appgroup
mode: '0755'
recurse: true
become: true
Delete File or Directory
- ansible.builtin.file:
path: /opt/myapp-old
state: absent
become: true
Multiple Directories
- name: Create project structure
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: deploy
mode: '0755'
loop:
- /opt/myapp/config
- /opt/myapp/logs
- /opt/myapp/data
- /opt/myapp/tmp
become: true
Versioned Deployment with Symlinks
- file:
path: "/opt/myapp-{{ version }}"
state: directory
- unarchive:
src: "myapp-{{ version }}.tar.gz"
dest: "/opt/myapp-{{ version }}"
- file:
src: "/opt/myapp-{{ version }}"
dest: /opt/myapp-current
state: link
notify: restart myapp
State Reference
| State | Description |
|-------|-------------|
| file | Set attributes (must exist) |
| directory | Create directory (mkdir -p) |
| link | Create symbolic link |
| hard | Create hard link |
| touch | Create empty / update timestamp |
| absent | Delete |
FAQ
Why quote mode as '0755'?
YAML interprets unquoted 0755 as decimal 493. Always quote octal permissions.
file vs copy vs template?
•file: Create empty files, directories, links, set permissions
• copy: Transfer files with content
• template: Render Jinja2 templates
Does directory create parents?
Yes — like mkdir -p.
Create Directory
- ansible.builtin.file:
path: /opt/myapp/logs
state: directory
owner: deploy
group: deploy
mode: '0755'
become: true
Create File
- file:
path: /opt/myapp/.env
state: touch
owner: deploy
mode: '0600'
become: true
Create Symlink
- file:
src: /opt/myapp/current/config.yml
dest: /etc/myapp/config.yml
state: link
become: true
Set Permissions
- file:
path: /opt/myapp/deploy.sh
mode: '0755'
owner: deploy
group: deploy
become: true
# Recursive permissions
- file:
path: /opt/myapp
state: directory
owner: deploy
group: deploy
recurse: true
become: true
Delete File or Directory
- file:
path: /tmp/old-cache
state: absent
become: true
Create Multiple Directories
- file:
path: "{{ item }}"
state: directory
owner: deploy
mode: '0755'
loop:
- /opt/myapp
- /opt/myapp/logs
- /opt/myapp/config
- /opt/myapp/data
- /opt/myapp/tmp
become: true
Deployment Directory Structure
- name: Create release directory
file:
path: "/opt/releases/{{ release_version }}"
state: directory
owner: deploy
become: true
- name: Deploy application...
# (copy/git/unarchive tasks)
- name: Update current symlink
file:
src: "/opt/releases/{{ release_version }}"
dest: /opt/myapp/current
state: link
force: true
become: true
SELinux Context
- file:
path: /var/www/myapp
state: directory
setype: httpd_sys_content_t
become: true
file Module States
| State | Action |
|-------|--------|
| file | Modify existing file attributes |
| directory | Create directory (+ parents) |
| link | Create symbolic link |
| hard | Create hard link |
| touch | Create empty file / update timestamp |
| absent | Delete file or directory |
Key Parameters
| Parameter | Description |
|-----------|-------------|
| path / dest | Target path |
| src | Source for links |
| state | Desired state |
| owner | File owner |
| group | File group |
| mode | Permissions (octal string) |
| recurse | Apply to contents (directories) |
| force | Force link creation |
| follow | Follow symlinks |
| access_time | Set access time |
| modification_time | Set modification time |
FAQ
file vs copy vs template?
file manages file metadata (permissions, ownership, state). copy transfers file content. template processes Jinja2 templates. Use file when you don't need to write content.
Why use '0755' with quotes?
YAML interprets 0755 as octal number 493. Quoting '0755' passes it as a string, which Ansible correctly interprets as permission mode.
How do I create a file with content?
Use copy: content="..." instead. file: state=touch creates an empty file only.
Related Articles
• how Ansible when statements work • Windows fleet automation with Ansible • Ansible loop patterns and tipsFile Module State Reference
The state parameter controls what the file module does:
| State | Description | Creates | Modifies |
|-------|-------------|---------|----------|
| file | Ensure file exists with attributes | No | Yes (permissions, owner) |
| directory | Create directory tree | Yes | Yes |
| touch | Create empty file or update timestamp | Yes | Yes |
| link | Create symbolic link | Yes | Yes |
| hard | Create hard link | Yes | Yes |
| absent | Remove file, directory, or link | No | Deletes |
Default State Behavior
When state is not specified, it defaults to file — which means the file must already exist. This is a common pitfall:
# This FAILS if /tmp/myfile doesn't exist
- ansible.builtin.file:
path: /tmp/myfile
mode: '0644'
# Use touch to create, then set permissions
- ansible.builtin.file:
path: /tmp/myfile
state: touch
mode: '0644'
Recursive Directory Operations
Create Nested Directories
- name: Create full directory tree
ansible.builtin.file:
path: /opt/app/config/ssl/certs
state: directory
mode: '0755'
owner: app
group: app
# Creates all parent directories automatically
Set Permissions Recursively
- name: Fix permissions on entire directory tree
ansible.builtin.file:
path: /var/www/html
state: directory
recurse: true
owner: www-data
group: www-data
mode: '0755'
Warning: recurse: true sets the same mode on both files and directories. For different file/directory permissions, use find with command:
- name: Set directory permissions to 755
ansible.builtin.command: find /var/www -type d -exec chmod 755 {} +
- name: Set file permissions to 644
ansible.builtin.command: find /var/www -type f -exec chmod 644 {} +
Working with Symbolic Links
Create Symlink
- name: Link current release
ansible.builtin.file:
src: /opt/app/releases/v2.1.0
dest: /opt/app/current
state: link
force: true
# force: true overwrites existing link
Create Relative Symlink
- name: Relative symlink within directory
ansible.builtin.file:
src: ../shared/config.yml
dest: /opt/app/current/config.yml
state: link
Remove Broken Symlinks
- name: Find broken symlinks
ansible.builtin.find:
paths: /opt/app
file_type: link
recurse: true
register: all_links
- name: Remove broken symlinks
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ all_links.files }}"
when: not item.islnk
SELinux Context Management
- name: Set SELinux context for web content
ansible.builtin.file:
path: /var/www/custom
state: directory
setype: httpd_sys_content_t
seuser: system_u
serole: object_r
- name: Set SELinux context recursively
ansible.builtin.file:
path: /var/www/custom
state: directory
recurse: true
setype: httpd_sys_rw_content_t
Temporary Files and Directories
- name: Create temporary directory
ansible.builtin.tempfile:
state: directory
prefix: ansible_deploy_
register: temp_dir
- name: Use temporary directory
ansible.builtin.debug:
msg: "Temp dir: {{ temp_dir.path }}"
- name: Clean up temporary directory
ansible.builtin.file:
path: "{{ temp_dir.path }}"
state: absent
when: temp_dir.path is defined
File Module vs Other Modules
| Task | Module | Why |
|------|--------|-----|
| Create empty file | file: state=touch | No content needed |
| Create file with content | copy: content=... | Has content |
| Create file from template | template | Jinja2 variables |
| Modify line in file | lineinfile | Single line changes |
| Add text block | blockinfile | Multi-line blocks |
| Download file | get_url | From URL |
| Copy from local | copy: src=... | Local to remote |
| Set permissions only | file: state=file | File already exists |
| Create directory | file: state=directory | Directory creation |
| Create symlink | file: state=link | Symbolic links |
Idempotency Notes
The file module is fully idempotent:
• state: directory — Creates only if missing, updates attributes if different
• state: touch — Always updates modification time (reports changed every run)
• state: absent — Only removes if exists
• state: link — Creates or updates symlink target
Tip: Avoid state: touch in production playbooks if you don't want changed on every run. Use state: file to only set attributes on existing files.
Category: troubleshooting