AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible for Linux System Administration: crontab, mounts, users, sudoers

By Luca Berton · Published 2024-01-01 · Category: installation

Complete guide to Ansible for Linux system administration. Manage crontab jobs, filesystem mounts, user accounts, groups, sudoers rules, and SSH keys.

Every Linux sysadmin runs the same tasks: manage users, set up cron jobs, mount filesystems, configure sudo. Here's how to automate all of them with Ansible — idempotently.

Managing Users

Create a User

- name: Create application user
  ansible.builtin.user:
    name: deploy
    comment: "Deployment user"
    groups:
      - wheel
      - docker
    shell: /bin/bash
    create_home: true
    home: /home/deploy
    state: present

Create Multiple Users

- name: Create team accounts
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups | default(omit) }}"
    shell: "{{ item.shell | default('/bin/bash') }}"
    state: present
  loop:
    - { name: alice, groups: ['developers', 'docker'] }
    - { name: bob, groups: ['developers'] }
    - { name: charlie, groups: ['developers', 'wheel'] }

- name: Deploy SSH keys for team ansible.posix.authorized_key: user: "{{ item.name }}" key: "{{ item.key }}" state: present exclusive: false loop: - { name: alice, key: "ssh-ed25519 AAAA... alice@company" } - { name: bob, key: "ssh-ed25519 AAAA... bob@company" } - { name: charlie, key: "ssh-ed25519 AAAA... charlie@company" }

Remove a User

- name: Remove former employee
  ansible.builtin.user:
    name: ex_employee
    state: absent
    remove: true    # Also removes home directory
    force: true     # Remove even if user is logged in

Set Password

- name: Set user password
  ansible.builtin.user:
    name: deploy
    password: "{{ 'SecurePassword123!' | password_hash('sha512', 'salt') }}"
    update_password: on_create    # Only set on creation, not every run
  no_log: true

Manage Groups

- name: Create application groups
  ansible.builtin.group:
    name: "{{ item }}"
    state: present
  loop:
    - developers
    - operations
    - dbadmins

- name: Add user to supplementary groups ansible.builtin.user: name: deploy groups: - developers - docker append: true # Don't remove existing groups

See also: Ansible Mount Module: Mount NFS, CIFS & ansible.posix.mount Guide

Managing Crontab

Add a Cron Job

- name: Schedule daily backup at 2 AM
  ansible.builtin.cron:
    name: "daily database backup"
    minute: "0"
    hour: "2"
    job: "/opt/scripts/backup-db.sh >> /var/log/backup.log 2>&1"
    user: postgres

Cron with Special Time

- name: Run at reboot
  ansible.builtin.cron:
    name: "start application on boot"
    special_time: reboot
    job: "/opt/app/bin/start.sh"
    user: deploy

# Other special_time values: hourly, daily, weekly, monthly, annually

Add Cron Environment Variable

- name: Set cron PATH
  ansible.builtin.cron:
    name: PATH
    env: true
    job: "/usr/local/bin:/usr/bin:/bin"
    user: deploy

- name: Set cron MAILTO ansible.builtin.cron: name: MAILTO env: true job: "admin@example.com" user: deploy

Remove a Cron Job

- name: Remove old backup job
  ansible.builtin.cron:
    name: "old backup script"
    state: absent
    user: postgres

Complete Cron Management

- name: Configure all cron jobs
  ansible.builtin.cron:
    name: "{{ item.name }}"
    minute: "{{ item.minute | default('*') }}"
    hour: "{{ item.hour | default('*') }}"
    day: "{{ item.day | default('*') }}"
    month: "{{ item.month | default('*') }}"
    weekday: "{{ item.weekday | default('*') }}"
    job: "{{ item.job }}"
    user: "{{ item.user | default('root') }}"
    state: present
  loop:
    - { name: "DB backup", hour: "2", minute: "0", job: "/opt/scripts/backup.sh", user: postgres }
    - { name: "Log rotation", hour: "3", minute: "30", job: "/usr/sbin/logrotate /etc/logrotate.conf" }
    - { name: "SSL renewal", hour: "4", minute: "0", weekday: "1", job: "certbot renew --quiet" }
    - { name: "Disk cleanup", hour: "5", minute: "0", day: "1", job: "/opt/scripts/cleanup.sh" }

Managing Filesystem Mounts

Mount an NFS Share

- name: Install NFS client
  ansible.builtin.apt:
    name: nfs-common
    state: present

- name: Mount NFS share ansible.posix.mount: src: "nfs-server:/exports/data" path: /mnt/data fstype: nfs opts: "rw,sync,hard,intr" state: mounted # Mounts AND adds to fstab

Mount Options Explained

# state: mounted — mount now AND add to fstab
# state: present — add to fstab only (don't mount now)
# state: unmounted — unmount but keep in fstab
# state: absent — unmount AND remove from fstab
# state: remounted — remount (apply new options)

Mount a New Disk

- name: Create filesystem
  community.general.filesystem:
    fstype: ext4
    dev: /dev/sdb1

- name: Mount data volume ansible.posix.mount: src: /dev/sdb1 path: /data fstype: ext4 opts: "defaults,noatime" state: mounted

- name: Set ownership ansible.builtin.file: path: /data owner: deploy group: deploy mode: '0755'

Mount tmpfs

- name: Mount tmpfs for application cache
  ansible.posix.mount:
    src: tmpfs
    path: /opt/app/cache
    fstype: tmpfs
    opts: "size=512M,mode=0755,uid=deploy,gid=deploy"
    state: mounted

See also: Ansible mount Module: Manage Filesystem Mounts and fstab (Complete Guide)

Managing Sudoers

Add Sudoers Rule (Safe Method)

# ALWAYS use /etc/sudoers.d/ — never edit /etc/sudoers directly
- name: Grant deploy user sudo for service management
  ansible.builtin.copy:
    content: |
      # Managed by Ansible
      deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp
    dest: /etc/sudoers.d/deploy
    mode: '0440'
    owner: root
    group: root
    validate: 'visudo -cf %s'    # Validate before writing!

Group-Based Sudo

- name: Grant wheel group full sudo
  ansible.builtin.copy:
    content: |
      # Managed by Ansible
      %wheel ALL=(ALL) NOPASSWD: ALL
    dest: /etc/sudoers.d/wheel
    mode: '0440'
    validate: 'visudo -cf %s'

- name: Grant developers limited sudo ansible.builtin.copy: content: | # Managed by Ansible %developers ALL=(deploy) NOPASSWD: /opt/app/bin/deploy.sh %developers ALL=(root) NOPASSWD: /usr/bin/journalctl -u myapp* dest: /etc/sudoers.d/developers mode: '0440' validate: 'visudo -cf %s'

Remove Sudoers Rule

- name: Remove old sudoers rule
  ansible.builtin.file:
    path: /etc/sudoers.d/old_rule
    state: absent

Complete Server Setup Playbook

---
- name: Configure Linux server
  hosts: all
  become: true
  vars:
    admin_users:
      - { name: alice, key: "ssh-ed25519 AAAA... alice" }
      - { name: bob, key: "ssh-ed25519 AAAA... bob" }
    app_user: deploy
    data_mount: /data

tasks: # Users - name: Create admin users ansible.builtin.user: name: "{{ item.name }}" groups: [wheel] shell: /bin/bash loop: "{{ admin_users }}"

- name: Deploy SSH keys ansible.posix.authorized_key: user: "{{ item.name }}" key: "{{ item.key }}" loop: "{{ admin_users }}"

- name: Create application user ansible.builtin.user: name: "{{ app_user }}" system: true shell: /bin/bash

# Sudo - name: Configure sudoers ansible.builtin.copy: content: "{{ app_user }} ALL=(root) NOPASSWD: /usr/bin/systemctl * myapp\n" dest: "/etc/sudoers.d/{{ app_user }}" mode: '0440' validate: 'visudo -cf %s'

# Cron - name: Schedule backups ansible.builtin.cron: name: "daily backup" hour: "2" minute: "0" job: "/opt/scripts/backup.sh" user: "{{ app_user }}"

# Mounts - name: Mount data volume ansible.posix.mount: src: /dev/sdb1 path: "{{ data_mount }}" fstype: ext4 state: mounted when: ansible_devices.sdb is defined

See also: Ansible mount Module: Mount Filesystems, NFS, SMB/CIFS Shares (Guide)

FAQ

Should I manage /etc/sudoers directly or use sudoers.d?

Always use /etc/sudoers.d/ drop-in files. Never edit /etc/sudoers directly — a syntax error locks you out of sudo entirely. The validate: 'visudo -cf %s' parameter ensures Ansible checks syntax before writing.

How do I handle user password expiry?

Use ansible.builtin.user with password_expire_max, password_expire_min, and password_expire_warn parameters. For LDAP/AD-managed users, manage password policies in the directory, not locally.

What's the difference between mounted and present in the mount module?

mounted mounts the filesystem immediately AND adds it to /etc/fstab. present only adds to /etc/fstab without mounting — useful for volumes that should mount on next reboot but not now.

Conclusion

Ansible replaces ad-hoc useradd, crontab -e, manual fstab edits, and hand-written sudoers files with version-controlled, idempotent, auditable automation. Every example above is safe to run repeatedly — exactly what production infrastructure needs.

Related Articles

Ansible cron Module GuideAnsible file Module CookbookAnsible user Module GuideHow to Make Playbooks Idempotent

See also

Ansible mount Module: Manage Filesystem Mounts and fstab (Complete Guide)

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home