Introduction

The ansible.builtin.user module manages user accounts on Linux — create users, set passwords, configure groups, manage SSH keys, set shells, and remove accounts. It wraps useradd, usermod, and userdel with idempotent, cross-platform behavior.

Parameters

ParameterDefaultDescription
name(required)Username
statepresentpresent to create/modify, absent to remove
uid—User ID number
group—Primary group
groups—Secondary groups (comma-separated or list)
appendfalseAppend to groups (vs replace)
shell/bin/bashLogin shell
home/home/<name>Home directory path
create_hometrueCreate home directory
password—Hashed password (NOT plaintext!)
comment—GECOS field (full name/description)
systemfalseCreate system user (UID < 1000)
expires—Account expiry (epoch timestamp)
generate_ssh_keyfalseGenerate SSH keypair
ssh_key_bits4096SSH key size
ssh_key_typersaKey type (rsa, ed25519, ecdsa)
removefalseRemove home dir when state: absent
forcefalseForce removal even if logged in

Create a User

---
- name: Create users
  hosts: all
  become: true
  tasks:
    - name: Create basic user
      ansible.builtin.user:
        name: alice
        comment: "Alice Smith"
        shell: /bin/bash
        state: present

    - name: Create user with specific UID and groups
      ansible.builtin.user:
        name: bob
        uid: 1500
        group: developers
        groups: docker,sudo
        append: true
        comment: "Bob Jones"

    - name: Create system user (for services)
      ansible.builtin.user:
        name: myapp
        system: true
        shell: /usr/sbin/nologin
        home: /opt/myapp
        create_home: true
        comment: "MyApp Service Account"

Set Password

⚠️ Passwords must be hashed, not plaintext:

---
- name: Create user with password
  hosts: all
  become: true
  tasks:
    # Generate hash with: ansible all -m debug -a "msg={{ 'mypassword' | password_hash('sha512') }}"
    - name: Create user with hashed password
      ansible.builtin.user:
        name: alice
        password: "{{ 'SecurePass123!' | password_hash('sha512') }}"
        update_password: on_create  # Only set password when creating user

    # Using vault-encrypted variable
    - name: Create user with vault password
      ansible.builtin.user:
        name: bob
        password: "{{ vault_bob_password | password_hash('sha512', 'salt123') }}"
        update_password: always  # Update password every run

update_password Options

ValueBehavior
always (default)Set password every run (changes if different)
on_createOnly set password when user is first created

Manage Groups

---
- name: Group management
  hosts: all
  become: true
  tasks:
    # ⚠️ WITHOUT append — REPLACES all secondary groups
    - name: DANGEROUS — removes from all other groups
      ansible.builtin.user:
        name: alice
        groups: docker
        # append: false (default!)

    # ✅ WITH append — adds to existing groups
    - name: SAFE — add to docker group
      ansible.builtin.user:
        name: alice
        groups: docker
        append: true

    # Multiple groups
    - name: Add to multiple groups
      ansible.builtin.user:
        name: alice
        groups:
          - docker
          - sudo
          - developers
        append: true

    # Change primary group
    - name: Set primary group
      ansible.builtin.user:
        name: alice
        group: developers  # Primary group (not groups!)

Generate SSH Key

---
- name: Create user with SSH key
  hosts: all
  become: true
  tasks:
    - name: Create user and generate SSH key
      ansible.builtin.user:
        name: deploy
        generate_ssh_key: true
        ssh_key_type: ed25519
        ssh_key_comment: "deploy@{{ inventory_hostname }}"
        ssh_key_file: .ssh/id_ed25519
      register: user_result

    - name: Show public key
      ansible.builtin.debug:
        msg: "{{ user_result.ssh_public_key }}"

    # Deploy authorized_keys
    - name: Add authorized key
      ansible.posix.authorized_key:
        user: deploy
        key: "{{ lookup('file', '~/.ssh/deploy_key.pub') }}"
        state: present

Create Multiple Users

---
- name: Bulk user management
  hosts: all
  become: true
  vars:
    users:
      - name: alice
        comment: "Alice Smith"
        groups: [developers, docker]
        shell: /bin/bash
      - name: bob
        comment: "Bob Jones"
        groups: [developers, sudo]
        shell: /bin/bash
      - name: charlie
        comment: "Charlie Brown"
        groups: [dbadmins]
        shell: /bin/zsh
      - name: deploy
        comment: "Deploy Account"
        groups: [deployers]
        shell: /bin/bash
        generate_ssh_key: true

  tasks:
    - name: Create all users
      ansible.builtin.user:
        name: "{{ item.name }}"
        comment: "{{ item.comment }}"
        groups: "{{ item.groups }}"
        shell: "{{ item.shell | default('/bin/bash') }}"
        append: true
        generate_ssh_key: "{{ item.generate_ssh_key | default(false) }}"
        state: present
      loop: "{{ users }}"

    - name: Deploy SSH authorized keys
      ansible.posix.authorized_key:
        user: "{{ item.name }}"
        key: "{{ lookup('file', 'ssh_keys/' + item.name + '.pub') }}"
      loop: "{{ users }}"
      when: "'ssh_keys/' + item.name + '.pub' is file"
      ignore_errors: true

Delete a User

- name: Remove user account
  ansible.builtin.user:
    name: olduser
    state: absent
    remove: true   # Also remove home directory
    force: true    # Force even if user is logged in

Practical Patterns

Service Account for Application

---
- name: Set up application
  hosts: appservers
  become: true
  tasks:
    - name: Create app group
      ansible.builtin.group:
        name: myapp
        system: true

    - name: Create service account
      ansible.builtin.user:
        name: myapp
        group: myapp
        system: true
        shell: /usr/sbin/nologin
        home: /opt/myapp
        create_home: true
        comment: "MyApp Service"

    - name: Set directory permissions
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: myapp
        group: myapp
        mode: "0755"
      loop:
        - /opt/myapp
        - /opt/myapp/data
        - /var/log/myapp

Lock/Unlock User

# Lock account (disable login)
- ansible.builtin.user:
    name: alice
    password_lock: true

# Unlock account
- ansible.builtin.user:
    name: alice
    password_lock: false

# Set account expiry
- ansible.builtin.user:
    name: contractor
    expires: "{{ ('2026-12-31' | to_datetime).strftime('%s') }}"

Change Shell

- name: Set zsh for developers
  ansible.builtin.user:
    name: "{{ item }}"
    shell: /bin/zsh
  loop: "{{ developers }}"

Troubleshooting

"useradd: group 'mygroup' does not exist"

Create the group first:

- ansible.builtin.group:
    name: mygroup
    state: present

- ansible.builtin.user:
    name: alice
    group: mygroup

Password Not Working

Passwords must be hashed. Plaintext won't work:

# WRONG — plaintext password
password: "mypassword"

# RIGHT — hashed password
password: "{{ 'mypassword' | password_hash('sha512') }}"

User Removed from Groups

You forgot append: true:

# This REMOVES alice from all groups except docker
- ansible.builtin.user:
    name: alice
    groups: docker

# This ADDS docker to alice's existing groups
- ansible.builtin.user:
    name: alice
    groups: docker
    append: true  # ← Critical!

Conclusion

ansible.builtin.user handles full lifecycle user management — creation, password hashing, group membership, SSH keys, and deletion. The most common mistake: forgetting append: true when adding groups (without it, all other group memberships are removed). Use update_password: on_create to avoid resetting passwords on every run. For service accounts, combine system: true with shell: /usr/sbin/nologin for security.