Introduction
Ansible doesn't have a dedicated "rename" module — renaming is a move operation. You can use ansible.builtin.command with mv, or the copy + file (remove original) pattern for idempotent renames. This guide covers all approaches with their tradeoffs.
Method 1: command + mv (Simplest)
- name: Rename file
ansible.builtin.command:
cmd: mv /etc/myapp/config.old /etc/myapp/config.conf
removes: /etc/myapp/config.old # Only run if source exists
creates: /etc/myapp/config.conf # Skip if dest exists
Rename with Condition
- name: Check if old file exists
ansible.builtin.stat:
path: /etc/myapp/config.old
register: old_config
- name: Rename old config to new name
ansible.builtin.command:
cmd: mv /etc/myapp/config.old /etc/myapp/config.conf
when: old_config.stat.exists
Method 2: copy + file (Idempotent)
- name: Copy file to new name (on remote)
ansible.builtin.copy:
src: /etc/myapp/config.old
dest: /etc/myapp/config.conf
remote_src: true
owner: root
group: root
mode: '0644'
- name: Remove original file
ansible.builtin.file:
path: /etc/myapp/config.old
state: absent
Method 3: ansible.posix.synchronize (For Directories)
- name: Rename directory
ansible.builtin.command:
cmd: mv /opt/myapp-v1 /opt/myapp-v2
removes: /opt/myapp-v1
creates: /opt/myapp-v2
Common Patterns
Rename with Timestamp (Backup)
- name: Get current timestamp
ansible.builtin.set_fact:
timestamp: "{{ ansible_date_time.iso8601_basic_short }}"
- name: Backup config before changes
ansible.builtin.command:
cmd: "mv /etc/myapp/config.conf /etc/myapp/config.conf.{{ timestamp }}"
removes: /etc/myapp/config.conf
Rename Multiple Files (Pattern)
- name: Find all .bak files
ansible.builtin.find:
paths: /etc/myapp/
patterns: "*.bak"
register: bak_files
- name: Rename .bak to .conf
ansible.builtin.command:
cmd: "mv {{ item.path }} {{ item.path | regex_replace('\\.bak$', '.conf') }}"
loop: "{{ bak_files.files }}"
Rename If Exists (Safe Pattern)
- name: Safely rename with full checks
block:
- name: Check source exists
ansible.builtin.stat:
path: "{{ src_path }}"
register: source_file
- name: Check dest doesn't exist
ansible.builtin.stat:
path: "{{ dest_path }}"
register: dest_file
- name: Rename file
ansible.builtin.command:
cmd: "mv {{ src_path }} {{ dest_path }}"
when:
- source_file.stat.exists
- not dest_file.stat.exists
vars:
src_path: /tmp/uploaded_file.tmp
dest_path: /opt/app/data/processed_file.dat
Atomic Rename (Replace in Place)
# Write new file, then atomically rename over old one
- name: Write new config to temp file
ansible.builtin.template:
src: config.j2
dest: /etc/myapp/config.conf.new
mode: '0644'
- name: Atomically replace old config
ansible.builtin.command:
cmd: mv /etc/myapp/config.conf.new /etc/myapp/config.conf
# mv on same filesystem is atomic (rename syscall)
Rename with Preserved Permissions
- name: Get original file info
ansible.builtin.stat:
path: /etc/myapp/oldname.conf
register: original_file
- name: Rename file
ansible.builtin.command:
cmd: mv /etc/myapp/oldname.conf /etc/myapp/newname.conf
when: original_file.stat.exists
# Permissions are preserved by mv automatically
Rename Directory
- name: Check if old directory exists
ansible.builtin.stat:
path: /opt/releases/current
register: current_dir
- name: Rename current to previous
ansible.builtin.command:
cmd: mv /opt/releases/current /opt/releases/previous
when: current_dir.stat.exists and current_dir.stat.isdir
Why No ansible.builtin.rename Module?
Ansible treats file operations as state declarations:
file: Ensure a file/directory exists or is absentcopy: Ensure a file has specific contenttemplate: Ensure a file matches a template
"Rename" is a procedural action, not a state. The closest idempotent approach is copy (remote_src) + file (state=absent).
Comparison of Methods
| Method | Idempotent | Speed | Preserves Permissions |
|---|---|---|---|
command: mv + creates/removes | ✅ | Fast | ✅ |
copy (remote_src) + file (absent) | ✅ | Slower (copies data) | Manual (set mode) |
shell: mv | ❌ (unless guarded) | Fast | ✅ |
Troubleshooting
| Issue | Fix |
|---|---|
| "changed" every run | Add creates: and removes: to command |
| Permission denied | Use become: true |
| "No such file" | Check with stat first, use when |
| Cross-filesystem move fails | mv handles this (falls back to copy+delete) |
| Want to rename symlink, not target | Use -T flag: mv -T |
Best Practices
- Use
creates+removeswith command module for idempotency - Check with
statfirst for complex conditions - Prefer
mvovercopy+delete— atomic on same filesystem - Use
blockfor multi-step renames — keeps logic together - Log what was renamed — register output for debugging
- Test in check mode —
commandis skipped in check mode; usecopy+fileif check mode matters
Conclusion
Use command: mv with creates/removes for simple idempotent renames. Use stat + when for conditional renames. Use the copy (remote_src) + file (absent) pattern when you need full idempotency in check mode. There's no built-in rename module because Ansible favors declarative state over procedural actions.