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?
I'm going to show you a live Playbook and some simple Ansible code. I'm Luca Berton and welcome to today's episode of Ansible Pilot.
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 - SELinuxThis module has some parameters to perform any tasks. The two required fields are "src" and "dest" which specify the filesystem paths of the har link and the target file. The state defines the type of object we are modifying, the default is "file" but for our use case, we need the "link" option. Let me highlight also the permission and SELinux parameters.
## 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: true
See 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: true
Symlink 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 app
Hard 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: true
Force 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: true
Symlink 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: true
Remove Link
# Remove symlink (not the target!)
- file:
path: /opt/myapp/current
state: absent
become: true
Rollback 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: true
FAQ
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
• the Ansible inventory deep-dive • managing files with ansible.builtin.file • configuring Windows services via AnsibleCategory: troubleshooting
Watch the video: Ansible Create Hard Link & Symlink: file Module Guide — Video Tutorial