How to Set Linux Password Policy with a Shell Script

Password policies are a fundamental component of system security, especially on enterprise-managed Linux devices. For Managed Service Providers (MSPs) and IT professionals overseeing fleets of Linux endpoints, enforcing consistent password policies is essential to reducing attack surfaces and ensuring regulatory compliance. This post explores a comprehensive shell script that configures critical password controls across various Linux distributions using authconfig, authselect, or manual PAM modifications. The ability to set the password policy for Linux devices with shell scripting adds automation and reliability to your endpoint management strategy.

Background

Many Linux environments, particularly those in enterprise or MSP-managed infrastructures, rely on decentralized systems, making it difficult to enforce uniform password requirements. Manual configuration is error-prone and inefficient at scale. While tools like Ansible and Puppet can help, they may be overkill for teams using RMM platforms like NinjaOne.

This shell script bridges the gap: it targets Debian 11+ and RHEL 8+ systems and dynamically applies password policies based on the availability of authconfig, authselect, or direct PAM configuration files. IT professionals can use shell scripting to set the password policy for Linux devices, ensuring better security hygiene without requiring heavy orchestration.

The Script

#!/usr/bin/env bash
#
# Description: Sets the password policy for linux devices.
# By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
# Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
# Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
# Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
# Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
# Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
# Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
# EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#
# Preset Parameter: --maxLoginAttempts "replaceMeWithANumber"
#		Define how many incorrect password attempts are allowed before the device locks.
#
# Preset Parameter: --loginAttemptLockTime "replaceMeWithANumber"
#		Set the lock duration (in minutes) after the maximum login attempts is reached. Max Login Attempts is required (if not previously set).
#
# Preset Parameter: --daysUntilPasswordExpiration "replaceMeWithANumber"
#		Specify how many days before a password expires.
#
# Preset Parameter: --minimumPasswordLength "replaceMeWithANumber"
#		Define the minimum number of characters required for a password.
#
# Preset Parameter: --passwordHistory "replaceMeWithANumber"
#		Define how many previous passwords must be remembered before reuse.
#   This option is only available on Debian-based or RHEL 8+-based distributions.
#   https://bugzilla.redhat.com/show_bug.cgi?id=1271804
#
# Preset Parameter: --help
#		Displays some help text.
#
# Minimum OS Architecture Supported: Debian 11 (Bullseye)+, Red Hat Enterprise Linux (RHEL) 8+
#
# Release Notes: Initial Release

# Initialize variables for various password policy parameters with default values
_arg_maxLoginAttempts=
_arg_loginAttemptLockTime=
_arg_daysUntilPasswordExpiration=
_arg_minimumPasswordLength=
_arg_passwordHistory=

print_help() {
  printf '\n\n%s\n\n' 'Usage: [--maxLoginAttempts|-a <arg>] [--loginAttemptLockTime|-t <arg>] [--daysUntilPasswordExpiration|-e <arg>] 
  [--minimumPasswordLength|-l <arg>] [--passwordHistory|-ph <arg>] [--help|-h]'
  printf '%s\n' 'Preset Parameter: --maxLoginAttempts "replaceMeWithANumber"'
  printf '\t%s\n' "Define how many incorrect password attempts are allowed before the device locks."
  printf '%s\n' 'Preset Parameter: --loginAttemptLockTime "replaceMeWithANumber"'
  printf '\t%s\n' "Set the lock duration (in minutes) after the maximum login attempts is reached. Max Login Attempts is required (if not previously set)."
  printf '%s\n' 'Preset Parameter: --daysUntilPasswordExpiration "replaceMeWithANumber"'
  printf '\t%s\n' "Specify how many days before a password expires."
  printf '%s\n' 'Preset Parameter: --minimumPasswordLength "replaceMeWithANumber"'
  printf '\t%s\n' "Define the minimum number of characters required for a password."
  printf '%s\n' 'Preset Parameter: --passwordHistory "replaceMeWithANumber"'
  printf '\t%s\n' "Define how many previous passwords must be remembered before reuse."
  printf '\t%s\n' "This option is only available on Debian-based or RHEL 8+-based distributions."
  printf '\t%s\n' "https://bugzilla.redhat.com/show_bug.cgi?id=1271804"
  printf '%s\n' 'Preset Parameter: --help'
  printf '\t%s\n' "Displays this help menu."
}

die() {
  local _ret="${2:-1}"
  echo "$1" >&2
  test "${_PRINT_HELP:-no}" = yes && print_help >&2
  exit "${_ret}"
}

parse_commandline() {
  while test $# -gt 0; do
    _key="$1"
    case "$_key" in
    --maxLoginAttempts | --maxloginattempts | --attempts | -a)
      test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
      _arg_maxLoginAttempts=$2
      shift
      ;;
    --maxLoginAttempts=*)
      _arg_maxLoginAttempts="${_key##--maxLoginAttempts=}"
      ;;
    --loginAttemptLockTime | --loginattemptlocktime | --locktime | -t)
      test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
      _arg_loginAttemptLockTime=$2
      shift
      ;;
    --loginAttemptLockTime=*)
      _arg_loginAttemptLockTime="${_key##--loginAttemptLockTime=}"
      ;;
    --daysUntilPasswordExpiration | --daysuntilpasswordexpiration | --expiration | -e)
      test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
      _arg_daysUntilPasswordExpiration=$2
      shift
      ;;
    --daysUntilPasswordExpiration=*)
      _arg_daysUntilPasswordExpiration="${_key##--daysUntilPasswordExpiration=}"
      ;;
    --minimumPasswordLength | --minimumpasswordlength | --length | -l)
      test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
      _arg_minimumPasswordLength=$2
      shift
      ;;
    --minimumPasswordLength=*)
      _arg_minimumPasswordLength="${_key##--minimumPasswordLength=}"
      ;;
    --passwordHistory | --passwordhistory | --history | -ph)
      test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
      _arg_passwordHistory=$2
      shift
      ;;
    --passwordHistory=*)
      _arg_passwordHistory="${_key##--passwordHistory=}"
      ;;
    --help | -h)
      _PRINT_HELP=yes die
      ;;
    *)
      _PRINT_HELP=yes die "[Error] Got an unexpected argument '$1'" 1
      ;;
    esac
    shift
  done
}

echo ""
parse_commandline "$@"

# If environment variables for the script parameters are set, override the command line argument values
if [[ -n "$maxLoginAttempts" ]]; then
  _arg_maxLoginAttempts="$maxLoginAttempts"
fi
if [[ -n "$loginAttemptLockTime" ]]; then
  _arg_loginAttemptLockTime="$loginAttemptLockTime"
fi
if [[ -n "$daysUntilPasswordExpiration" ]]; then
  _arg_daysUntilPasswordExpiration="$daysUntilPasswordExpiration"
fi
if [[ -n "$minimumPasswordLength" ]]; then
  _arg_minimumPasswordLength="$minimumPasswordLength"
fi
if [[ -n "$passwordHistory" ]]; then
  _arg_passwordHistory="$passwordHistory"
fi

# Check if the script is being run as root. If not, exit with an error message.
if [[ $(id -u) -ne 0 ]]; then
  _PRINT_HELP=no die "[Error] This script must be run with root permissions. Try running it with sudo or as the system/root user." 1
fi

# Validate max login attempts. Ensure it is a positive integer greater than zero.
if [[ -n "$_arg_maxLoginAttempts" ]]; then
  _arg_maxLoginAttempts=$(echo "$_arg_maxLoginAttempts" | xargs)

  if [[ -z "$_arg_maxLoginAttempts" ]]; then
    _PRINT_HELP=yes die "[Error] An invalid number of max login attempts was given. Please specify a positive whole number that is greater than 0." 1
  fi
fi
if [[ "$_arg_maxLoginAttempts" =~ [^0-9] ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for max login attempts was given: '$_arg_maxLoginAttempts'. Please specify a positive whole number that is greater than 0." 1
fi
if [[ "$_arg_maxLoginAttempts" == 0 ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for max login attempts was given: '$_arg_maxLoginAttempts'. Please specify a positive whole number that is greater than 0." 1
fi

# Validate login attempt lock time. Ensure it is a positive integer greater than zero.
if [[ -n "$_arg_loginAttemptLockTime" ]]; then
  _arg_loginAttemptLockTime=$(echo "$_arg_loginAttemptLockTime" | xargs)

  if [[ -z "$_arg_loginAttemptLockTime" ]]; then
    _PRINT_HELP=yes die "[Error] An invalid lock time was given. Please specify a positive whole number that is greater than 0." 1
  fi
fi
if [[ "$_arg_loginAttemptLockTime" =~ [^0-9] ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for lock time was given: '$_arg_loginAttemptLockTime'. Please specify a positive whole number that is greater than 0." 1
fi
if [[ "$_arg_loginAttemptLockTime" == 0 ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for lock time was given: '$_arg_loginAttemptLockTime'. Please specify a positive whole number that is greater than 0." 1
fi

# Validate password expiration time. Ensure it is a positive whole number or 99999 for no expiration.
if [[ -n "$_arg_daysUntilPasswordExpiration" ]]; then
  _arg_daysUntilPasswordExpiration=$(echo "$_arg_daysUntilPasswordExpiration" | xargs)

  if [[ -z "$_arg_daysUntilPasswordExpiration" ]]; then
    _PRINT_HELP=yes die "[Error] An invalid expiration time was given. Please specify a positive whole number or '99999' for no expiration." 1
  fi

  if [[ "$_arg_daysUntilPasswordExpiration" =~ [^0-9] ]]; then
    _PRINT_HELP=yes die "[Error] An invalid value for password expiration was given: '$_arg_daysUntilPasswordExpiration'. Please specify a positive whole number that is greater than 0 and less than or equal to 99999." 1
  fi

  if [[ "$_arg_daysUntilPasswordExpiration" == 0 || "$_arg_daysUntilPasswordExpiration" -gt 99999 ]]; then
    _PRINT_HELP=yes die "[Error] An invalid value for password expiration was given: '$_arg_daysUntilPasswordExpiration'. Please specify a positive whole number that is greater than 0 and less than or equal to 99999." 1
  fi
fi

# Validate minimum password length. Ensure it is a positive whole number greater than or equal to 8.
if [[ -n "$_arg_minimumPasswordLength" ]]; then
  _arg_minimumPasswordLength=$(echo "$_arg_minimumPasswordLength" | xargs)

  if [[ -z "$_arg_minimumPasswordLength" ]]; then
    _PRINT_HELP=yes die "[Error] An invalid minimum password length was given. Please specify a positive whole number that is greater than or equal to '8'." 1
  fi
fi
if [[ "$_arg_minimumPasswordLength" =~ [^0-9] ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for minimum password length was given: '$_arg_minimumPasswordLength'. Please specify a positive whole number that is greater than or equal to 8." 1
fi
if [[ -n "$_arg_minimumPasswordLength" && "$_arg_minimumPasswordLength" -lt 8 ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for minimum password length was given: '$_arg_minimumPasswordLength'. Please specify a positive whole number that is greater than or equal to 8." 1
fi

# Validate password history setting. Ensure it is a positive whole number greater than zero.
if [[ -n "$_arg_passwordHistory" ]]; then
  _arg_passwordHistory=$(echo "$_arg_passwordHistory" | xargs)

  if [[ -z "$_arg_passwordHistory" ]]; then
    _PRINT_HELP=yes die "[Error] An invalid number of passwords to remember was given. Please specify a positive whole number that is greater than 0." 1
  fi
fi
if [[ "$_arg_passwordHistory" =~ [^0-9] ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for the number of passwords to remember was given: '$_arg_passwordHistory'. Please specify a positive whole number that is greater than 0." 1
fi
if [[ "$_arg_passwordHistory" == 0 ]]; then
  _PRINT_HELP=yes die "[Error] An invalid value for the number of passwords to remember was given: '$_arg_passwordHistory'. Please specify a positive whole number that is greater than 0." 1
fi

today=$(date "+%s")

# Ensure that at least one password policy is being set. If none are provided and the reset flag is off, throw an error.
if [[ -z "$_arg_maxLoginAttempts" && -z "$_arg_loginAttemptLockTime" && -z "$_arg_daysUntilPasswordExpiration" && -z "$_arg_minimumPasswordLength" && -z "$_arg_passwordHistory" ]]; then
  _PRINT_HELP=yes die "[Error] You must specify the password policy you are trying to set." 1
fi

# Check if authselect and authconfig commands are available
authselectAvailable=$(command -v authselect)
authconfigAvailable=$(command -v authconfig)

# Proceed only if authconfig is available but authselect is not
if [[ -n "$authconfigAvailable" && -z "$authselectAvailable" ]]; then
  echo "Detected the command authconfig. Setting the password policy using authconfig."

  # If maximum login attempts or login attempt lock time arguments are provided
  if [[ -n "$_arg_maxLoginAttempts" || -n "$_arg_loginAttemptLockTime" ]]; then
    echo ""
    echo "Checking the current status of the login attempt lock and lock time."

    # Check current status of faillock settings
    failLockStatus=$(authconfig --test | grep "pam_faillock" | grep -v "disabled")

    # Extract current maximum login attempts and lock time values
    currentAttempts=$(authconfig --test | grep "pam_faillock" | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)
    currentLockTime=$(authconfig --test | grep "pam_faillock" | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)

    # Convert lock time from seconds to minutes if available
    if [[ -n "$currentLockTime" ]]; then
      currentLockTimeMinutes=$((currentLockTime / 60))
    fi

    # If login attempt lock time is provided, convert to seconds
    if [[ -n "$_arg_loginAttemptLockTime" ]]; then
      loginAttemptLockTimeMinutes="$_arg_loginAttemptLockTime"
      _arg_loginAttemptLockTime=$((_arg_loginAttemptLockTime * 60))
    fi

    echo "Successfully retrieved the current lock status and lock time."

    # If faillock module is not enabled, enable it
    if [[ -z "$failLockStatus" ]]; then
      echo "Enabling faillock PAM module."
      if ! authconfig --enablefaillock --update; then
        _PRINT_HELP=no die "[Error] Failed to enable faillock. This is required to set the max login attempts allowed as well as the lock time." 1
      fi

      # Verify that faillock has been enabled successfully
      failLockStatus=$(authconfig --test | grep "pam_faillock" | grep -v "disabled")
      if [[ -z "$failLockStatus" ]]; then
        _PRINT_HELP=no die "[Error] Failed to enable faillock. This is required to set the max login attempts allowed as well as the lock time." 1
      fi
    fi

    # Check if max login attempts value is already set correctly
    if [[ -n "$_arg_maxLoginAttempts" && -n "$currentAttempts" && "$_arg_maxLoginAttempts" == "$currentAttempts" ]]; then
      echo "The maximum login attempts is already set to '$_arg_maxLoginAttempts'. Skipping."
      _arg_maxLoginAttempts=
    fi

    # Check if lock time value is already set correctly
    if [[ -n "$_arg_loginAttemptLockTime" && -n "$currentLockTime" && "$_arg_loginAttemptLockTime" == "$currentLockTime" ]]; then
      echo "The lock time is already set to '$loginAttemptLockTimeMinutes' minutes. Skipping."
      _arg_loginAttemptLockTime=
    fi
  fi

  # Exit with error if only max login attempts or lock time is provided without the other
  if [[ -n "$_arg_maxLoginAttempts" && -z "$_arg_loginAttemptLockTime" && -z "$currentLockTime" ]]; then
    _PRINT_HELP=yes die "[Error] When specifying the maximum number of login attempts, you must also specify the login attempt lock time." 1
  fi

  if [[ -n "$_arg_loginAttemptLockTime" && -z "$_arg_maxLoginAttempts" && -z "$currentAttempts" ]]; then
    _PRINT_HELP=yes die "[Error] When specifying a login attempt lock time you must also specify the maximum login attempts." 1
  fi

  # If both maximum login attempts and lock time arguments are provided, update authconfig policy
  if [[ -n "$_arg_maxLoginAttempts" && -n "$_arg_loginAttemptLockTime" ]]; then
    echo "Setting the maximum login attempts and lock time in the authconfig policy."

    # Update authconfig with max login attempts and lock time
    if ! authconfig --faillockargs="deny=$_arg_maxLoginAttempts unlock_time=$_arg_loginAttemptLockTime" --update; then
      _PRINT_HELP=no die "[Error] Failed to set the maximum login attempts and lock time in the authconfig policy." 1
    fi

    # Verify the updated values
    newAttempts=$(authconfig --test | grep "pam_faillock" | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)
    newLockTime=$(authconfig --test | grep "pam_faillock" | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)

    if [[ -n "$newAttempts" && "$newAttempts" == "$_arg_maxLoginAttempts" ]]; then
      echo "Successfully added the max login attempts to the authconfig policy."
    else
      _PRINT_HELP=no die "[Error] Failed to add the max login attempts to the authconfig policy." 1
    fi

    if [[ -n "$newLockTime" && "$newLockTime" == "$_arg_loginAttemptLockTime" ]]; then
      echo "Successfully set the lock time in the authconfig policy."
    else
      _PRINT_HELP=no die "[Error] Failed to set the lock time in the authconfig policy." 1
    fi
  fi

  # Update only maximum login attempts if lock time is not provided
  if [[ -n "$_arg_maxLoginAttempts" && -z "$_arg_loginAttemptLockTime" ]]; then
    echo "Modifying the maximum login attempts from '$currentAttempts' to '$_arg_maxLoginAttempts' in the authconfig policy."

    # Update max login attempts in authconfig
    if ! authconfig --faillockargs="deny=$_arg_maxLoginAttempts unlock_time=$currentLockTime" --update; then
      _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the authconfig policy." 1
    fi

    # Verify the new maximum login attempts value
    newAttempts=$(authconfig --test | grep "pam_faillock" | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)

    if [[ -n "$newAttempts" && "$newAttempts" == "$_arg_maxLoginAttempts" ]]; then
      echo "Successfully modified the max login attempts in the authconfig policy."
    else
      _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the authconfig policy." 1
    fi
  fi

  # Update only lock time if max login attempts is not provided
  if [[ -n "$_arg_loginAttemptLockTime" && -z "$_arg_maxLoginAttempts" ]]; then
    echo "Modifying the current lock time from '$currentLockTimeMinutes' minutes to '$loginAttemptLockTimeMinutes' minutes in the authconfig policy."

    # Update lock time in authconfig
    if ! authconfig --faillockargs="deny=$currentAttempts unlock_time=$_arg_loginAttemptLockTime" --update; then
      _PRINT_HELP=no die "[Error] Failed to modify the lock time in the authconfig policy." 1
    fi

    # Verify the new lock time value
    newLockTime=$(authconfig --test | grep "pam_faillock" | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)

    if [[ -n "$newLockTime" && "$newLockTime" == "$_arg_loginAttemptLockTime" ]]; then
      echo "Successfully modified the lock time of the authconfig policy."
    else
      _PRINT_HELP=no die "[Error] Failed to set the lock time in the authconfig policy." 1
    fi
  fi

  # If minimum password length is specified, retrieve and update the policy
  if [[ -n "$_arg_minimumPasswordLength" ]]; then
    echo ""
    echo "Retrieving the current password quality policy."

    # Check if the password quality configuration file exists
    if [[ ! -f "/etc/security/pwquality.conf" ]]; then
      _PRINT_HELP=no die "[Error] The file '/etc/security/pwquality.conf' does not exist. Unable to read the current password quality policy." 1
    fi

    # Get the current minimum password length value
    currentMinimumLength=$(grep -v "^#" /etc/security/pwquality.conf | grep -v '^\s*$' | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)
    echo "Successfully retrieved the current password quality policy."

    # If the minimum password length is already set correctly, skip the update
    if [[ -n "$currentMinimumLength" && "$currentMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
      echo "The minimum password length is already '$_arg_minimumPasswordLength'. Skipping."
      _arg_minimumPasswordLength=
    fi
  fi

  # Set or update the minimum password length in the policy
  if [[ -n "$_arg_minimumPasswordLength" ]]; then
    if [[ -n "$currentMinimumLength" ]]; then
      echo "Updating the minimum password length from '$currentMinimumLength' to '$_arg_minimumPasswordLength'."
    else
      echo "Setting the minimum password length to '$_arg_minimumPasswordLength'."
    fi

    # Update the password quality policy with the new minimum length
    if ! authconfig --passminlen="$_arg_minimumPasswordLength" --update; then
      _PRINT_HELP=no die "[Error] Failed to modify the password length in the authconfig policy." 1
    fi

    # Verify the updated minimum password length value
    newMinimumLength=$(grep -v "^#" /etc/security/pwquality.conf | grep -v '^\s*$' | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*" | xargs)
    if [[ -n "$newMinimumLength" && "$newMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
      echo "Successfully set the minimum password length."
    else
      _PRINT_HELP=no die "[Error] Failed to modify the minimum password length in '/etc/security/pwquality.conf'." 1
    fi
  fi
fi

# Check if authselect is available
if [[ -n "$authselectAvailable" ]]; then
  echo "The command authselect was detected. Setting the password policy using authselect."

  # Check if max login attempts or lock time arguments are provided
  if [[ -n "$_arg_maxLoginAttempts" || -n "$_arg_loginAttemptLockTime" ]]; then
    echo ""
    echo "Checking the current status of the login attempt lock and lock time."

    # Check if the faillock module is enabled in authselect
    failLockStatus=$(authselect current | grep "with-faillock")

    # Exit if faillock.conf file does not exist
    if [[ ! -f "/etc/security/faillock.conf" ]]; then
      _PRINT_HELP=no die "[Error] The file '/etc/security/faillock.conf' does not exist. Unable to read the current faillock policy." 1
    fi

    # Retrieve current maximum login attempts and lock time from faillock.conf
    currentAttempts=$(grep -v "^#" /etc/security/faillock.conf | grep -v '^\s*$' | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    currentLockTime=$(grep -v "^#" /etc/security/faillock.conf | grep -v '^\s*$' | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")

    # Convert lock time to minutes if it exists
    if [[ -n "$currentLockTime" ]]; then
      currentLockTimeMinutes=$((currentLockTime / 60))
    fi

    # Convert provided lock time to seconds for internal use
    if [[ -n "$_arg_loginAttemptLockTime" ]]; then
      loginAttemptLockTimeMinutes="$_arg_loginAttemptLockTime"
      _arg_loginAttemptLockTime=$((_arg_loginAttemptLockTime * 60))
    fi

    echo "Successfully retrieved the current lock status and lock time."

    # Enable faillock if not currently enabled
    if [[ -z "$failLockStatus" ]]; then
      echo "Enabling faillock PAM module."
      if ! authselect enable-feature with-faillock; then
        _PRINT_HELP=no die "[Error] Failed to enable faillock. This is required to set the max login attempts allowed as well as the lock time." 1
      fi

      if ! authselect apply-changes; then
        _PRINT_HELP=no die "[Error] Failed to enable faillock. This is required to set the max login attempts allowed as well as the lock time." 1
      fi

      # Confirm if faillock has been enabled successfully
      failLockStatus=$(authselect current | grep "with-faillock")
      if [[ -z "$failLockStatus" ]]; then
        _PRINT_HELP=no die "[Error] Failed to enable faillock. This is required to set the max login attempts allowed as well as the lock time." 1
      fi
    fi

    # Skip updating if max login attempts or lock time are already set to the desired values
    if [[ -n "$_arg_maxLoginAttempts" && -n "$currentAttempts" && "$_arg_maxLoginAttempts" == "$currentAttempts" ]]; then
      echo "The maximum login attempts is already set to '$_arg_maxLoginAttempts'. Skipping."
      _arg_maxLoginAttempts=
    fi

    if [[ -n "$_arg_loginAttemptLockTime" && -n "$currentLockTime" && "$_arg_loginAttemptLockTime" == "$currentLockTime" ]]; then
      echo "The lock time is already set to '$loginAttemptLockTimeMinutes' minutes. Skipping."
      _arg_loginAttemptLockTime=
    fi
  fi

  # Exit with error if only max login attempts or lock time is provided without the other
  if [[ -n "$_arg_maxLoginAttempts" && -z "$_arg_loginAttemptLockTime" && -z "$currentLockTime" ]]; then
    _PRINT_HELP=yes die "[Error] When specifying the maximum number of login attempts, you must also specify the login attempt lock time." 1
  fi

  if [[ -n "$_arg_loginAttemptLockTime" && -z "$_arg_maxLoginAttempts" && -z "$currentAttempts" ]]; then
    _PRINT_HELP=yes die "[Error] When specifying a login attempt lock time you must also specify the maximum login attempts." 1
  fi

  # Backup faillock.conf if changes are to be made
  if [[ -n "$_arg_maxLoginAttempts" || -n "$_arg_loginAttemptLockTime" ]]; then
    faillockBackup="/etc/security/$today-faillock.conf.backup"
    echo "Backing up '/etc/security/faillock.conf' to '$faillockBackup'"

    # Exit if backup file already exists to avoid overwriting
    if [[ -f "$faillockBackup" ]]; then
      _PRINT_HELP=no die "[Error] The backup file '$faillockBackup' already exists. Unable to backup faillock policy." 1
    fi

    # Attempt to create backup of faillock.conf, exit if it fails
    if ! cp /etc/security/faillock.conf "$faillockBackup"; then
      _PRINT_HELP=no die "[Error] Unable to backup faillock policy. Failed to save file to '$faillockBackup'" 1
    fi

    # Confirm if the backup was successful
    if [[ -f "$faillockBackup" ]]; then
      echo "Backup created successfully."
    else
      _PRINT_HELP=no die "[Error] Unable to backup faillock. Failed to save the file to '$faillockBackup'" 1
    fi
  fi

  # Configure maximum login attempts and lock time in faillock policy if they are not set already
  if [[ -n "$_arg_maxLoginAttempts" && -n "$_arg_loginAttemptLockTime" && -z "$currentAttempts" && -z "$currentLockTime" ]]; then
    echo "Configuring the maximum login attempts and lock time in the faillock policy."

    # Define the new faillock policy values
    faillockPolicy="silent
deny = $_arg_maxLoginAttempts
unlock_time = $_arg_loginAttemptLockTime"

    # Append new policy to faillock.conf, exit if it fails
    if ! echo "$faillockPolicy" >>"/etc/security/faillock.conf"; then
      _PRINT_HELP=no die "[Error] Failed to configure the maximum login attempts and lock time in the faillock policy." 1
    fi

    # Confirm if the values were correctly set in faillock.conf
    newAttempts=$(grep -v "^#" /etc/security/faillock.conf | grep -v '^\s*$' | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    newLockTime=$(grep -v "^#" /etc/security/faillock.conf | grep -v '^\s*$' | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")

    # Verify if new max login attempts were set correctly
    if [[ -n "$newAttempts" && "$newAttempts" == "$_arg_maxLoginAttempts" ]]; then
      echo "Successfully set the maximum login attempts in the faillock policy."
    else
      _PRINT_HELP=no die "[Error] Unable to configure the maximum login attempts in the faillock policy." 1
    fi

    if [[ -n "$newLockTime" && "$newLockTime" == "$_arg_loginAttemptLockTime" ]]; then
      echo "Successfully added the lock time to the faillock policy."
    else
      _PRINT_HELP=no die "[Error] Failed to add the lock time to the faillock policy." 1
    fi
  fi

  # Update max login attempts in faillock.conf if the current attempts differ from desired setting
  if [[ -n "$_arg_maxLoginAttempts" && -n "$currentAttempts" ]]; then
    echo "Updating the current maximum login attempts from '$currentAttempts' to '$_arg_maxLoginAttempts' in the faillock policy."

    # Update deny value in faillock.conf using sed, exit if it fails
    if ! sed -i "s/^[^#]*deny[[:space:]]*=[[:space:]]*[0-9]*/deny = $_arg_maxLoginAttempts/g" "/etc/security/faillock.conf"; then
      _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the faillock policy." 1
    fi

    # Confirm if the updated max login attempts value was set correctly
    newAttempts=$(grep -v "^#" /etc/security/faillock.conf | grep -v '^\s*$' | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    if [[ -n "$newAttempts" && "$newAttempts" == "$_arg_maxLoginAttempts" ]]; then
      echo "Successfully updated the maximum login attempts in the faillock policy."
    else
      _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the faillock policy." 1
    fi
  fi

  # Check if a new lock time argument is provided and differs from the current setting
  if [[ -n "$_arg_loginAttemptLockTime" && -n "$currentLockTime" ]]; then
    echo "Updating the lock time from '$currentLockTimeMinutes' minutes to '$loginAttemptLockTimeMinutes' minutes in the faillock policy."

    # Modify the unlock_time value in faillock.conf, exiting with error if the operation fails
    if ! sed -i "s/^[^#]*unlock_time[[:space:]]*=[[:space:]]*[0-9]*/unlock_time = $_arg_loginAttemptLockTime/g" "/etc/security/faillock.conf"; then
      _PRINT_HELP=no die "[Error] Failed to modify the lock time in the faillock policy." 1
    fi

    # Retrieve and verify the updated lock time from faillock.conf
    newLockTime=$(grep -v "^#" /etc/security/faillock.conf | grep -v '^\s*$' | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    if [[ -n "$newLockTime" && "$newLockTime" == "$_arg_loginAttemptLockTime" ]]; then
      echo "Successfully modified the lock time in the faillock policy."
    else
      _PRINT_HELP=no die "[Error] Failed to modify the lock time in the faillock policy." 1
    fi
  fi

  # Check if a minimum password length argument is provided
  if [[ -n "$_arg_minimumPasswordLength" ]]; then
    echo ""
    echo "Retrieving the current password quality policy."

    # Exit with error if pwquality.conf does not exist
    if [[ ! -f "/etc/security/pwquality.conf" ]]; then
      _PRINT_HELP=no die "[Error] The file '/etc/security/pwquality.conf' does not exist. Unable to read the current password quality policy." 1
    fi

    # Retrieve the current minimum password length setting from pwquality.conf
    currentMinimumLength=$(grep -v "^#" /etc/security/pwquality.conf | grep -v '^\s*$' | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    echo "Successfully retrieved the current password quality policy."

    # Skip updating if the current minimum length matches the desired setting
    if [[ -n "$currentMinimumLength" && "$currentMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
      echo "The minimum password length is already '$_arg_minimumPasswordLength'. Skipping."
      _arg_minimumPasswordLength=
    fi
  fi

  # Back up pwquality.conf if changes are to be made
  if [[ -n "$_arg_minimumPasswordLength" ]]; then
    passwordQualityBackup="/etc/security/$today-pwquality.conf.backup"
    echo "Backing up '/etc/security/pwquality.conf' to '$passwordQualityBackup'"

    # Exit if backup file already exists to prevent overwriting
    if [[ -f "$passwordQualityBackup" ]]; then
      _PRINT_HELP=no die "[Error] The backup file '$passwordQualityBackup' already exists. Unable to backup password quality policy." 1
    fi

    # Attempt to create the backup, exiting if it fails
    if ! cp "/etc/security/pwquality.conf" "$passwordQualityBackup"; then
      _PRINT_HELP=no die "[Error] Failed to back up the password quality policy to '$passwordQualityBackup'." 1
    fi

    # Confirm if the backup was successful
    if [[ -f "$passwordQualityBackup" ]]; then
      echo "Backup created successfully."
    else
      _PRINT_HELP=no die "[Error] Failed to back up the password quality policy to '$passwordQualityBackup'." 1
    fi
  fi

  # Set minimum password length if no current setting exists
  if [[ -n "$_arg_minimumPasswordLength" && -z "$currentMinimumLength" ]]; then
    echo "Setting the minimum password length to '$_arg_minimumPasswordLength'."

    # Append new minlen value to pwquality.conf, exiting with error if the operation fails
    if ! echo "minlen = $_arg_minimumPasswordLength" >>"/etc/security/pwquality.conf"; then
      _PRINT_HELP=no die "[Error] Unable to set the minimum password length in '/etc/security/pwquality.conf'." 1
    fi

    # Confirm the updated minimum password length value
    newMinimumLength=$(grep -v "^#" /etc/security/pwquality.conf | grep -v '^\s*$' | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    if [[ -n "$newMinimumLength" && "$newMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
      echo "Successfully set the minimum password length."
    else
      _PRINT_HELP=no die "[Error] Unable to set the minimum password length in '/etc/security/pwquality.conf'." 1
    fi
  fi

  # Update the minimum password length if a current setting already exists
  if [[ -n "$_arg_minimumPasswordLength" && -n "$currentMinimumLength" ]]; then
    echo "Updating the minimum password length from '$currentMinimumLength' to '$_arg_minimumPasswordLength'."

    # Modify minlen value in pwquality.conf using sed, exit if the operation fails
    if ! sed -i "s/^[^#]*minlen[[:space:]]*=[[:space:]]*[0-9]*/minlen = $_arg_minimumPasswordLength/g" "/etc/security/pwquality.conf"; then
      _PRINT_HELP=no die "[Error] Failed to update the password length policy." 1
    fi

    # Verify if the update was successful
    newMinimumLength=$(grep -v "^#" /etc/security/pwquality.conf | grep -v '^\s*$' | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    if [[ -n "$newMinimumLength" && "$newMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
      echo "Successfully updated the minimum password length."
    else
      _PRINT_HELP=no die "[Error] Failed to update the minimum password length in '/etc/security/pwquality.conf'." 1
    fi
  fi

  # Check if a password history argument is provided
  if [[ -n "$_arg_passwordHistory" ]]; then
    echo ""
    echo "Retrieving the current password history policy."

    # Exit with error if pwhistory.conf does not exist
    if [[ ! -f "/etc/security/pwhistory.conf" ]]; then
      errorMessage="[Error] The file '/etc/security/pwhistory.conf' does not exist.
[Error] This system may not support changing the password history.
[Error] https://bugzilla.redhat.com/show_bug.cgi?id=2063379
[Error] Unable to read the current password history policy.
"
      _PRINT_HELP=no die "$errorMessage" 1
    fi

    # Retrieve current password history setting from pwhistory.conf
    currentPasswordHistory=$(grep -v "^#" /etc/security/pwhistory.conf | grep -v '^\s*$' | grep -o -e "remember[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    echo "Successfully retrieved the current password history policy."

    # Skip updating if current password history matches desired setting
    if [[ -n "$currentPasswordHistory" && "$currentPasswordHistory" == "$_arg_passwordHistory" ]]; then
      echo "The minimum password history is already set to remember the past '$_arg_passwordHistory' passwords. Skipping."
      _arg_passwordHistory=
    fi
  fi

  # Back up pwhistory.conf if changes are to be made
  if [[ -n "$_arg_passwordHistory" ]]; then
    passwordHistoryBackup="/etc/security/$today-pwhistory.conf.backup"
    echo "Backing up '/etc/security/pwhistory.conf' to '$passwordHistoryBackup'"

    # Exit if backup file already exists
    if [[ -f "$passwordHistoryBackup" ]]; then
      _PRINT_HELP=no die "[Error] The backup file '$passwordHistoryBackup' already exists. Unable to backup the password history policy." 1
    fi

    # Attempt to create backup, exiting if it fails
    if ! cp "/etc/security/pwhistory.conf" "$passwordHistoryBackup"; then
      _PRINT_HELP=no die "[Error] Failed to back up the password history policy to '$passwordHistoryBackup'." 1
    fi

    # Confirm if the backup was successful
    if [[ -f "$passwordHistoryBackup" ]]; then
      echo "Backup created successfully."
    else
      _PRINT_HELP=no die "[Error] Failed to back up the password history policy to '$passwordHistoryBackup'." 1
    fi
  fi

  # Set password history requirement if no current setting exists
  if [[ -n "$_arg_passwordHistory" && -z "$currentPasswordHistory" ]]; then
    echo "Setting the password history requirement."

    # Append remember value to pwhistory.conf, exit if operation fails
    if ! echo "remember = $_arg_passwordHistory" >>"/etc/security/pwhistory.conf"; then
      _PRINT_HELP=no die "[Error] Failed to configure the password history requirement in '/etc/security/pwhistory.conf'." 1
    fi

    # Verify if the password history setting was added successfully
    newPasswordHistory=$(grep -v "^#" /etc/security/pwhistory.conf | grep -v '^\s*$' | grep -o -e "remember[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    if [[ -n "$newPasswordHistory" && "$newPasswordHistory" == "$_arg_passwordHistory" ]]; then
      echo "Successfully set the password history requirement."
    else
      _PRINT_HELP=no die "[Error] Failed to set the password history requirement in the file '/etc/security/pwhistory.conf'." 1
    fi
  fi

  # Update password history requirement if current setting already exists
  if [[ -n "$_arg_passwordHistory" && -n "$currentPasswordHistory" ]]; then
    echo "Updating the password history requirement from remembering the last '$currentPasswordHistory' passwords to the last '$_arg_passwordHistory' passwords."

    # Modify remember value in pwhistory.conf, exit if operation fails
    if ! sed -i "s/^[^#]*remember[[:space:]]*=[[:space:]]*[0-9]*/remember = $_arg_passwordHistory/g" "/etc/security/pwhistory.conf"; then
      _PRINT_HELP=no die "[Error] Failed to update the password history policy." 1
    fi

    # Verify if the update was successful
    newPasswordHistory=$(grep -v "^#" /etc/security/pwhistory.conf | grep -v '^\s*$' | grep -o -e "remember[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
    if [[ -n "$newPasswordHistory" && "$newPasswordHistory" == "$_arg_passwordHistory" ]]; then
      echo "Successfully updated the password history requirement."
    else
      _PRINT_HELP=no die "[Error] Failed to update the password history requirement in the file '/etc/security/pwhistory.conf'." 1
    fi
  fi
fi

# Check if a days until password expiration argument is provided
if [[ -n "$_arg_daysUntilPasswordExpiration" ]]; then
  echo ""
  echo "Retrieving the current password expiration policy."

  # Exit with error if login.defs file does not exist
  if [[ ! -f "/etc/login.defs" ]]; then
    _PRINT_HELP=no die "[Error] The file '/etc/login.defs' does not exist. Unable to read the current password expiration policy." 1
  fi

  # Exit with error if login.defs file is empty
  if [[ ! -s "/etc/login.defs" ]]; then
    _PRINT_HELP=no die "[Error] The file '/etc/login.defs' is empty. Unable to read the current password expiration policy." 1
  fi

  # Retrieve current maximum password age setting from login.defs
  currentMaxAge=$(grep "^PASS_MAX_DAYS" "/etc/login.defs" | grep -o -e "[0-9]*")

  echo "Successfully retrieved the current password expiration policy."

  # Skip updating if current max age matches desired setting
  if [[ -n "$currentMaxAge" && "$currentMaxAge" == "$_arg_daysUntilPasswordExpiration" ]]; then
    echo "The current password expiration policy is already set to a maximum of '$_arg_daysUntilPasswordExpiration' days. Skipping."
    skipMaxAge="on"
  fi
fi

# Back up login.defs if changes to password expiration are needed
if [[ -n "$_arg_daysUntilPasswordExpiration" && "$skipMaxAge" != "on" ]]; then
  loginDefsBackup="/etc/$today-login.defs.backup"
  echo "Backing up '/etc/login.defs' to '$loginDefsBackup'"

  # Exit if backup file already exists to prevent overwriting
  if [[ -f "$loginDefsBackup" ]]; then
    _PRINT_HELP=no die "[Error] The backup file '$loginDefsBackup' already exists. Unable to backup login.defs." 1
  fi

  # Attempt to create backup, exiting if it fails
  if ! cp /etc/login.defs "$loginDefsBackup"; then
    _PRINT_HELP=no die "[Error] Unable to backup login.defs. Failed to save file to '$loginDefsBackup'" 1
  fi

  # Confirm if the backup was successful
  if [[ -f "$loginDefsBackup" ]]; then
    echo "Backup created successfully."
  else
    _PRINT_HELP=no die "[Error] Unable to backup login.defs. Failed to save file to '$loginDefsBackup'" 1
  fi
fi

# Set maximum password age if there is no existing setting
if [[ -n "$_arg_daysUntilPasswordExpiration" && -z "$currentMaxAge" && "$skipMaxAge" != "on" ]]; then
  echo "Configuring the maximum password age in '/etc/login.defs'."

  # Append new PASS_MAX_DAYS value to login.defs, exiting if the operation fails
  if ! echo "PASS_MAX_DAYS   $_arg_daysUntilPasswordExpiration" >>"/etc/login.defs"; then
    _PRINT_HELP=no die "[Error] Failed to configure the password expiration policy in '/etc/login.defs'." 1
  fi

  # Verify if the maximum age setting was added successfully
  newMaxAge=$(grep "^PASS_MAX_DAYS" "/etc/login.defs" | grep -o -e "[0-9]*")
  if [[ -n "$newMaxAge" && "$newMaxAge" == "$_arg_daysUntilPasswordExpiration" ]]; then
    echo "Successfully added a password expiration of '$_arg_daysUntilPasswordExpiration' days for new users."
  else
    _PRINT_HELP=no die "[Error] Failed to configure the password expiration policy in '/etc/login.defs'." 1
  fi
fi

# Update existing maximum password age if a current setting is already present
if [[ -n "$_arg_daysUntilPasswordExpiration" && -n "$currentMaxAge" && "$skipMaxAge" != "on" ]]; then
  echo "Updating the maximum password age from '$currentMaxAge' to '$_arg_daysUntilPasswordExpiration'."

  # Modify PASS_MAX_DAYS value in login.defs using sed, exiting if the operation fails
  if ! sed -i "s/^PASS_MAX_DAYS[[:space:]]*[0-9]*/PASS_MAX_DAYS   $_arg_daysUntilPasswordExpiration/g" "/etc/login.defs"; then
    _PRINT_HELP=no die "[Error] Failed to set the maximum password age in '/etc/login.defs'." 1
  fi

  # Confirm the update was successful
  newMaxAge=$(grep "^PASS_MAX_DAYS" "/etc/login.defs" | grep -o -e "[0-9]*")
  if [[ -n "$newMaxAge" && "$newMaxAge" == "$_arg_daysUntilPasswordExpiration" ]]; then
    echo "Successfully modified the maximum password age for new users."
  else
    _PRINT_HELP=no die "[Error] Failed to configure the password expiration policy in '/etc/login.defs'." 1
  fi
fi

# Apply password expiration policy to existing accounts if a days until expiration argument is provided
if [[ -n "$_arg_daysUntilPasswordExpiration" ]]; then
  echo "Retrieving list of existing accounts."

  # Retrieve user information, exiting if the operation fails
  if ! allUsers=$(cut -f 1,3 -d ':' "/etc/passwd"); then
    _PRINT_HELP=no die "[Error] Failed to retrieve existing users from '/etc/passwd'." 1
  fi

  # Retrieve minimum and maximum user IDs from login.defs, exiting if retrieval fails
  if ! minId=$(grep "^UID_MIN" "/etc/login.defs" | grep -o -e "[0-9]*"); then
    _PRINT_HELP=no die "[Error] Failed to find the minimum user ID in '/etc/login.defs'." 1
  fi

  if ! maxId=$(grep "^UID_MAX" "/etc/login.defs" | grep -o -e "[0-9]*"); then
    _PRINT_HELP=no die "[Error] Failed to find the minimum user ID in '/etc/login.defs'." 1
  fi

  # Ensure both minimum and maximum IDs were found, otherwise exit with error
  if [[ -z "$minId" || -z "$maxId" ]]; then
    _PRINT_HELP=no die "[Error] Failed to find the minimum or maximum user ID in '/etc/login.defs'." 1
  fi

  echo "Setting the maximum password age for existing accounts."

  # Loop through each user to apply the expiration policy if the user ID is within the range
  for user in $allUsers; do
    uid=$(echo "$user" | cut -f 2 -d ":")

    # Check if the user ID is within the defined range
    if [[ "$uid" -ge "$minId" && "$uid" -le "$maxId" ]]; then
      usernameToModify=$(echo "$user" | cut -f 1 -d ":")
      currentMaxAge=$(chage -l "$usernameToModify" | grep Max | grep -o -e "[0-9]*" | xargs)

      # Skip updating if the user's maximum password age already matches the desired setting
      if [[ -n "$currentMaxAge" && "$currentMaxAge" == "$_arg_daysUntilPasswordExpiration" ]]; then
        echo "The user '$usernameToModify' already has a maximum password age of '$_arg_daysUntilPasswordExpiration'. Skipping."
        continue
      fi

      echo "Updating the maximum password age for the user '$usernameToModify'."

      # Attempt to apply the expiration policy, exiting with error if it fails
      if ! chage --maxdays "$_arg_daysUntilPasswordExpiration" "$usernameToModify"; then
        _PRINT_HELP=no die "[Error] Failed to update the maximum password age for the user '$usernameToModify'." 1
      fi

      # Confirm the update was successful
      newMaxAge=$(chage -l "$usernameToModify" | grep Max | grep -o -e "[0-9]*" | xargs)
      if [[ -z "$newMaxAge" || "$newMaxAge" != "$_arg_daysUntilPasswordExpiration" ]]; then
        _PRINT_HELP=no die "[Error] Failed to update the maximum password age for the user '$usernameToModify'." 1
      else
        echo "Successfully updated the maximum password age for the user '$usernameToModify'."
      fi
    fi
  done
fi

# Check if authselect or authconfig is available
if [[ -n "$authselectAvailable" || -n "$authconfigAvailable" ]]; then
  # If password history is specified and only authconfig is available, exit with error
  if [[ -n "$_arg_passwordHistory" && -n "$authconfigAvailable" && -z "$authselectAvailable" ]]; then
    errorMessage="
[Error] This system uses authconfig to modify the PAM configuration files.
[Error] Unfortunately, authconfig does not provide an option to implement a password history requirement and will overwrite all manual changes every time it is run.
[Error] Please upgrade your system to a distribution that supports authselect or allows manual edits of PAM configuration files.
[Error] https://bugzilla.redhat.com/show_bug.cgi?id=1271804"
    _PRINT_HELP=no die "$errorMessage" 1
  fi

  exit
fi

# Check if max login attempts or lock time is provided
if [[ -n "$_arg_maxLoginAttempts" || -n "$_arg_loginAttemptLockTime" ]]; then
  echo ""
  echo "Attempting to retrieve the current PAM authentication policy."

  # Check if common-auth file exists and is not empty
  if [[ ! -f "/etc/pam.d/common-auth" ]]; then
    _PRINT_HELP=no die "[Error] The file '/etc/pam.d/common-auth' does not exist. Cannot read the current PAM authentication policy." 1
  fi
  if [[ ! -s "/etc/pam.d/common-auth" ]]; then
    _PRINT_HELP=no die "[Error] The file '/etc/pam.d/common-auth' is empty. Cannot read the current PAM authentication policy." 1
  fi

  # Retrieve current max login attempts and lock time from common-auth
  currentAttempts=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep "pam_faillock.so" | grep "preauth" | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  currentLockTime=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep "pam_faillock.so" | grep "preauth" | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")

  # Convert lock time to minutes if it exists
  if [[ -n "$currentLockTime" ]]; then
    currentLockTimeMinutes=$((currentLockTime / 60))
  fi

  # Convert provided lock time to seconds if specified
  if [[ -n "$_arg_loginAttemptLockTime" ]]; then
    loginAttemptLockTimeMinutes="$_arg_loginAttemptLockTime"
    _arg_loginAttemptLockTime=$((_arg_loginAttemptLockTime * 60))
  fi

  echo "Current PAM authentication policy retrieved successfully."

  # Skip max login attempts setting if it matches the current configuration
  if [[ -n "$_arg_maxLoginAttempts" && -n "$currentAttempts" && "$_arg_maxLoginAttempts" == "$currentAttempts" ]]; then
    echo "The maximum login attempts is already set to '$_arg_maxLoginAttempts'. Skipping."
    _arg_maxLoginAttempts=
  fi

  # Skip lock time setting if it matches the current configuration
  if [[ -n "$_arg_loginAttemptLockTime" && -n "$currentLockTime" && "$_arg_loginAttemptLockTime" == "$currentLockTime" ]]; then
    echo "The lock time is already set to '$loginAttemptLockTimeMinutes' minutes. Skipping."
    _arg_loginAttemptLockTime=
  fi
fi

# Check for missing lock time or max attempts arguments
if [[ -n "$_arg_maxLoginAttempts" && -z "$_arg_loginAttemptLockTime" && -z "$currentLockTime" ]]; then
  _PRINT_HELP=yes die "[Error] When specifying the maximum number of login attempts, you must also specify the login attempt lock time." 1
fi
if [[ -n "$_arg_loginAttemptLockTime" && -z "$_arg_maxLoginAttempts" && -z "$currentAttempts" ]]; then
  _PRINT_HELP=yes die "[Error] When specifying a login attempt lock time you must also specify the maximum login attempts." 1
fi

# Create a backup of the common-auth file if settings need to be applied
if [[ -n "$_arg_maxLoginAttempts" || -n "$_arg_loginAttemptLockTime" ]]; then
  commonAuthBackup="/etc/pam.d/$today-common-auth.backup"
  echo "Creating a backup of '/etc/pam.d/common-auth' to '$commonAuthBackup'."

  # Exit if a backup file already exists
  if [[ -f "$commonAuthBackup" ]]; then
    _PRINT_HELP=no die "[Error] Backup file '$commonAuthBackup' already exists. Cannot create a backup of the common-auth policy." 1
  fi

  # Attempt to create a backup, exiting if it fails
  if ! cp /etc/pam.d/common-auth "$commonAuthBackup"; then
    _PRINT_HELP=no die "[Error] Failed to create a backup of the common-auth policy to '$commonAuthBackup'." 1
  fi

  # Confirm successful backup creation
  if [[ -f "$commonAuthBackup" ]]; then
    echo "Backup created successfully."
  else
    _PRINT_HELP=no die "[Error] Unable to backup common-auth. Failed to save file to '$commonAuthBackup'" 1
  fi
fi

# Apply max login attempts and lock time settings if none exist in the current policy
if [[ -n "$_arg_maxLoginAttempts" && -n "$_arg_loginAttemptLockTime" && -z "$currentAttempts" && -z "$currentLockTime" ]]; then
  echo "Configuring the maximum login attempts and lock time in the PAM authentication policy."

  # Prepend authfail and preauth lines to common-auth for faillock, exiting if any operation fails
  if ! sed -i "1i auth  required  pam_faillock.so  authfail  deny=$_arg_maxLoginAttempts  unlock_time=$_arg_loginAttemptLockTime" "/etc/pam.d/common-auth"; then
    _PRINT_HELP=no die "[Error] Failed to configure the maximum login attempts and lock time in the PAM authentication policy." 1
  fi
  if ! sed -i "1i auth  required  pam_faillock.so  preauth  silent  deny=$_arg_maxLoginAttempts  unlock_time=$_arg_loginAttemptLockTime" "/etc/pam.d/common-auth"; then
    _PRINT_HELP=no die "[Error] Failed to configure the maximum login attempts and lock time in the PAM authentication policy." 1
  fi

  # Verify if the settings were applied successfully
  newAttempts=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep "pam_faillock.so" | grep "preauth" | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  newLockTime=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep "pam_faillock.so" | grep "preauth" | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")

  if [[ -n "$newAttempts" && "$newAttempts" == "$_arg_maxLoginAttempts" ]]; then
    echo "Successfully configured the maximum login attempts in the PAM authentication policy."
  else
    _PRINT_HELP=no die "[Error] Failed to add the max login attempts to the PAM authentication policy." 1
  fi
  if [[ -n "$newLockTime" && "$newLockTime" == "$_arg_loginAttemptLockTime" ]]; then
    echo "Successfully added the lock time to the PAM authentication policy."
  else
    _PRINT_HELP=no die "[Error] Failed to configure the lock time in the PAM authentication policy." 1
  fi
fi

# Modify existing max login attempts setting if it doesn't match the desired setting
if [[ -n "$_arg_maxLoginAttempts" && -n "$currentAttempts" ]]; then
  echo "Modifying the current max login attempts from '$currentAttempts' to '$_arg_maxLoginAttempts' in the PAM authentication policy."

  # Update max login attempts in preauth and authfail lines, exiting if any operation fails
  existingPreAuthLine=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep -e "pam_faillock\.so [[:space:]]*preauth")
  newAttemptPreAuthLine=$(echo "$existingPreAuthLine" | sed "s/deny[[:space:]]*=[[:space:]]*[0-9]*/deny=$_arg_maxLoginAttempts/g")
  if ! sed -i "s/$existingPreAuthLine/$newAttemptPreAuthLine/g" "/etc/pam.d/common-auth"; then
    _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the PAM authentication policy 1." 1
  fi

  existingAuthFailLine=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep -e "pam_faillock\.so [[:space:]]*authfail")
  newAttemptAuthFailLine=$(echo "$existingAuthFailLine" | sed "s/deny[[:space:]]*=[[:space:]]*[0-9]*/deny=$_arg_maxLoginAttempts/g")
  if ! sed -i "s/$existingAuthFailLine/$newAttemptAuthFailLine/g" "/etc/pam.d/common-auth"; then
    _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the PAM authentication policy 2." 1
  fi

  # Confirm the new max attempts setting was applied
  newAttempts=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep "pam_faillock.so" | grep "preauth" | grep -o -e "deny[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  if [[ -n "$newAttempts" && "$newAttempts" == "$_arg_maxLoginAttempts" ]]; then
    echo "Successfully modified the max login attempts."
  else
    _PRINT_HELP=no die "[Error] Failed to modify the max login attempts in the PAM authentication policy." 1
  fi
fi

# Check if a new lock time is specified and differs from the current one
if [[ -n "$_arg_loginAttemptLockTime" && -n "$currentLockTime" ]]; then
  echo "Modifying the current lock time from '$currentLockTimeMinutes' minutes to '$loginAttemptLockTimeMinutes' minutes in the PAM authentication policy template."

  # Find and replace the unlock_time in the preauth line
  existingPreAuthLine=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep -e "pam_faillock\.so [[:space:]]*preauth")
  newLockTimePreAuthLine=$(echo "$existingPreAuthLine" | sed "s/unlock_time[[:space:]]*=[[:space:]]*[0-9]*/unlock_time=$_arg_loginAttemptLockTime/g")
  if ! sed -i "s/$existingPreAuthLine/$newLockTimePreAuthLine/g" "/etc/pam.d/common-auth"; then
    _PRINT_HELP=no die "[Error] Failed to modify the lock time in the PAM authentication policy." 1
  fi

  # Find and replace the unlock_time in the authfail line
  existingAuthFailLine=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep -e "pam_faillock\.so [[:space:]]*authfail")
  newLockTimeAuthFailLine=$(echo "$existingAuthFailLine" | sed "s/unlock_time[[:space:]]*=[[:space:]]*[0-9]*/unlock_time=$_arg_loginAttemptLockTime/g")
  if ! sed -i "s/$existingAuthFailLine/$newLockTimeAuthFailLine/g" "/etc/pam.d/common-auth"; then
    _PRINT_HELP=no die "[Error] Failed to modify the lock time in the PAM authentication policy." 1
  fi

  # Confirm the lock time modification
  echo "Successfully modified the current lock time."
  newLockTime=$(grep -v "^#" /etc/pam.d/common-auth | grep -v '^\s*$' | grep "pam_faillock.so" | grep "preauth" | grep -o -e "unlock_time[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  if [[ -n "$newLockTime" && "$newLockTime" == "$_arg_loginAttemptLockTime" ]]; then
    echo "Successfully added the lock time to the PAM authentication policy."
  else
    _PRINT_HELP=no die "[Error] Failed to configure the lock time in the PAM authentication policy." 1
  fi
fi

# Check if minimum password length or password history arguments are specified
if [[ -n "$_arg_minimumPasswordLength" || -n "$_arg_passwordHistory" ]]; then
  echo ""
  echo "Retrieving the current PAM common password policy."

  # Check if common-password file exists and is not empty
  if [[ ! -f "/etc/pam.d/common-password" ]]; then
    _PRINT_HELP=no die "[Error] The file '/etc/pam.d/common-password' does not exist. Cannot read the current PAM common password policy." 1
  fi
  if [[ ! -s "/etc/pam.d/common-password" ]]; then
    _PRINT_HELP=no die "[Error] The file '/etc/pam.d/common-password' is empty. Cannot read the current PAM common password policy." 1
  fi

  # Check if pam_unix.so module is present in the file
  if ! grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" 1>/dev/null; then
    _PRINT_HELP=no die "[Error] The 'pam_unix.so' module is missing. Cannot append the minimum password length." 1
  fi

  # Retrieve current settings for minimum length and password history
  currentMinimumLength=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  currentPasswordHistory=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep -o -e "remember[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")

  echo "Successfully retrieved the current PAM common password policy."

  # Skip minimum length setting if it matches the current configuration
  if [[ -n "$_arg_minimumPasswordLength" && -n $currentMinimumLength && "$_arg_minimumPasswordLength" == "$currentMinimumLength" ]]; then
    echo "The minimum password length is already '$_arg_minimumPasswordLength'. Skipping."
    _arg_minimumPasswordLength=
  fi

  # Skip password history setting if it matches the current configuration
  if [[ -n "$_arg_passwordHistory" && -n $currentPasswordHistory && "$_arg_passwordHistory" == "$currentPasswordHistory" ]]; then
    echo "The password history requirement is already set to remember the past '$_arg_passwordHistory' passwords. Skipping."
    _arg_passwordHistory=
  fi
fi

# Create a backup of common-password file if either minimum length or password history needs to be set
if [[ -n "$_arg_minimumPasswordLength" || -n "$_arg_passwordHistory" ]]; then
  commonPasswordBackup="/etc/pam.d/$today-common-password.backup"
  echo "Creating a backup of '/etc/pam.d/common-password' to '$commonPasswordBackup'."

  # Exit if a backup file already exists
  if [[ -f "$commonPasswordBackup" ]]; then
    _PRINT_HELP=no die "[Error] The backup file '$commonPasswordBackup' already exists. Unable to backup common-password." 1
  fi

  # Attempt to create a backup, exiting if it fails
  if ! cp /etc/pam.d/common-password "$commonPasswordBackup"; then
    _PRINT_HELP=no die "[Error] Failed to create a backup of 'common-password' to '$commonPasswordBackup'." 1
  fi

  # Confirm successful backup creation
  if [[ -f "$commonPasswordBackup" ]]; then
    echo "Backup created successfully."
  else
    _PRINT_HELP=no die "[Error] Failed to create a backup of 'common-password' to '$commonPasswordBackup'." 1
  fi
fi

# Set the minimum password length if it does not currently exist in the file
if [[ -n "$_arg_minimumPasswordLength" && -z "$currentMinimumLength" ]]; then
  echo "Setting the minimum password length to '$_arg_minimumPasswordLength'."

  # Find the line with pam_unix.so and append minlen setting
  currentPasswordLine=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so")
  currentPasswordLine=$(echo "$currentPasswordLine" | sed 's/\[/\\[/g' | sed 's/\]/\\]/g')
  newMinLengthLine="$currentPasswordLine minlen=$_arg_minimumPasswordLength"
  newMinLengthLine=${newMinLengthLine//\\/}

  if ! sed -i "s/$currentPasswordLine/$newMinLengthLine/g" "/etc/pam.d/common-password"; then
    _PRINT_HELP=no die "[Error] Unable to set the minimum password length in '/etc/pam.d/common-password'." 1
  fi

  # Verify the setting was applied successfully
  newMinimumLength=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  if [[ -n "$newMinimumLength" && "$newMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
    echo "Successfully set the minimum password length."
  else
    _PRINT_HELP=no die "[Error] Unable to set the minimum password length in '/etc/pam.d/common-password'." 1
  fi
fi

# Update minimum password length if it already exists but differs from desired value
if [[ -n "$_arg_minimumPasswordLength" && -n "$currentMinimumLength" ]]; then
  echo "Updating the minimum password length from '$currentMinimumLength' to '$_arg_minimumPasswordLength'."

  # Modify the existing minlen setting
  currentMinLengthLine=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep "minlen")
  currentMinLengthLine=$(echo "$currentMinLengthLine" | sed 's/\[/\\[/g' | sed 's/\]/\\]/g')
  newMinLengthLine=$(echo "$currentMinLengthLine" | sed "s/minlen[[:space:]]*=[[:space:]]*[0-9]*/minlen=$_arg_minimumPasswordLength/g")
  newMinLengthLine=${newMinLengthLine//\\/}

  if ! sed -i "s/$currentMinLengthLine/$newMinLengthLine/g" "/etc/pam.d/common-password"; then
    _PRINT_HELP=no die "[Error] Unable to update the minimum password length in '/etc/pam.d/common-password'." 1
  fi

  # Verify the updated setting
  newMinimumLength=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep -o -e "minlen[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  if [[ -n "$newMinimumLength" && "$newMinimumLength" == "$_arg_minimumPasswordLength" ]]; then
    echo "Successfully modified the minimum password length required."
  else
    _PRINT_HELP=no die "[Error] Unable to update the minimum password length in '/etc/pam.d/common-password'." 1
  fi
fi

# Set or modify the password history requirement
if [[ -n "$_arg_passwordHistory" && -z "$currentPasswordHistory" ]]; then
  echo "Setting the password history requirement."

  currentPasswordLine=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so")
  currentPasswordLine=$(echo "$currentPasswordLine" | sed 's/\[/\\[/g' | sed 's/\]/\\]/g')
  newPasswordHistoryLine="$currentPasswordLine remember=$_arg_passwordHistory"
  newPasswordHistoryLine=${newPasswordHistoryLine//\\/}

  if ! sed -i "s/$currentPasswordLine/$newPasswordHistoryLine/g" "/etc/pam.d/common-password"; then
    _PRINT_HELP=no die "[Error] Failed to set the password history requirement in '/etc/pam.d/common-password'." 1
  fi

  # Confirm the new setting
  newPasswordHistory=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep -o -e "remember[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  if [[ -n "$newPasswordHistory" && "$newPasswordHistory" == "$_arg_passwordHistory" ]]; then
    echo "Successfully set the password history requirement."
  else
    _PRINT_HELP=no die "[Error] Failed to set the password history requirement in '/etc/pam.d/common-password'." 1
  fi
fi

# Modify the password history setting if it already exists but differs from the new desired value
if [[ -n "$_arg_passwordHistory" && -n "$currentPasswordHistory" && "$_arg_passwordHistory" != "$currentPasswordHistory" ]]; then
  echo "Updating the password history requirement from remembering the last '$currentPasswordHistory' passwords to the last '$_arg_passwordHistory' passwords."

  currentPasswordLine=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep "minlen")
  currentPasswordLine=$(echo "$currentPasswordLine" | sed 's/\[/\\[/g' | sed 's/\]/\\]/g')
  newPasswordHistoryLine=$(echo "$currentPasswordLine" | sed "s/remember[[:space:]]*=[[:space:]]*[0-9]*/remember=$_arg_passwordHistory/g")
  newPasswordHistoryLine=${newPasswordHistoryLine//\\/}

  if ! sed -i "s/$currentPasswordLine/$newPasswordHistoryLine/g" "/etc/pam.d/common-password"; then
    _PRINT_HELP=no die "[Error] Failed to change the password history requirement in '/etc/pam.d/common-password'." 1
  fi

  # Confirm the updated setting
  newPasswordHistory=$(grep -v "^#" /etc/pam.d/common-password | grep -v '^\s*$' | grep "pam_unix.so" | grep -o -e "remember[[:space:]]*=[[:space:]]*[0-9]*" | grep -o -e "[0-9]*")
  if [[ -n "$newPasswordHistory" && "$newPasswordHistory" == "$_arg_passwordHistory" ]]; then
    echo "Successfully modified the password history requirement."
  else
    _PRINT_HELP=no die "[Error] Failed to change the password history requirement in '/etc/pam.d/common-password'." 1
  fi
fi

 

Detailed Breakdown

The script performs the following functions:

1. Argument Parsing and Validation

It accepts parameters like:

  • –maxLoginAttempts: Number of allowed failed attempts before lockout.
  • –loginAttemptLockTime: Duration (in minutes) for account lockout.
  • –daysUntilPasswordExpiration: Password expiration policy.
  • –minimumPasswordLength: Required password length.
  • –passwordHistory: Number of previous passwords remembered.

Each value is strictly validated to prevent misconfiguration.

2. Permission Check

The script must be run with root privileges. It checks id -u and exits otherwise.

3. Platform Detection

It detects whether to use:

  • authselect (modern RHEL-based systems),
  • authconfig (legacy RHEL-based systems),
  • Or directly edit PAM files for Debian-based systems.

4. Policy Application

Based on the available configuration system, the script:

  • Enables faillock if necessary.
  • Updates /etc/security/faillock.conf, pwquality.conf, and pwhistory.conf.
  • Applies password expiration rules via /etc/login.defs and chage.
  • Backs up config files before changes to ensure rollback capability.

5. Fallback to Manual Configuration

If neither authconfig nor authselect is available, the script modifies PAM modules like /etc/pam.d/common-auth and /etc/pam.d/common-password.

Potential Use Cases

Scenario:

An MSP managing 1,000+ mixed-distribution Linux servers wants to enforce:

  • Password expiration every 90 days,
  • Lock accounts for 10 minutes after 5 failed login attempts,
  • Prevent users from reusing the last 5 passwords.

Solution:

They deploy this script via NinjaOne’s scripting engine, passing arguments like:

bash

CopyEdit

–maxLoginAttempts 5 –loginAttemptLockTime 10 –daysUntilPasswordExpiration 90 –minimumPasswordLength 12 –passwordHistory 5

With one policy execution, password standards are enforced consistently across Debian and RHEL systems.

Comparisons

Method Pros Cons
Manual Configuration Full control Time-consuming, error-prone
PAM GUI tools User-friendly Limited in headless/server environments
Configuration Management (e.g., Ansible) Scalable Requires infrastructure & scripting
This Shell Script Lightweight, cross-platform, backup-friendly Requires root and basic Bash knowledge

This Linux password policy shell script offers a balance between flexibility and simplicity, making it ideal for IT teams without a full DevOps stack.

FAQs

Q1: Will this script work on all Linux distros?

It supports Debian 11+ and RHEL 8+ specifically. Older distributions may lack necessary modules like authselect.

Q2: Can I use this in a cron job for periodic enforcement?

Yes, but be cautious. Overwriting password policies repeatedly can lead to unexpected lockouts if not tested properly.

Q3: What if I don’t have authconfig or authselect?

The script gracefully falls back to direct PAM edits using /etc/pam.d/common-auth and related files.

Q4: Is a reboot required after running this?

No. Changes take effect immediately for new sessions.

Implications

Misconfigured password policies can either weaken security (if too lenient) or impede user productivity (if too strict). By automating these settings, IT administrators ensure a secure baseline across their environment. Moreover, maintaining password history and enforcing expiration policies helps reduce vulnerabilities like password reuse and brute-force attacks.

From a compliance perspective, the script aids adherence to CIS benchmarks, PCI-DSS, and HIPAA, which often require password aging and complexity controls.

Recommendations

  • Test in a staging environment before deploying to production.
  • Use NinjaOne’s scheduling engine to automate periodic audits.
  • Keep backups of configuration files as the script does.
  • Combine with account monitoring tools for a holistic security approach.
  • Ensure time synchronization across devices for accurate policy enforcement.

Final Thoughts

Managing password policies at scale doesn’t have to be a pain point. With this Linux password policy shell script, IT teams and MSPs gain a reliable, version-controlled way to enforce critical security standards across distributed Linux systems. Integrated with NinjaOne, this script becomes even more powerful. NinjaOne enables seamless script deployment, centralized policy enforcement, and real-time compliance reporting—empowering IT professionals to stay ahead of evolving security demands.

By using shell scripting to set the password policy for Linux devices, you reduce human error, ensure consistency, and significantly harden your endpoints. This approach is a must-have in any modern IT toolkit.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our Terms of Use:

  • Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
  • Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
  • Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library belonging to or under the control of any other software provider.
  • Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
  • Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
  • Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
  • EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).