Introduction

Ansible has no dedicated "rename" module. Instead, use ansible.builtin.copy + ansible.builtin.file (idempotent), or ansible.builtin.command with mv (simple). This guide covers all approaches with their trade-offs.

Method 1: copy + file (Idempotent)

# Rename by copying to new name, then removing original
- name: Copy file to new name
  ansible.builtin.copy:
    src: /opt/myapp/config.old
    dest: /opt/myapp/config.new
    remote_src: true

- name: Remove original
  ansible.builtin.file:
    path: /opt/myapp/config.old
    state: absent

Pros: Idempotent, safe, preserves permissions Cons: Two tasks, briefly duplicates data

Method 2: command mv (Simple)

# Simple rename
- name: Rename file
  ansible.builtin.command: mv /opt/myapp/old_name.conf /opt/myapp/new_name.conf
  args:
    removes: /opt/myapp/old_name.conf
    creates: /opt/myapp/new_name.conf

removes + creates make it idempotent:

  • Skips if source doesn't exist (already renamed)
  • Skips if destination already exists
# Rename directory
- name: Rename directory
  ansible.builtin.command: mv /opt/old_app /opt/new_app
  args:
    removes: /opt/old_app
    creates: /opt/new_app

Pros: Simple, atomic, handles directories Cons: Uses command module (ansible-lint warning)

Method 3: stat + command (Safe)

# Check before rename
- name: Check if old file exists
  ansible.builtin.stat:
    path: /opt/myapp/old_name.conf
  register: old_file

- name: Rename file
  ansible.builtin.command: mv /opt/myapp/old_name.conf /opt/myapp/new_name.conf
  when: old_file.stat.exists

Rename Multiple Files

# Rename with pattern
- name: Rename config files
  ansible.builtin.command: "mv {{ item.old }} {{ item.new }}"
  args:
    removes: "{{ item.old }}"
    creates: "{{ item.new }}"
  loop:
    - { old: /etc/myapp/app.conf.bak, new: /etc/myapp/app.conf }
    - { old: /etc/myapp/db.conf.bak, new: /etc/myapp/db.conf }
    - { old: /etc/myapp/log.conf.bak, new: /etc/myapp/log.conf }

Rename with Backup

# Backup current, then rename new
- name: Backup current config
  ansible.builtin.copy:
    src: /etc/myapp/config.yml
    dest: "/etc/myapp/config.yml.{{ ansible_date_time.date }}.bak"
    remote_src: true
  ignore_errors: true    # OK if no current config

- name: Move new config into place
  ansible.builtin.command: mv /tmp/config.yml.new /etc/myapp/config.yml
  args:
    removes: /tmp/config.yml.new

Rename Directory

# Rename directory
- name: Rename app directory
  ansible.builtin.command: mv /opt/myapp-v1 /opt/myapp-v2
  args:
    removes: /opt/myapp-v1
    creates: /opt/myapp-v2

# Rename with contents preservation
- name: Rename and update symlink
  block:
    - name: Rename directory
      ansible.builtin.command: mv /opt/myapp-v1 /opt/myapp-v2
      args:
        removes: /opt/myapp-v1
        creates: /opt/myapp-v2

    - name: Update symlink
      ansible.builtin.file:
        src: /opt/myapp-v2
        dest: /opt/myapp
        state: link

Windows Rename

# Windows file rename
- name: Rename file on Windows
  ansible.windows.win_command: >
    cmd /c move "C:\app\old_name.txt" "C:\app\new_name.txt"
  args:
    creates: "C:\\app\\new_name.txt"
    removes: "C:\\app\\old_name.txt"

# PowerShell rename
- name: Rename with PowerShell
  ansible.windows.win_powershell:
    script: |
      if (Test-Path "C:\app\old_name.txt") {
          Rename-Item -Path "C:\app\old_name.txt" -NewName "new_name.txt"
      }

Atomic Rename Pattern

For configuration files that services read:

# Write to temp, then atomic rename
- name: Generate config to temp file
  ansible.builtin.template:
    src: config.yml.j2
    dest: /etc/myapp/config.yml.tmp
    validate: "/opt/myapp/validate-config %s"

- name: Atomic rename into place
  ansible.builtin.command: mv /etc/myapp/config.yml.tmp /etc/myapp/config.yml
  args:
    removes: /etc/myapp/config.yml.tmp
  notify: Reload myapp

Batch Rename with find

# Find and rename files matching pattern
- name: Find .bak files
  ansible.builtin.find:
    paths: /etc/myapp/
    patterns: "*.bak"
  register: bak_files

- name: Rename .bak to .conf
  ansible.builtin.command: "mv {{ item.path }} {{ item.path | regex_replace('\\.bak$', '.conf') }}"
  loop: "{{ bak_files.files }}"

Troubleshooting

"mv: cannot move — Permission denied"

- command: mv /etc/protected.conf /etc/protected.conf.old
  become: true

Cross-Filesystem Move

mv across filesystems copies then deletes. For large files:

- ansible.posix.synchronize:
    src: /mount1/bigfile
    dest: /mount2/bigfile
  delegate_to: "{{ inventory_hostname }}"

- ansible.builtin.file:
    path: /mount1/bigfile
    state: absent

Conclusion

For simple renames, use command: mv with removes/creates for idempotency. For safety-critical renames, use copy + file (two tasks but fully idempotent). For atomic config updates, write to .tmp then mv into place. Always check with stat if unsure whether the source exists. On Windows, use win_command or win_powershell with Rename-Item.