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 absent
  • copy: Ensure a file has specific content
  • template: 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

MethodIdempotentSpeedPreserves Permissions
command: mv + creates/removes✅Fast✅
copy (remote_src) + file (absent)✅Slower (copies data)Manual (set mode)
shell: mv❌ (unless guarded)Fast✅

Troubleshooting

IssueFix
"changed" every runAdd creates: and removes: to command
Permission deniedUse become: true
"No such file"Check with stat first, use when
Cross-filesystem move failsmv handles this (falls back to copy+delete)
Want to rename symlink, not targetUse -T flag: mv -T

Best Practices

  1. Use creates + removes with command module for idempotency
  2. Check with stat first for complex conditions
  3. Prefer mv over copy+delete — atomic on same filesystem
  4. Use block for multi-step renames — keeps logic together
  5. Log what was renamed — register output for debugging
  6. Test in check mode — command is skipped in check mode; use copy+file if 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.