Ansible Create Hard Link & Symlink: file Module Guide
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
How to create hard links and symbolic links with Ansible file module. Use state=hard and state=link with permissions, force, and practical examples.

How to create a hard link in Linux with Ansible?
See also: Ansible Create Symlink: file Module with state=link (Guide)
Ansible creates a hard link
> ansible.builtin.file Manage files and file properties
Today we're talking about the Ansible module file.
The full name is ansible.builtin.file, which means that is part of the collection of modules "builtin" with ansible and shipped with it.
It's a module pretty stable and out for years.
It works in a different variety of operating systems.
It manages files and file properties.
For a symlink (or softlink) use see the following parameters of Ansible file module.
For Windows targets, use the ansible.windows.win_file module instead.
Parameters
- src string - symlink path
- dest string - destination file path
- state string - file/absent/directory/link/hard/touch
- mode/owner/group - permission
- setype/seuser/selevel - SELinux
## Playbook
Let's jump into a real-life playbook on how to create a symbolic link with Ansible.
code
- create_hardlink.yml
---
- name: file module demo
hosts: all
vars:
mylink: "~/link"
myfile: "~/example.txt"
tasks:
- name: Creating hardlink
ansible.builtin.file:
src: "{{ myfile }}"
dest: "{{ mylink }}"
state: hard
execution
$ ansible-playbook -i virtualmachines/demo/inventory create\ link/hardlink.yml
PLAY [file module demo] ***************************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [demo.example.com]
TASK [Creating hardlink] **************************************************************************
changed: [demo.example.com]
PLAY RECAP ****************************************************************************************
demo.example.com : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
before execution
$ ssh devops@demo.example.com
[devops@demo ~]$ ls -al
total 16
drwx------. 4 devops wheel 111 Dec 1 15:07 .
drwxr-xr-x. 4 root root 35 Nov 28 16:46 ..
drwx------. 3 devops wheel 17 Nov 28 16:46 .ansible
-rw-------. 1 devops wheel 224 Dec 1 15:07 .bash_history
-rw-r--r--. 1 devops wheel 18 Dec 4 2020 .bash_logout
-rw-r--r--. 1 devops wheel 141 Dec 4 2020 .bash_profile
-rw-r--r--. 1 devops wheel 376 Dec 4 2020 .bashrc
drwx------. 2 devops wheel 29 Nov 28 16:46 .ssh
[devops@demo ~]$ echo test > example.txt
[devops@demo ~]$ cat example.txt
test
[devops@demo ~]$
after execution
$ ssh devops@demo.example.com
[devops@demo ~]$ ls -al
total 24
drwx------. 4 devops wheel 142 Dec 1 15:09 .
drwxr-xr-x. 4 root root 35 Nov 28 16:46 ..
drwx------. 3 devops wheel 17 Nov 28 16:46 .ansible
-rw-------. 1 devops wheel 276 Dec 1 15:08 .bash_history
-rw-r--r--. 1 devops wheel 18 Dec 4 2020 .bash_logout
-rw-r--r--. 1 devops wheel 141 Dec 4 2020 .bash_profile
-rw-r--r--. 1 devops wheel 376 Dec 4 2020 .bashrc
drwx------. 2 devops wheel 29 Nov 28 16:46 .ssh
-rw-r--r--. 2 devops wheel 5 Dec 1 15:08 example.txt
-rw-r--r--. 2 devops wheel 5 Dec 1 15:08 link
[devops@demo ~]$ cat link
test
[devops@demo ~]$ cat example.txt
test
[devops@demo ~]$ ls -al
total 24
drwx------. 4 devops wheel 142 Dec 1 15:09 .
drwxr-xr-x. 4 root root 35 Nov 28 16:46 ..
drwx------. 3 devops wheel 17 Nov 28 16:46 .ansible
-rw-------. 1 devops wheel 276 Dec 1 15:08 .bash_history
-rw-r--r--. 1 devops wheel 18 Dec 4 2020 .bash_logout
-rw-r--r--. 1 devops wheel 141 Dec 4 2020 .bash_profile
-rw-r--r--. 1 devops wheel 376 Dec 4 2020 .bashrc
drwx------. 2 devops wheel 29 Nov 28 16:46 .ssh
-rw-r--r--. 2 devops wheel 5 Dec 1 15:08 example.txt
-rw-r--r--. 2 devops wheel 5 Dec 1 15:08 link
[devops@demo ~]$ chmod 777 link
[devops@demo ~]$ ls -al
total 24
drwx------. 4 devops wheel 142 Dec 1 15:09 .
drwxr-xr-x. 4 root root 35 Nov 28 16:46 ..
drwx------. 3 devops wheel 17 Nov 28 16:46 .ansible
-rw-------. 1 devops wheel 276 Dec 1 15:08 .bash_history
-rw-r--r--. 1 devops wheel 18 Dec 4 2020 .bash_logout
-rw-r--r--. 1 devops wheel 141 Dec 4 2020 .bash_profile
-rw-r--r--. 1 devops wheel 376 Dec 4 2020 .bashrc
drwx------. 2 devops wheel 29 Nov 28 16:46 .ssh
-rwxrwxrwx. 2 devops wheel 5 Dec 1 15:08 example.txt
-rwxrwxrwx. 2 devops wheel 5 Dec 1 15:08 link
[devops@demo ~]$ touch link
[devops@demo ~]$ ls -al
total 24
drwx------. 4 devops wheel 142 Dec 1 15:09 .
drwxr-xr-x. 4 root root 35 Nov 28 16:46 ..
drwx------. 3 devops wheel 17 Nov 28 16:46 .ansible
-rw-------. 1 devops wheel 276 Dec 1 15:08 .bash_history
-rw-r--r--. 1 devops wheel 18 Dec 4 2020 .bash_logout
-rw-r--r--. 1 devops wheel 141 Dec 4 2020 .bash_profile
-rw-r--r--. 1 devops wheel 376 Dec 4 2020 .bashrc
drwx------. 2 devops wheel 29 Nov 28 16:46 .ssh
-rwxrwxrwx. 2 devops wheel 5 Dec 1 15:10 example.txt
-rwxrwxrwx. 2 devops wheel 5 Dec 1 15:10 link
[devops@demo ~]$ touch link
[devops@demo ~]$ ls -al
total 24
drwx------. 4 devops wheel 142 Dec 1 15:09 .
drwxr-xr-x. 4 root root 35 Nov 28 16:46 ..
drwx------. 3 devops wheel 17 Nov 28 16:46 .ansible
-rw-------. 1 devops wheel 359 Dec 1 15:11 .bash_history
-rw-r--r--. 1 devops wheel 18 Dec 4 2020 .bash_logout
-rw-r--r--. 1 devops wheel 141 Dec 4 2020 .bash_profile
-rw-r--r--. 1 devops wheel 376 Dec 4 2020 .bashrc
drwx------. 2 devops wheel 29 Nov 28 16:46 .ssh
-rwxrwxrwx. 2 devops wheel 5 Dec 1 15:19 example.txt
-rwxrwxrwx. 2 devops wheel 5 Dec 1 15:19 link
[devops@demo ~]$ ls -li *
134907033 -rwxrwxrwx. 2 devops wheel 5 Dec 1 15:19 example.txt
134907033 -rwxrwxrwx. 2 devops wheel 5 Dec 1 15:19 link
[devops@demo ~]$
See also: Ansible Create Directory: file Module with state=directory (Guide)
Conclusion
Now you know how to create a hard link in the Linux filesystem with Ansible.
Create Hard Link
- ansible.builtin.file:
src: /opt/myapp/current/bin/app
dest: /usr/local/bin/app
state: hard
become: trueSee also: Ansible Create File with Content: copy Module content Parameter
Create Symbolic Link
- file:
src: /opt/releases/v2.5.0
dest: /opt/myapp/current
state: link
become: trueSymlink for Version Management
# Deploy new version
- unarchive:
src: "app-{{ version }}.tar.gz"
dest: /opt/releases/
remote_src: true
become: true
# Update symlink to new version
- file:
src: "/opt/releases/{{ version }}"
dest: /opt/myapp/current
state: link
force: true # Overwrite existing link
become: true
notify: restart appHard Link vs Symbolic Link
| Feature | Hard Link | Symbolic Link |
|---|---|---|
| Cross filesystem | ❌ | ✅ |
| Link to directory | ❌ | ✅ |
| Original deleted | File persists | Link breaks |
| Inode | Same as source | Different |
state value | hard | link |
Multiple Symlinks
- file:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
state: link
loop:
- { src: /opt/myapp/current/bin/app, dest: /usr/local/bin/app }
- { src: /opt/myapp/current/etc/app.conf, dest: /etc/app.conf }
- { src: /opt/myapp/current/lib, dest: /usr/local/lib/myapp }
become: trueForce Overwrite
# Replace existing file/link with new symlink
- file:
src: /opt/releases/v3.0.0
dest: /opt/myapp/current
state: link
force: true # Required if dest already exists and isn't a link
become: trueSymlink with Owner
- file:
src: /shared/data
dest: /home/appuser/data
state: link
owner: appuser
group: appuser
follow: false # Set ownership on the link itself
become: trueRemove Link
# Remove symlink (not the target!)
- file:
path: /opt/myapp/current
state: absent
become: trueRollback Pattern
- name: Deploy new version
block:
- file:
src: "/opt/releases/{{ new_version }}"
dest: /opt/myapp/current
state: link
force: true
- uri:
url: http://localhost:8080/health
status_code: 200
retries: 5
delay: 3
rescue:
- name: Rollback to previous version
file:
src: "/opt/releases/{{ old_version }}"
dest: /opt/myapp/current
state: link
force: true
become: trueFAQ
When to use hard links vs symlinks?
Symlinks for most cases (versioned deploys, config management). Hard links only when you need the file to survive source deletion.
Can I create a symlink to a non-existent target?
Yes — with force: true. The link will be "dangling" until the target exists.
follow parameter?
follow: true (default) — operations apply to the target. follow: false — operations apply to the link itself.
Related Articles
Category: troubleshooting
Watch the video: Ansible Create Hard Link & Symlink: file Module Guide — Video Tutorial