Managing users efficiently is a key part of automating system administration with Ansible. In this guide, you’ll learn how to create users, set passwords, add users to groups, and configure remote access using Ansible’s powerful tools.
We’ll cover:
The Ansible user
module is used to manage user accounts on Linux and UNIX-like operating systems on target systems. It can set user properties such as UID, home directory, login shell, and password hash. Ansible tasks are idempotent, so re-running the task will not create duplicate users.
Unlike using shell commands directly, the user
module handles edge cases and reports clearly whether a change was made. Compared to manual scripting, it integrates better with Ansible’s check mode (--check
) and handlers.
The Ansible user
module offers a wide set of options to control user account properties:
name
(required): Username to managestate
: Whether the user should exist (present
) or be removed (absent
)uid
: User ID to assign to the usergroups:
Primary and additional group names (use instead of deprecatedgroup
parameter)shell
: Login shell of the user (e.g.,/bin/bash
)home
: Home directory path for the userpassword
: Hashed password to set for the userssh_key_bits
: Number of bits for SSH key generation when generate_ssh_key is truessh_key_type
: Type of SSH key to generate (rsa
,ecdsa
, ored25519
)login_class
: (BSD) Login class to setseuser
: SELinux user mapping
Each platform (Linux, BSD, Solaris) might slightly affect behavior for less common options like login_class
or seuser
, so you should check the module documentation when targeting different systems.
The two primary methods for adding a user in Ansible are:
- Using the
user
module – Theansible.builtin.user
module is the standard, recommended way to create and manage users directly. - Using raw or shell/command modules – Alternatively, you can manually run user creation commands (
useradd
,adduser
) via theansible.builtin.shell
,ansible.builtin.command
, oransible.builtin.raw
modules. This is strongly discouraged for production use because it bypasses idempotency and risks race conditions or duplication.
Method 1: Using the user module in Ansible playbooks
To add a user in Ansible, you use the user
module within a playbook or ad-hoc command.
The user
module manages user accounts on target systems. Depending on the state
parameter, it can create, modify, or remove users. You can also set options like home directory, shell, UID, and encrypted passwords.
Before adding a user to a group, ensure the group exists. Otherwise, the task may fail, depending on the system’s behavior. For example:
- name: Add a new user
ansible.builtin.user:
name: "newuser"
state: present
shell: /bin/bash
groups: "sudo"
create_home: yes
This task ensures that newuser
exists, has a home directory, uses /bin/bash
, and belongs to the sudo
group.
Note: If you need to set a password, it must be pre-hashed (e.g., using openssl passwd -6 'password'
or mkpasswd
with SHA-512
) and passed using the password parameter. We will cover this later in the article.
Method 2: Using ad-hoc Ansible commands
The user
module is the standard, idempotent way to manage users in Ansible. However, bypassing it using shell or command modules risks race conditions and duplication. If unavoidable, always wrap shell usage with creates
or proper error checking.
To add a user in Ansible with an ad-hoc command without using the user
module, you can use the ansible.builtin.shell
or ansible.builtin.command
module to directly execute a system command like useradd
.
Using shell
allows execution of more complex commands, while command
is safer but more limited (no pipes, redirection, etc.).
ansible all -m shell -a "useradd newusername" -b
If you want to specify additional options, such as setting a home directory or shell, you can extend the command:
ansible all -m shell -a "useradd -m -s /bin/bash newusername" -b
For a safer experience without the user
module, always check if the user exists first:
ansible all -m shell -a "id -u newusername || useradd -m -s /bin/bash newusername" -b
Always check if the user exists first, or use the creates
argument to prevent issues.
To add a user to a group in Ansible, you can use the user
module with the groups
parameter. Setting append: yes
ensures the user is added to the group without altering their other memberships.
Without append: yes
, specifying groups replaces all existing secondary groups for the user, which can cause unintended permission changes.
Example 1: Adding a single user
In the example below, we add johndoe
to the developers
group while keeping their existing groups.
- name: Add user to a group
ansible.builtin.user:
name: johndoe
groups: developers
append: yes
If the group does not exist yet, you should first create it using the ansible.builtin.group
module to prevent failure.
Example 2: Adding multiple users to multiple groups
You can add multiple users to multiple groups in Ansible by looping over users and groups, using the user
module with append: yes
. When adding users to groups, using with_items
(for older Ansible versions) or loop
(current standard) is the most efficient way. (Read more: Ansible Loops : How To Use, Tutorial & Examples)
In this example, johndoe
joins developers
, janedoe
joins admins
, and alice
joins testers
:
- name: Add multiple users to multiple groups
hosts: all
become: yes
tasks:
- name: Ensure users are in the right groups
ansible.builtin.user:
name: "{{ item.user }}"
groups: "{{ item.group }}"
append: yes
loop:
- { user: 'johndoe', group: 'developers' }
- { user: 'janedoe', group: 'admins' }
- { user: 'alice', group: 'testers' }
On the other hand, if you need to assign multiple groups to one user, you can list groups as a comma-separated string:
groups: "developers,admins"
When assigning multiple groups, Linux expects a comma-separated string (groups: "developers,admins"
), but some UNIX variants might differ slightly. Always verify platform behavior if in doubt.
All three primary methods of adding a password to a user in Ansible use the Ansible user
module to actually create or modify the user with a password:
- Pre-hash the password manually (with
mkpasswd
). - Hash it dynamically inside Ansible (using
password_hash
filter). - Retrieve it securely (with Vault), possibly hashing it at runtime.
The user
module never directly accepts plaintext passwords. It always expects a properly hashed password string. Thus, while mkpasswd
, Jinja2 filters, and Vault help prepare the password securely, the actual user creation or password assignment is always handled by the user
module itself.
Method 1: Using mkpasswd
You generate a hashed password outside of Ansible using a tool like mkpasswd
(part of whois
package) or openssl passwd
, and then pass that hashed value in your Ansible playbook. This method is simple and highly secure if you trust your manual handling of the hash.
Install mkpasswd:
- Debian/Ubuntu:
sudo apt install whois
- RHEL/CentOS:
sudo yum install expect
Now generate the hashed password:
mkpasswd --method=SHA-512
Example output:
$6$rounds=656000$Xf8DqC1dkvnyc7...
Then use it in a playbook:
- name: Create user with pre-hashed password
hosts: all
become: true
tasks:
- name: Create user with pre-hashed password
ansible.builtin.user:
name: myuser
password: "$6$rounds=656000$Xf8DqC1dkvnyc7...."
Method 2: Hashing passwords dynamically in playbooks
You can use Jinja2 filters like password_hash
within Ansible to generate the hash during playbook execution. The password_hash
Jinja2 filter tells Ansible to hash the plaintext password using the sha512
algorithm during playbook execution.
Note: Hashing inside playbooks exposes plaintext passwords in memory and logs if not handled carefully. Avoid in production unless absolutely necessary.
For example:
- name: Create user with dynamically hashed password
hosts: all
become: true
vars:
plain_password: "MySecurePassword123"
tasks:
- name: Create user with password hashed in playbook
ansible.builtin.user:
name: myuser
password: "{{ plain_password | password_hash('sha512') }}"
Alternatively, you can use the community.general.password_hash
plugin for enhanced flexibility across environments.
Method 3: Using Ansible playbooks with Vault
You encrypt sensitive data, such as plaintext passwords, with Ansible Vault and then hash it at runtime or use pre-hashed values.
Here, the password is never visible in plaintext form inside the playbook. It stays encrypted at rest and only becomes available during playbook execution. This method combines secure storage (Vault) with dynamic hashing, offering maximum security for production environments.
Vault-encrypt your password:
ansible-vault encrypt_string 'MySecurePassword123' --name 'vault_password'
Then in your playbook:
- name: Create user with password from Vault
hosts: all
become: true
vars:
vault_password: !vault |
$ANSIBLE_VAULT;1.1;AES256;...(vault encrypted password)
tasks:
- name: Create user with password from Vault, hashed
ansible.builtin.user:
name: myuser
password: "{{ vault_password | password_hash('sha512') }}"
Creating a user in Ansible is often part of configuring remote access. You typically create a dedicated SSH user with specific permissions and configure Ansible to connect using that user for subsequent tasks.
Ansible connects to managed hosts using SSH by default. Remote access must be set up so that Ansible can log into each host non-interactively, typically using SSH keys. The configuration is defined globally in ansible.cfg
, per playbook, or dynamically in inventory files.
Key parameters include:
ansible_user
: The username to log into the remote machine.ansible_ssh_private_key_file
: The private key file for authentication.ansible_port
: If a non-standard SSH port is used.ansible_host
: If the target hostname differs from the inventory name.ansible_connection
: Set tossh
(lowercase). Use paramiko only if OpenSSH is unavailable. OpenSSH is now preferred for performance and security.
Example in an inventory file:
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa
You can globally configure defaults in ansible.cfg
under the [defaults]
and [ssh_connection]<
/span> sections.
lookup('file', ...)
reads a local file on the control machine, not on the target hosts. It will fail if the file does not exist or the control machine lacks access. If needed, prefer the copy
module with content
for greater portability.
Playbook example to create a user and SSH access:
- name: Create ansible user
hosts: all
become: true
tasks:
- name: Create a new user
user:
name: ansible
groups: sudo
shell: /bin/bash
state: present
- name: Add authorized key
authorized_key:
user: ansible
state: present
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
After this, update your inventory to use ansible_user=ansible
.
Ansible’s user
module allows declarative user management by specifying user attributes like name, UID, shell, and password. Ensuring user creation is idempotent (running multiple times without side effects) and secure is critical, especially in production environments.
Key best practices involve:
- Avoid hardcoded credentials by leveraging Ansible Vault to encrypt passwords and sensitive data.
- Use unique UIDs when necessary, especially in large environments, to avoid conflicts.
- Specify system users (
system: yes
) when creating service accounts to distinguish them from regular users. - Ensure password hashing is done properly, typically by generating hashes externally (e.g., using
mkpasswd
) rather than plain-text input. - Set default shells and home directory permissions explicitly to maintain consistency across different operating systems.
- Use different SSH keys per user whenever possible to enhance security.
- Set
/usr/sbin/nologin
as shell for system users who should not have interactive access.
Spacelift’s vibrant ecosystem and excellent GitOps flow are helpful for managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization.
Another advantage of using Spacelift is that you can manage infrastructure tools like Ansible, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their stacks with building workflows across tools.
Our latest Ansible enhancements solve three of the biggest challenges engineers face when they are using Ansible:
- Having a centralized place in which you can run your playbooks
- Combining IaC with configuration management to create a single workflow
- Getting insights into what ran and where
Provisioning, configuring, governing, and even orchestrating your containers can be performed with a single workflow, separating the elements into smaller chunks to identify issues more easily.
Would you like to see this in action, or just get a tl;dr? Check out this video showing you Spacelift’s Ansible functionality:
If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.
Ansible’s user
module allows for efficient, idempotent management of Linux and UNIX user accounts, enabling creation, modification, password assignment, and group management declaratively.
Secure password handling requires hashing passwords beforehand or securely using Ansible Vault, whereas SSH-based remote access setup ensures smooth automation. Following best practices like avoiding plaintext passwords, using unique UIDs, and applying strict SSH controls strengthens security and consistency across environments.
Manage Ansible better with Spacelift
Managing large-scale playbook execution is hard. Spacelift enables you to automate Ansible playbook execution with visibility and control over resources, and seamlessly link provisioning and configuration workflows.