
"Automate Linux User Offboarding: Lock, Archive, Revoke"
Before you run this
This is a bash script that offboards a single local Linux user on a standalone server: it locks the account, expires it so no login (password or key) succeeds, kills any live sessions, backs up the user's crontab, disables their SSH authorized_keys, and archives their home directory to a tarball. It does not delete the account or the home directory — removal is left as a deliberate, manual step so you have an undo path.
It must run as root (or via sudo), because usermod, chage, loginctl and reading another user's crontab all require privilege.
Some of what it does is effectively irreversible in practice: killing sessions drops the user's running work, and once you follow through with the optional home-directory deletion the data exists only in the archive. Locking, expiring and disabling keys are reversible (I show how at the end), but treat the whole thing as a one-way door for the person leaving.
Test first. Read the script line by line, then run it against a throwaway test user on a non-production VM — create a testleaver account, give it a crontab and an SSH key, and confirm the script behaves before you point it at a real departing employee. Never run an offboarding script for the first time against a live account.
Assumptions, held throughout:
- Debian 12 / Ubuntu 22.04, systemd-based,
bashas root's shell. - Local accounts only. If your users live in LDAP, FreeIPA, or Active Directory, disabling them happens in the directory, not with
usermod— this script does not apply to those. - Home directories under
/home, SSH keys in~/.ssh/authorized_keys(the OpenSSH default). If you set a customAuthorizedKeysFileinsshd_config, adjust accordingly.
The script
Save this as offboard-user.sh. Replace nothing in the code itself — it takes the username as an argument.
#!/usr/bin/env bash
# Offboard a single local user: lock, expire, kill sessions,
# back up crontab, disable SSH keys, archive home.
set -euo pipefail
USER_TO_OFFBOARD="${1:-}"
ARCHIVE_DIR="/var/offboarding" # where tarballs and crontab backups land
DATE_TAG="$(date +%Y%m%d-%H%M%S)"
if [[ -z "$USER_TO_OFFBOARD" ]]; then
echo "Usage: sudo $0 <username>" >&2
exit 1
fi
if [[ "$EUID" -ne 0 ]]; then
echo "This must run as root." >&2
exit 1
fi
# Confirm the account exists before touching anything.
if ! id "$USER_TO_OFFBOARD" >/dev/null 2>&1; then
echo "User '$USER_TO_OFFBOARD' does not exist." >&2
exit 1
fi
HOME_DIR="$(getent passwd "$USER_TO_OFFBOARD" | cut -d: -f6)"
echo "About to offboard: $USER_TO_OFFBOARD (home: $HOME_DIR)"
read -r -p "Type the username again to confirm: " CONFIRM
[[ "$CONFIRM" == "$USER_TO_OFFBOARD" ]] || { echo "Aborted."; exit 1; }
mkdir -p "$ARCHIVE_DIR"
# 1. Lock the password (prepends ! to the hash in /etc/shadow).
usermod -L "$USER_TO_OFFBOARD"
# 2. Expire the account as of the epoch, so *all* logins fail,
# including SSH key logins (PAM account check rejects an expired account).
chage -E 0 "$USER_TO_OFFBOARD"
# 3. Back up the crontab (if any), then remove it.
if crontab -l -u "$USER_TO_OFFBOARD" >/dev/null 2>&1; then
crontab -l -u "$USER_TO_OFFBOARD" \
> "$ARCHIVE_DIR/${USER_TO_OFFBOARD}-crontab-${DATE_TAG}.txt"
crontab -r -u "$USER_TO_OFFBOARD"
echo "Crontab backed up and removed."
fi
# 4. Disable SSH keys by renaming authorized_keys (belt-and-braces
# alongside the account expiry above).
if [[ -f "$HOME_DIR/.ssh/authorized_keys" ]]; then
mv "$HOME_DIR/.ssh/authorized_keys" \
"$HOME_DIR/.ssh/authorized_keys.disabled-${DATE_TAG}"
echo "authorized_keys disabled."
fi
# 5. Archive the home directory (leaves the original in place).
if [[ -d "$HOME_DIR" ]]; then
tar czf "$ARCHIVE_DIR/${USER_TO_OFFBOARD}-home-${DATE_TAG}.tar.gz" \
-C "$(dirname "$HOME_DIR")" "$(basename "$HOME_DIR")"
echo "Home archived to $ARCHIVE_DIR/${USER_TO_OFFBOARD}-home-${DATE_TAG}.tar.gz"
fi
# 6. Terminate any live sessions and running processes.
# loginctl handles logind sessions; pkill catches stragglers.
loginctl terminate-user "$USER_TO_OFFBOARD" 2>/dev/null || true
pkill -KILL -u "$USER_TO_OFFBOARD" 2>/dev/null || true
echo "Offboarding complete for $USER_TO_OFFBOARD."
echo "Home directory NOT deleted. Remove it manually once the archive is verified."
Make it executable and run it against your test user first:
chmod +x offboard-user.sh
sudo ./offboard-user.sh testleaver
Why both lock and expire
usermod -L locks the password hash, but on a key-only server that alone would not stop SSH — the user could still log in with their key. Expiring the account with chage -E 0 makes PAM's account phase reject the login regardless of method, which is what actually shuts the door. Renaming authorized_keys is a third layer so the key is visibly gone, not just refused.
On deleting the home directory
I deliberately did not put rm -rf or userdel -r in the script. Once the archive is verified and your retention window has passed, delete the home directory by hand:
# Only after you have confirmed the tarball extracts cleanly.
sudo rm -rf /home/testleaver
If you also want to remove the account entirely (not just disable it), userdel is the standard tool — check man userdel for the exact behaviour of -r, -f, and how it handles the mail spool on your distro before you use it.
Verify it worked
Check the account is locked and expired:
# Password field starts with ! when locked.
sudo passwd -S testleaver
# Account expiry should show a past date.
sudo chage -l testleaver
passwd -S prints an L in the second field for a locked account; passwd -S output and the chage -l "Account expires" line together confirm the account state.
Confirm no live sessions remain:
loginctl list-sessions
ps -u testleaver # should return no processes
Confirm the crontab is gone and the SSH key is disabled:
sudo crontab -l -u testleaver # "no crontab for testleaver"
ls -la /home/testleaver/.ssh/ # authorized_keys.disabled-* present, no active file
Confirm the archive is real and readable — list its contents, don't assume:
tar tzf /var/offboarding/testleaver-home-*.tar.gz | head
Undo (before you delete anything)
Everything except the killed sessions and any deleted home directory is reversible:
# Unlock the password.
sudo usermod -U testleaver
# Remove the expiry (-1 disables account expiration).
sudo chage -E -1 testleaver
# Re-enable SSH keys.
sudo mv /home/testleaver/.ssh/authorized_keys.disabled-* \
/home/testleaver/.ssh/authorized_keys
# Restore the crontab from the backup.
sudo crontab -u testleaver /var/offboarding/testleaver-crontab-*.txt
If you archived and then deleted the home directory, restore it by extracting the tarball back into place:
sudo tar xzf /var/offboarding/testleaver-home-*.tar.gz -C /home
For the exact semantics of any of these tools, the manual pages are the authority: man usermod, man chage, man crontab, and man loginctl. Read them before adapting this to a directory-backed environment or a non-default home/key layout — that's where the assumptions above stop holding.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
Related guides
A Bash Wrapper to Run One Command Across Many Hosts Over SSH
This is a small bash wrapper that reads a list of hostnames from a file and runs the same command on all of them over SSH, several at a time in parallel , tagging every line of output with the host it came from. The purpose is to save you from looping through servers by hand when you want to check a value, restart a service, or gather a fact fleet-wide.
Inventory Installed Packages and Services Across Servers over SSH
This guide gives you a small bash script that logs into a list of servers over SSH, and on each one dumps two things to a text file on your control machine: the list of installed packages (via dpkg-query on Debian/Ubuntu or rpm on RHEL/Alma) and the list of systemd service unit files. The purpose is a point-in-time software and service inventory you can diff, audit, or archive.
Automate a Weekly Patch-and-Report Routine for a Small Server Fleet
This sets up a weekly, unattended package upgrade on each server, then emails you a plain-text report of what was upgraded and whether a reboot is now pending. The upgrade step runs apt-get upgrade non-interactively, so it changes installed software on the host. Package upgrades are not cleanly reversible: apt has no "undo the last upgrade" button, so treat this with the same caution as any change to a running server.
Automate a Read-Only Security Audit Report for a Linux Server
This guide builds a single bash script that takes a read-only snapshot of a server's security posture — logged-in users, listening sockets, running services, UID 0 accounts, the effective SSH config, pending updates, SUID/SGID binaries, and world-writable files — and writes it to a timestamped text file. It inspects and records; it never edits config, kills processes, changes permissions, or installs anything.




