Introduction
Managing user accounts is one of the most fundamental tasks in Linux system administration. The Ansible ansible.builtin.user module provides a declarative, idempotent way to create, modify, and remove user accounts across your entire infrastructure. This article covers everything from basic user creation to advanced scenarios including password hashing, SSH key generation, group management, and account expiration.
Module Overview
The ansible.builtin.user module is a builtin module shipped with Ansible core. It manages user accounts on Linux, macOS, FreeBSD, and SunOS systems.
Under the hood it uses:
- Linux:
useradd,usermod,userdel - FreeBSD:
pw useradd - macOS:
dscl create
For Windows targets, use ansible.windows.win_user instead.
Parameters Reference
| Parameter | Type | Description |
|---|---|---|
name | string (required) | Username to create/manage |
state | string | present (default) or absent |
password | string | Hashed password (use password_hash filter) |
uid | integer | Numeric user ID |
group | string | Primary group |
groups | list | Additional groups |
append | boolean | Append to groups (true) or replace (false, default) |
shell | string | Login shell (e.g., /bin/bash) |
home | string | Home directory path |
create_home | boolean | Create home directory (true default) |
comment | string | GECOS field / user description |
system | boolean | Create a system account |
expires | float | Account expiry (epoch time, -1 to remove) |
password_expire_min | integer | Minimum days between password changes |
password_expire_max | integer | Maximum days before password must change |
generate_ssh_key | boolean | Generate SSH keypair |
ssh_key_bits | integer | SSH key size (default: 4096) |
ssh_key_file | string | SSH key file path |
ssh_key_type | string | SSH key type (rsa, ed25519, etc.) |
ssh_key_passphrase | string | Passphrase for SSH key |
remove | boolean | Remove home dir when state: absent |
force | boolean | Force removal even if user is logged in |
move_home | boolean | Move home dir when home changes |
skeleton | string | Skeleton directory for home |
password_lock | boolean | Lock the password |
Basic User Creation
The simplest example — create a user with default settings:
---
- name: Create basic user
hosts: all
become: true
tasks:
- name: Create user 'deploy'
ansible.builtin.user:
name: deploy
state: present
This creates the user with a home directory at /home/deploy, the default shell, and no password.
Complete User Creation Playbook
A production-ready example with password, groups, SSH key, and shell:
---
- name: User management Playbook
hosts: all
become: true
tasks:
- name: Create user with full configuration
ansible.builtin.user:
name: example
password: "{{ 'MySecureP@ss!' | password_hash('sha512', 'mysecretsalt') }}"
groups:
- wheel
- adm
append: true
state: present
shell: /bin/bash
system: false
create_home: true
home: /home/example
comment: "Example application user"
generate_ssh_key: true
ssh_key_type: ed25519
ssh_key_bits: 4096
ssh_key_comment: "example@{{ inventory_hostname }}"
register: user_result
- name: Display SSH public key
ansible.builtin.debug:
msg: "{{ user_result.ssh_public_key }}"
when: user_result.ssh_public_key is defined
Password Hashing
Never store plaintext passwords in playbooks. Ansible requires pre-hashed passwords:
Method 1: Inline with password_hash Filter
password: "{{ 'plaintext_password' | password_hash('sha512') }}"
Method 2: With a Salt for Idempotency
Without a fixed salt, the hash changes on every run, causing unnecessary changed status:
password: "{{ 'plaintext_password' | password_hash('sha512', 'fixed_salt_value') }}"
Method 3: Using Ansible Vault
Store the password in an encrypted vault file:
# group_vars/all/vault.yml (encrypted)
vault_user_password: "MySecureP@ss!"
# playbook
- name: Create user with vaulted password
ansible.builtin.user:
name: deploy
password: "{{ vault_user_password | password_hash('sha512', 'salt') }}"
Method 4: Pre-generate the Hash
# Generate hash on command line
python3 -c "import crypt; print(crypt.crypt('MyPassword', crypt.mksalt(crypt.METHOD_SHA512)))"
# Or use mkpasswd
mkpasswd --method=sha-512
Group Management
Primary vs Additional Groups
Pay close attention to group (singular) vs groups (plural):
- name: Set primary and additional groups
ansible.builtin.user:
name: deploy
group: developers # Primary group (singular)
groups: # Additional groups (plural)
- docker
- sudo
append: true # Add to these groups, don't replace existing
Warning: Without append: true, Ansible removes the user from all groups not listed in groups.
Ensure User Is in Specific Group Only
- name: Reset user groups
ansible.builtin.user:
name: deploy
groups:
- developers
append: false # Remove from all other secondary groups
SSH Key Generation
Automatically generate SSH keypairs for service accounts:
- name: Create service account with SSH key
ansible.builtin.user:
name: ansible_svc
generate_ssh_key: true
ssh_key_type: ed25519
ssh_key_comment: "ansible-service-account"
ssh_key_file: .ssh/id_ed25519
register: svc_user
- name: Save public key for distribution
ansible.builtin.copy:
content: "{{ svc_user.ssh_public_key }}"
dest: /tmp/ansible_svc_pub_key
delegate_to: localhost
Account Expiration
Set Account Expiry Date
- name: Create temporary contractor account
ansible.builtin.user:
name: contractor01
expires: "{{ ('2026-12-31' | to_datetime).strftime('%s') }}"
comment: "Contractor - expires Dec 2026"
Set Password Expiry Policy
- name: Create user with password policy
ansible.builtin.user:
name: secure_user
password: "{{ password | password_hash('sha512', 'salt') }}"
password_expire_min: 7 # Min 7 days between changes
password_expire_max: 90 # Must change every 90 days
Remove Account Expiry
- name: Remove account expiry
ansible.builtin.user:
name: contractor01
expires: -1
System Accounts
Create system accounts for services (low UID, no login):
- name: Create application service account
ansible.builtin.user:
name: myapp
system: true
shell: /usr/sbin/nologin
home: /opt/myapp
create_home: true
comment: "MyApp service account"
Removing Users
- name: Remove user and home directory
ansible.builtin.user:
name: old_user
state: absent
remove: true # Also remove home directory
force: true # Remove even if user is logged in
Bulk User Management
Create multiple users from a variable list:
---
- name: Manage multiple users
hosts: all
become: true
vars:
users:
- name: alice
groups: [developers, docker]
shell: /bin/bash
- name: bob
groups: [developers]
shell: /bin/zsh
- name: charlie
groups: [ops, docker, sudo]
shell: /bin/bash
tasks:
- name: Create all users
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
append: true
state: present
create_home: true
loop: "{{ users }}"
- name: Set authorized keys
ansible.posix.authorized_key:
user: "{{ item.name }}"
key: "{{ lookup('file', 'keys/' + item.name + '.pub') }}"
state: present
loop: "{{ users }}"
when: lookup('file', 'keys/' + item.name + '.pub', errors='ignore')
Return Values
The user module returns useful information:
- name: Create user and capture result
ansible.builtin.user:
name: deploy
generate_ssh_key: true
register: result
- name: Show return values
ansible.builtin.debug:
var: result
Key return values:
result.name— Usernameresult.uid— Numeric UIDresult.group— Primary GIDresult.home— Home directory pathresult.shell— Login shellresult.ssh_key_file— Path to private keyresult.ssh_public_key— Public key contentresult.ssh_fingerprint— Key fingerprint
Common Mistakes
1. Forgetting append: true
# ❌ This REMOVES the user from all groups except 'docker'
groups: [docker]
# ✅ This ADDS 'docker' to existing groups
groups: [docker]
append: true
2. Plaintext Passwords
# ❌ Will NOT work - Ansible expects a hash
password: "MyPassword123"
# ✅ Correct - use password_hash
password: "{{ 'MyPassword123' | password_hash('sha512', 'salt') }}"
3. Missing become
# ❌ User creation requires root privileges
- hosts: all
tasks:
- ansible.builtin.user:
name: newuser
# ✅ Use become: true
- hosts: all
become: true
tasks:
- ansible.builtin.user:
name: newuser
Related Articles
- Ansible become Privilege Escalation
- Change User Password with Ansible
- Ansible Vault Guide
- Ansible group Module
Conclusion
The ansible.builtin.user module is essential for managing user accounts at scale. Key takeaways: always hash passwords with password_hash, use append: true when adding groups, generate SSH keys for service accounts, and leverage Ansible Vault for sensitive credentials. With proper automation, user management becomes consistent, auditable, and repeatable across your entire infrastructure.