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.

See also

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home