Skip to content

Module 8.3: Package Management & User Administration

Hands-On Lab Available
Ubuntu intermediate 30 min
Launch Lab ↗

Opens in Killercoda in a new tab

Operations — LFCS | Complexity: [MEDIUM] | Time: 40-50 min

Before starting this module:


After this module, you will be able to:

  • Manage packages using apt/dnf (install, update, pin, hold) and resolve dependency conflicts
  • Administer users and groups with proper access controls and password policies
  • Configure sudo access with fine-grained permissions for different admin roles
  • Audit installed packages for security vulnerabilities and unnecessary software

Two things happen on every Linux server, every day: software gets installed and people need access. Package management and user administration are the bread and butter of system administration — the skills you will use more often than anything else in your career.

Understanding these skills helps you:

  • Keep systems patched — Unpatched software is the number one attack vector in production
  • Control access — The principle of least privilege starts with user accounts and sudo
  • Automate provisioning — Every Ansible playbook, Dockerfile, and cloud-init script uses package and user commands
  • Pass the LFCS exam — User management and package management are tested directly

If you have ever SSH’d into a server and typed apt install or useradd, you have already started. This module makes sure you really understand what those commands do under the hood.


  • Debian’s package archive contains over 60,000 packages — making it one of the largest curated software collections in the world. Every single one is maintained by a volunteer. The apt tool manages the dependency graph between all of them automatically.

  • The /etc/shadow file was invented because /etc/passwd was world-readable. In the early days of Unix, password hashes lived in /etc/passwd where any user could read (and crack) them. Shadow passwords moved the hashes to a root-only file — a simple fix that dramatically improved security.

  • visudo exists because of real disasters. A single syntax error in /etc/sudoers can lock every user out of sudo, including root. visudo validates the file before saving. There is no undo if you edit it with a regular editor and make a mistake.

  • RPM was created by Red Hat in 1997 and stands for “Red Hat Package Manager” (later backronymed to “RPM Package Manager,” making it a recursive acronym like GNU). The .rpm format is still used by RHEL, Fedora, SUSE, and Amazon Linux.


A package is a compressed archive containing:

nginx_1.24.0-1_amd64.deb
├── Pre-compiled binaries (/usr/sbin/nginx)
├── Configuration files (/etc/nginx/nginx.conf)
├── Documentation (/usr/share/doc/nginx/)
├── Metadata (version, description, maintainer)
├── Dependencies (requires: libc6, libpcre3, libssl3)
└── Scripts (pre-install, post-install, pre-remove, post-remove)

Without packages, you would compile every piece of software from source, manually track files, and resolve dependency conflicts by hand. Package managers handle all of this automatically.

Every Linux distribution has two layers of package management:

LayerDebian/UbuntuRHEL/FedoraPurpose
Low-leveldpkgrpmInstall/remove individual package files
High-levelaptdnfResolve dependencies, download from repositories

Think of it like this: dpkg/rpm are like manually installing an app from a downloaded file. apt/dnf are like an app store that finds, downloads, and installs everything you need automatically.


Terminal window
# Refresh the list of available packages from repositories
# This does NOT upgrade anything — it just downloads the latest catalog
sudo apt update
# Output shows which repositories were fetched:
# Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
# Get:2 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB]
# Fetched 2,345 kB in 3s (782 kB/s)

Always run apt update before installing or upgrading. Without it, you are working from a stale catalog and may install outdated versions.

Terminal window
# Install a single package
sudo apt install nginx
# Install multiple packages at once
sudo apt install nginx curl vim
# Install without interactive confirmation
sudo apt install -y nginx
# Install a specific version
sudo apt install nginx=1.24.0-1ubuntu1
Terminal window
# Search for packages by name or description
apt search "web server"
# Show detailed info about a package (installed or available)
apt show nginx
# Package: nginx
# Version: 1.24.0-1ubuntu1
# Depends: libc6, libpcre2-8-0, libssl3, zlib1g
# Description: small, powerful, scalable web/proxy server
# List installed packages
apt list --installed
# List installed packages matching a pattern
apt list --installed 2>/dev/null | grep nginx
Terminal window
# Remove the package but keep configuration files
sudo apt remove nginx
# Remove the package AND its configuration files
sudo apt purge nginx
# Remove packages that were installed as dependencies but are no longer needed
sudo apt autoremove
# Nuclear option: purge + autoremove
sudo apt purge -y nginx && sudo apt autoremove -y

The difference between remove and purge matters. If you remove nginx and reinstall it later, your old configuration files are still there. If you purge, you start fresh.

Terminal window
# Upgrade all packages to their latest versions (safe — never removes packages)
sudo apt upgrade
# Upgrade all packages, allowing removal of packages if needed for dependency resolution
sudo apt full-upgrade
# See what would be upgraded without doing it
apt list --upgradable

Sometimes you download a .deb file directly (like from a vendor’s website). That is when dpkg comes in:

Terminal window
# Install a .deb file
sudo dpkg -i google-chrome-stable_current_amd64.deb
# If dpkg fails due to missing dependencies, fix them:
sudo apt install -f
# List all installed packages
dpkg -l
# List installed packages matching a pattern
dpkg -l | grep nginx
# Find which package owns a file on your system
dpkg -S /usr/sbin/nginx
# nginx-core: /usr/sbin/nginx
# List all files installed by a package
dpkg -L nginx-core
Terminal window
# Add a PPA (Ubuntu-specific shortcut)
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
# Add a third-party repository manually
# 1. Download and add the GPG key
curl -fsSL https://packages.example.com/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/example-archive-keyring.gpg
# 2. Add the repository definition
echo "deb [signed-by=/usr/share/keyrings/example-archive-keyring.gpg] https://packages.example.com/apt stable main" | sudo tee /etc/apt/sources.list.d/example.list
# 3. Update and install
sudo apt update
sudo apt install example-package

Repository definitions live in /etc/apt/sources.list and /etc/apt/sources.list.d/. Use the .d/ directory for third-party repos — it keeps things organized and easy to remove.

Sometimes you need to prevent a package from being upgraded — for example, if a newer kernel breaks your hardware:

Terminal window
# Prevent a package from being upgraded
sudo apt-mark hold linux-image-generic
# Show held packages
apt-mark showhold
# Release the hold
sudo apt-mark unhold linux-image-generic

Terminal window
# Install a package
sudo dnf install nginx
# Install without confirmation
sudo dnf install -y nginx
# Remove a package
sudo dnf remove nginx
# Install a local .rpm file (dnf resolves dependencies, unlike plain rpm)
sudo dnf install ./package-1.0.0.x86_64.rpm
Terminal window
# Search for packages
dnf search "web server"
# Show package details
dnf info nginx
# List all installed packages
dnf list installed
# Find which package provides a file
dnf provides /usr/sbin/nginx
# or for a command you don't have yet:
dnf provides */bin/traceroute
Terminal window
# Update all packages
sudo dnf update
# Update a specific package
sudo dnf update nginx
# Check for available updates
dnf check-update
Terminal window
# Query all installed packages
rpm -qa
# Query a specific package
rpm -qi nginx
# Name : nginx
# Version : 1.24.0
# Release : 1.el9
# List files in an installed package
rpm -ql nginx
# Find which package owns a file
rpm -qf /usr/sbin/nginx
# nginx-core-1.24.0-1.el9.x86_64
# Verify installed package (checks file integrity)
rpm -V nginx
# S.5....T. c /etc/nginx/nginx.conf
# (S=size, 5=md5, T=time changed — the config was modified)
Terminal window
# Add a repository from a URL
sudo dnf config-manager --add-repo https://packages.example.com/example.repo
# List enabled repositories
dnf repolist
# List all repositories (including disabled)
dnf repolist all
# Enable a specific repository
sudo dnf config-manager --set-enabled powertools

TaskDebian/Ubuntu (apt)RHEL/Fedora (dnf)
Update package listsapt updatednf check-update
Install packageapt install nginxdnf install nginx
Remove packageapt remove nginxdnf remove nginx
Purge (remove + config)apt purge nginxdnf remove nginx (removes configs too)
Upgrade allapt upgradednf update
Searchapt search termdnf search term
Show infoapt show nginxdnf info nginx
List installedapt list --installeddnf list installed
Which package owns filedpkg -S /path/to/filerpm -qf /path/to/file
Install local filedpkg -i file.debdnf install ./file.rpm
Hold/exclude from upgradeapt-mark hold pkgdnf versionlock add pkg
Clean cacheapt cleandnf clean all

Packages are cryptographically signed by their maintainers. Your system verifies these signatures automatically — but you should understand what is happening:

Terminal window
# --- Debian/Ubuntu ---
# List trusted GPG keys
apt-key list # deprecated but still works
# Modern approach: keys in /usr/share/keyrings/ or /etc/apt/keyrings/
# Verify a .deb file's signature
dpkg-sig --verify package.deb
# --- RHEL/Fedora ---
# Import a GPG key
sudo rpm --import https://packages.example.com/RPM-GPG-KEY-example
# Verify an RPM's signature
rpm --checksig package.rpm
# package.rpm: digests signatures OK
# Check which keys are trusted
rpm -qa gpg-pubkey*

If you ever see a warning like “The following signatures couldn’t be verified,” stop and investigate. Never blindly add --nogpgcheck — that defeats the purpose of signed packages and opens you to supply chain attacks.


Module 1.4 introduced the concepts of UIDs, GIDs, and file permissions. This section covers the practical administration: creating users, managing groups, configuring sudo access, and understanding the critical files involved.

Every user account is a single line in /etc/passwd:

username:x:UID:GID:comment:home_directory:login_shell

Real example:

/bin/bash
grep "deploy" /etc/passwd

Breaking it down:

FieldValueMeaning
usernamedeployLogin name
xxPassword stored in /etc/shadow (not here)
UID1001Numeric user ID
GID1001Primary group ID
commentDeploy UserFull name / description (GECOS field)
home/home/deployHome directory path
shell/bin/bashLogin shell
Terminal window
# View the file (it is world-readable — no passwords here)
cat /etc/passwd
# Count total users
wc -l /etc/passwd
# List only human users (UID >= 1000, excluding nobody)
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3}' /etc/passwd

This is the sensitive file. Only root can read it:

username:$hashed_password:last_changed:min:max:warn:inactive:expire:reserved

Real example:

Terminal window
sudo grep "deploy" /etc/shadow
# deploy:$6$rounds=656000$randomsalt$longHashHere...:19750:0:99999:7:::
FieldValueMeaning
usernamedeployLogin name
password$6$...Hashed password ($6$ = SHA-512)
last_changed19750Days since Jan 1 1970 password was last changed
min0Minimum days between password changes
max99999Maximum days before password must be changed
warn7Days before expiry to warn user
inactive(empty)Days after expiry before account is disabled
expire(empty)Date account expires (days since epoch)

Password hash prefixes tell you the algorithm:

PrefixAlgorithmStatus
$1$MD5Weak — do not use
$5$SHA-256Acceptable
$6$SHA-512Current default on most distros
$y$yescryptModern default on Debian 12+, Fedora 38+
! or *(none)Account is locked / no password login
groupname:x:GID:member_list
Terminal window
grep "docker" /etc/group
# docker:x:999:deploy,alice
# List all groups a user belongs to
groups deploy
# deploy : deploy docker sudo
# Same info with GIDs
id deploy
# uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),999(docker),27(sudo)

Terminal window
# Create a user with defaults
sudo useradd alice
# This creates the user but:
# - No password set (account locked)
# - Home directory created (if CREATE_HOME=yes in /etc/login.defs)
# - Shell from /etc/default/useradd
# Create a user with all the options you typically want
sudo useradd -m -s /bin/bash -c "Alice Smith" -G sudo,docker alice
# -m Create home directory
# -s Set login shell
# -c Set comment/full name
# -G Add to supplementary groups
# Set password immediately after
sudo passwd alice
# Create a user with a specific UID
sudo useradd -u 2000 -m -s /bin/bash bob
# Create a user with an expiration date
sudo useradd -m -s /bin/bash -e 2026-12-31 contractor
Terminal window
# Add user to a supplementary group (APPEND — critical flag!)
sudo usermod -aG docker alice
# -a Append to group list (without -a, it REPLACES all groups!)
# -G Supplementary group
# Change login shell
sudo usermod -s /bin/zsh alice
# Lock an account (prefix password hash with !)
sudo usermod -L alice
# Unlock an account
sudo usermod -U alice
# Change home directory and move files
sudo usermod -d /home/newalice -m alice
# Change username
sudo usermod -l newalice alice

The -aG flag is so important it deserves its own warning: usermod -G docker alice (without -a) removes alice from every other supplementary group. This has locked people out of sudo access more times than anyone cares to count.

Terminal window
# Remove user but keep home directory
sudo userdel alice
# Remove user AND their home directory and mail spool
sudo userdel -r alice
Terminal window
# Set or change a user's password (interactive)
sudo passwd alice
# Set password non-interactively (useful in scripts)
echo "alice:NewPassword123!" | sudo chpasswd
# Force password change on next login
sudo passwd -e alice
# View password aging information
sudo chage -l alice
# Last password change : Mar 15, 2026
# Password expires : never
# Account expires : never
# Set password to expire every 90 days
sudo chage -M 90 alice
# Set account expiration date
sudo chage -E 2026-12-31 contractor

Terminal window
# Create a new group
sudo groupadd developers
# Create with a specific GID
sudo groupadd -g 3000 devops
# Add existing user to the group
sudo usermod -aG developers alice
# Remove a user from a group (no direct command — use gpasswd)
sudo gpasswd -d alice developers
# Delete a group
sudo groupdel developers
# Rename a group
sudo groupmod -n dev-team developers

CharacteristicSystem AccountRegular Account
UID range1-9991000+
Home directoryOften /var/lib/service or none/home/username
Login shell/usr/sbin/nologin or /bin/false/bin/bash or similar
PurposeRun daemons/servicesHuman users
Created byPackage installationAdministrator
Examplewww-data, mysql, postgresalice, deploy
Terminal window
# Create a system account (for running a service)
sudo useradd -r -s /usr/sbin/nologin -d /var/lib/myapp -c "MyApp Service" myapp
# -r Create a system account (UID < 1000, no aging)
# -s /usr/sbin/nologin Prevent interactive login
# Verify it cannot log in
sudo su - myapp
# This account is currently not available.

When useradd -m creates a home directory, it copies everything from /etc/skel:

.bash_logout
# See what's in the skeleton directory
ls -la /etc/skel
# .bashrc
# .profile
# Customize the skeleton for new users
sudo cp /path/to/company-bashrc /etc/skel/.bashrc
sudo mkdir /etc/skel/.ssh
sudo touch /etc/skel/.ssh/authorized_keys
sudo chmod 700 /etc/skel/.ssh
sudo chmod 600 /etc/skel/.ssh/authorized_keys
# Now every new user gets these files automatically
sudo useradd -m -s /bin/bash newuser
ls -la /home/newuser/
# drwx------ .ssh/
# -rw-r--r-- .bashrc (your customized version)

This is how organizations standardize user environments across servers. Put your standard shell configuration, SSH directory structure, and any other defaults into /etc/skel.


Running commands as root is dangerous. sudo provides:

  • Auditing — Every sudo command is logged (/var/log/auth.log or journalctl)
  • Granularity — Grant specific commands, not full root access
  • Accountability — You know who ran the command, not just “root did something”
  • Time limits — sudo credentials expire (default 15 minutes)

The War Story: Never Edit sudoers with vim

Section titled “The War Story: Never Edit sudoers with vim”

Here is a story that has happened at countless companies. A junior admin needs to give a developer sudo access. They know the sudoers file is at /etc/sudoers, so they do what seems logical:

Terminal window
sudo vim /etc/sudoers # DO NOT DO THIS

They add a line, but make a tiny typo — maybe a missing comma or an extra space in the wrong place. They save and quit. Vim does not validate sudoers syntax.

Now sudo is broken. Every sudo command returns:

>>> /etc/sudoers: syntax error near line 42 <<<
sudo: parse error in /etc/sudoers near line 42
sudo: no valid sudoers sources found, quitting

Nobody can use sudo. Including root (if root login is disabled, which it often is on cloud servers). The only ways to fix this:

  1. Boot into single-user/recovery mode (if you have physical/console access)
  2. Mount the disk from another instance (in the cloud)
  3. Use pkexec if PolicyKit is installed (rare lifeline)

The fix was always simple: use visudo.

Terminal window
# Edit the sudoers file safely
sudo visudo
# visudo does two critical things:
# 1. Locks the file so two admins can't edit simultaneously
# 2. Validates syntax before saving — rejects invalid changes
# Edit a specific sudoers drop-in file
sudo visudo -f /etc/sudoers.d/developers

If you make a syntax error, visudo tells you and asks what to do:

>>> /etc/sudoers: syntax error near line 25 <<<
What now? (e)dit, (x)exit without saving, (Q)quit without saving
Terminal window
# Basic format:
# WHO WHERE=(AS_WHOM) WHAT
# user host=(runas) commands
# Give alice full sudo access
alice ALL=(ALL:ALL) ALL
# Give bob sudo access without password
bob ALL=(ALL:ALL) NOPASSWD: ALL
# Give the devops group access to restart services only
%devops ALL=(root) /usr/bin/systemctl restart *, /usr/bin/systemctl status *
# Give deploy user access to deploy commands only, no password
deploy ALL=(root) NOPASSWD: /usr/bin/rsync, /usr/bin/systemctl restart myapp

Breaking down alice ALL=(ALL:ALL) ALL:

PartMeaning
aliceThis rule applies to user alice
First ALLOn any host (relevant for shared sudoers via LDAP/NIS)
(ALL:ALL)Can run as any user:any group
Last ALLCan run any command

Instead of editing the main sudoers file, use drop-in files. This is the modern, recommended approach:

Terminal window
# Create a file for the developers team
sudo visudo -f /etc/sudoers.d/developers
# Contents:
# %developers ALL=(ALL:ALL) ALL
# Create a file for a specific service account
sudo visudo -f /etc/sudoers.d/deploy
# Contents:
# deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/rsync

Rules for drop-in files:

  • File names must NOT contain . or ~ (they will be silently ignored)
  • Files must be owned by root with permissions 0440
  • Always create them with visudo -f, which sets correct permissions
  • The main /etc/sudoers must include: @includedir /etc/sudoers.d (or #includedir on older systems — the # is NOT a comment here)

MistakeWhat HappensFix
apt install without apt update firstInstall outdated or missing package versionsAlways run sudo apt update before installing
usermod -G without -aUser is removed from ALL other supplementary groupsAlways use usermod -aG group user
Editing /etc/sudoers with vim/nanoSyntax error locks out all sudo accessAlways use visudo
Using apt-key add for GPG keysDeprecated; keys trusted for ALL repositoriesUse signed-by with keyring files in /usr/share/keyrings/
Deleting a user without -rOrphaned home directory wastes disk and is a security riskUse userdel -r or manually clean up /home/username
Setting password via command line argumentPassword visible in shell history and process listUse passwd interactively or pipe through chpasswd
Forgetting nologin shell for service accountsService accounts can be used for interactive loginCreate with useradd -r -s /usr/sbin/nologin
Adding --nogpgcheck to silence warningsDisables cryptographic verification of packagesImport the correct GPG key instead
Sudoers drop-in file with . in nameFile is silently ignored — rule never appliesName files without dots or tildes (e.g., developers not developers.conf)
Running apt full-upgrade without checkingMay remove packages to resolve dependenciesRun apt list --upgradable first to review changes

Q1: What is the difference between apt remove and apt purge?

Show Answer

apt remove uninstalls the package binaries but leaves configuration files on disk. apt purge removes both the binaries and all configuration files. If you plan to reinstall with the same config, use remove. If you want a clean slate, use purge.

Q2: You need to find which installed package provides the file /usr/bin/curl on a Debian system. What command do you use?

Show Answer
/usr/bin/curl
dpkg -S /usr/bin/curl

On RHEL/Fedora, the equivalent is rpm -qf /usr/bin/curl.

Q3: What does the $6$ prefix in a password hash in /etc/shadow indicate?

Show Answer

$6$ indicates the password is hashed using SHA-512. Other prefixes: $1$ is MD5 (weak), $5$ is SHA-256, and $y$ is yescrypt (modern default on newer distributions).

Q4: Why is usermod -aG docker alice correct but usermod -G docker alice dangerous?

Show Answer

Without the -a (append) flag, -G replaces the user’s entire supplementary group list. So usermod -G docker alice would remove alice from every other group (including sudo), leaving her in only docker. With -a, the group is added to the existing list.

Q5: A colleague created a sudoers drop-in file at /etc/sudoers.d/web.devs but the rules are not taking effect. What is wrong?

Show Answer

The filename contains a dot (.). Files in /etc/sudoers.d/ with dots or tildes in their names are silently skipped. The fix is to rename it to something like web-devs (using a hyphen instead of a dot).

Q6: You need to prevent the nginx package from being upgraded during your next apt upgrade. How do you do it, and how do you reverse it later?

Show Answer
Terminal window
# Hold the package
sudo apt-mark hold nginx
# Verify it is held
apt-mark showhold
# Release the hold when ready
sudo apt-mark unhold nginx

On RHEL/Fedora, the equivalent is dnf versionlock add nginx and dnf versionlock delete nginx.

Q7: What is the purpose of /etc/skel?

Show Answer

/etc/skel is the “skeleton” directory. When a new user is created with useradd -m, the contents of /etc/skel are copied into their new home directory. This is how administrators provide default configuration files (.bashrc, .profile, .ssh/ directory structure) to all new users automatically.


Hands-On Exercise: User and Package Administration

Section titled “Hands-On Exercise: User and Package Administration”

Objective: Practice the full lifecycle of user management and package operations on a Linux system.

Environment: Any Ubuntu/Debian VM, container, or WSL instance with root access.

Terminal window
# Update package lists
sudo apt update
# Install two packages
sudo apt install -y tree jq
# Verify installation
which tree && which jq
# Find which package owns the jq binary
dpkg -S $(which jq)
# List all files installed by the jq package
dpkg -L jq
# Show package information
apt show jq
# Hold jq to prevent upgrades
sudo apt-mark hold jq
apt-mark showhold
# Should show: jq
# Release the hold
sudo apt-mark unhold jq
# Remove jq (keep config) then purge tree (remove everything)
sudo apt remove -y jq
sudo apt purge -y tree
sudo apt autoremove -y
Terminal window
# Create a group
sudo groupadd -g 3000 webteam
# Create a user with full options
sudo useradd -m -s /bin/bash -c "Test Developer" -G webteam -u 2000 testdev
# Verify the user
id testdev
# uid=2000(testdev) gid=2000(testdev) groups=2000(testdev),3000(webteam)
grep testdev /etc/passwd
# testdev:x:2000:2000:Test Developer:/home/testdev:/bin/bash
# Set a password
echo "testdev:TempPass123!" | sudo chpasswd
# Verify shadow entry exists
sudo grep testdev /etc/shadow | cut -d: -f1-3
# Force password change on next login
sudo passwd -e testdev
# Verify password aging
sudo chage -l testdev
# Last password change: password must be changed
# Create a second user and add to the same group
sudo useradd -m -s /bin/bash -c "Test Operator" testops
sudo usermod -aG webteam testops
# Verify group membership
grep webteam /etc/group
# webteam:x:3000:testdev,testops
Terminal window
# Create a sudoers drop-in file for the webteam group
sudo visudo -f /etc/sudoers.d/webteam
# Add this line (type it in the editor that opens):
# %webteam ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx
# Verify the file was created with correct permissions
ls -la /etc/sudoers.d/webteam
# -r--r----- 1 root root ... /etc/sudoers.d/webteam
# Test: switch to testdev and try allowed vs denied commands
sudo -u testdev sudo -l
# (root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx
Terminal window
# Add a custom file to /etc/skel
echo "Welcome to the team! Read /wiki for onboarding docs." | sudo tee /etc/skel/WELCOME.txt
# Create a new user and verify they got the file
sudo useradd -m -s /bin/bash skeltest
ls -la /home/skeltest/WELCOME.txt
cat /home/skeltest/WELCOME.txt
# Welcome to the team! Read /wiki for onboarding docs.
Terminal window
# Remove users
sudo userdel -r testdev
sudo userdel -r testops
sudo userdel -r skeltest
# Remove group
sudo groupdel webteam
# Remove sudoers file
sudo rm /etc/sudoers.d/webteam
# Remove skel customization
sudo rm /etc/skel/WELCOME.txt
  • Installed and removed packages using apt, verified with dpkg -S
  • Held and unheld a package with apt-mark
  • Created users with specific UID, shell, groups, and home directory
  • Set passwords and configured password aging with chage
  • Created a sudoers drop-in file using visudo -f
  • Customized /etc/skel and verified it works for new users
  • Cleaned up all test users, groups, and files

Continue with Module 8.4: Service Configuration & Scheduling to learn about systemd unit files, cron jobs, and timers — the tools that keep Linux services running and tasks executing on schedule.