Set Up a Linux Server for a Dev Team
Configure a multi-user Linux server with permissions, ACLs, and cron jobs.
Introduction
30 Second Summary
Every company that builds software needs someone to manage the servers where code runs. Creating accounts for new developers, controlling file access, and keeping systems healthy are daily tasks of a Linux system administrator.
In this project, you will configure a Linux server for a development team. You'll create user accounts, set up secure shared directories, install tools, schedule automated maintenance, and troubleshoot a disk space issue.
What You'll Build
A fully configured Linux server environment running in a container on your Mac, simulating the real-world scenario of onboarding a new development team.
By the end of this project, you'll have:
- A running Ubuntu Linux environment with multiple user accounts and groups.
- Shared project directories with proper ownership, permissions, and ACLs.
- Scheduled maintenance cron jobs and managed processes with a diagnosed and resolved disk space issue.
- Secret Mission: Configure per-user resource limits and build a multi-team ACL structure with inherited default permissions.
Are there any prerequisites?
This project requires a Mac with Apple Silicon running macOS 26 (Tahoe) and Apple's container CLI already installed. No additional tools or accounts are needed.
Launch Your Linux Lab
Every Linux sysadmin needs a safe environment to practice in. A sandboxed server lets you experiment freely without risking anything on your real machine.
Apple's container CLI turns your Mac into a Linux lab by running each container inside its own lightweight virtual machine. In this step, you'll launch an Ubuntu server and get oriented inside it.
In this step, get ready to:
- Start the container service and pull the Ubuntu 24.04 image.
- Launch an interactive Linux container.
- Verify your environment with basic Linux commands.
Start the container service and pull the image
Before you can run containers, the background service that manages them needs to be active. This service handles pulling images, creating VMs, and managing the lifecycle of your containers.
- Start the container service by running this command:
container system start
What does this command do?
This starts the container-apiserver background service on your Mac. If this is your first time, it also downloads and installs a Linux kernel that your containers will use.
You should see output confirming the services have started. If prompted about installing the kernel, allow it.
- Pull the Ubuntu 24.04 image by running this command:
container image pull ubuntu:24.04
What does this command do?
This downloads the ubuntu:24.04 image from Docker Hub to your local machine. The image is a pre-built snapshot of an Ubuntu system that your container will boot from.
The download may take a minute depending on your internet speed. You should see progress output as each layer is pulled.
Seeing a connection error?
Make sure the service is running by checking container system status. If it shows the service is not active, run container system start again.
If you see a network-related error, check your internet connection. The image needs to be downloaded from Docker Hub.
Help me troubleshoot my container image pull error.
Launch your Ubuntu container
With the image downloaded, you can now launch a container from it. A container is a running instance of an image, similar to how a running program is an instance of an application file.
- Launch an interactive Ubuntu container by running this command:
container run --name linux-lab -ti ubuntu:24.04
What does this command do?
- --name linux-lab gives the container a memorable name you can reference later.
- -t allocates a terminal (TTY) so you get a proper command prompt.
- -i keeps standard input open so you can type commands interactively.
- ubuntu:24.04 tells it which image to create the container from.
Your terminal prompt changes after the command completes. You are now inside the Ubuntu container as the root user.
Prompt didn't change?
If you see an error about the name already being in use, a container called linux-lab already exists from a previous attempt. Remove it first by running container delete linux-lab and then try the container run command again.
Help me launch my Ubuntu container.
Explore your new Linux environment
You are now working inside a full Linux system. These first commands will confirm what operating system is running and help you get familiar with navigating the filesystem.
- Check your system information and explore the filesystem by running these commands one at a time:
uname -a
cat /etc/os-release
ls /
pwd
whoami
What do these commands do?
- uname -a prints the kernel name, version, and architecture. You should see Linux and aarch64 (ARM 64-bit).
- cat /etc/os-release displays detailed info about the Linux distribution. Confirm it shows Ubuntu 24.04.
- ls / lists the top-level directories in the filesystem (bin, etc, home, usr, and more). This is the standard Linux directory structure.
- pwd prints your current working directory. You should see / (the root of the filesystem).
- whoami shows the current user. It should output root.
Your lab is reusable
This container persists on your Mac. If you exit and want to reconnect later, you can start it again with container start -ai linux-lab. You do not need to recreate it every time you practice.
Your Linux lab is up and running. Next up, you'll start mastering essential file operations and text processing commands inside it.
Master Essential File Operations
Your Linux lab is live. Now it is time to learn the commands you will use every single day as a sysadmin.
Every task on a Linux server starts with navigating the filesystem, creating files, and transforming text. These commands map directly to the Essential Commands domain of the LFCS exam, which accounts for 20% of the total score.
In this step, get ready to:
- Create a project directory structure and practice copying, moving, finding, and redirecting.
- Process text with grep, sed, and awk to search, replace, and extract data.
- Archive files with tar and analyze text with sorting and counting tools.
Create project files and directories
Before you can manage a server, you need to build a workspace. Linux uses a hierarchical directory structure, and mkdir -p lets you create nested folders in a single command.
- Create a practice directory with nested folders by running these commands:
mkdir -p /root/practice/{reports,data,scripts}
mkdir -p /root/practice/data/{raw,processed}
What do these commands do?
The -p flag creates parent directories as needed. Without it, mkdir fails if any parent folder is missing.
The curly braces {reports,data,scripts} expand into three separate directories in one shot. This is called brace expansion.
- Verify your directory tree by running:
ls /root/practice
ls /root/practice/data
You should see reports, data, and scripts in the first listing. The second listing shows raw and processed inside data.
- Move into your practice directory by running:
cd /root/practice
- Create sample files using I/O redirection, then copy and move them by running:
echo "Server started successfully" > data/raw/server.log
echo "Error: disk full" >> data/raw/server.log
echo "Warning: high CPU usage" >> data/raw/server.log
echo "Error: connection timeout" >> data/raw/server.log
echo "Project README" > reports/readme.txt
cp reports/readme.txt reports/readme-backup.txt
mv reports/readme-backup.txt data/processed/
What does this code do?
The > operator creates a new file (or overwrites an existing one) with the output of the command. The >> operator appends to the end of an existing file without erasing it.
The cp command copies a file to a new location. The mv command moves a file, which also works for renaming.
- Confirm the files are in place by running:
ls reports/
ls data/processed/
cat data/raw/server.log
You should see readme.txt in reports, readme-backup.txt in data/processed, and four log lines printed from the server.log file.
Not seeing your files?
Make sure you ran cd /root/practice before the file creation commands. All paths here are relative to that directory.
If you see "No such file or directory", double-check you created the directories with the mkdir -p commands first.
help me figure out why my files are missing in my Linux practice directory
- Use find to locate files by name and type, then practice the < (input redirection) and | (pipe) operators by running:
find /root/practice -name "*.txt"
find /root/practice -type d
find /root/practice -name "*.log"
wc -l < data/raw/server.log
cat data/raw/server.log | grep "Error"
What does this code do?
The find command searches directories recursively. The -name flag matches filenames with wildcard patterns. The -type d flag restricts results to directories only.
The < operator sends a file's contents as input to a command. The pipe | sends the output of one command directly into another command as input.
You should see two .txt files found, several directories listed, one .log file found, a line count of 4, and the two lines containing "Error" printed.
Process text with grep, sed, and awk
Linux servers generate enormous amounts of text in log files, config files, and command output. The power of Linux comes from combining small text-processing tools together with pipes.
- Search for patterns in your log file using grep by running:
grep "Error" data/raw/server.log
grep -i "error" data/raw/server.log
grep -n "Warning" data/raw/server.log
grep -c "Error" data/raw/server.log
What does this code do?
The grep command searches files for lines matching a pattern. The -i flag makes the search case-insensitive. The -n flag shows line numbers. The -c flag counts matching lines instead of printing them.
You should see two Error lines from the first command, and a count of 2 from the last command.
- Use sed to find and replace text in your log file by running:
sed 's/Error/CRITICAL/' data/raw/server.log
cat data/raw/server.log
sed -i 's/Warning/ALERT/' data/raw/server.log
cat data/raw/server.log
What does this code do?
The sed command performs find-and-replace on text streams. Without -i, it only prints the modified output without changing the file. With -i, it edits the file in place.
Notice how the first cat still shows "Warning" because the first sed ran without -i. The second cat shows "ALERT" because the second sed used -i to save the change.
- Extract structured data using awk and pipe commands together by running:
cat /etc/passwd | awk -F: '{print $1, $7}'
awk -F: '{print $1, $3}' /etc/passwd | head -5
awk -F: '$3 == 0 {print $1}' /etc/passwd
What does this code do?
The awk command processes text column by column. The -F: flag sets the colon as the field delimiter (since /etc/passwd uses colons between fields).
The $1 refers to the first column (username), $3 is the third column (user ID), and $7 is the seventh column (default shell). The last command finds users with UID 0, which is always root.
You should see a list of usernames paired with their shells, then the first five users with their UIDs, and finally just root printed from the last command.
The pipe philosophy
Piping commands together with | is the core of how Linux works. Each command does one small job. You chain them to build powerful data pipelines without writing scripts.
Archive files and analyze text
Sysadmins regularly compress files for backups and transfers. Linux offers multiple compression formats, and tar is the standard tool for bundling files into archives.
- Create compressed archives using both gzip and bzip2 formats by running:
cd /root/practice
tar -czf reports-archive.tar.gz reports/
tar -cjf data-archive.tar.bz2 data/
ls -lh *.tar.*
What does this code do?
The tar command bundles files into a single archive. The -c flag creates an archive, -z compresses with gzip, -j compresses with bzip2, and -f specifies the output filename.
Gzip (.tar.gz) is faster. Bzip2 (.tar.bz2) produces smaller files but takes longer. Both are common on Linux servers.
The ls -lh output should show both archive files with their sizes in human-readable format.
- Extract an archive to verify its contents by running:
mkdir extracted
tar -xzf reports-archive.tar.gz -C extracted/
ls extracted/reports/
What does this code do?
The -x flag extracts instead of creating. The -C flag specifies where to extract the files. You should see readme.txt in the extracted reports directory.
Seeing 'cannot stat' or empty output?
Make sure you are in /root/practice when running the tar commands. Run pwd to check your current directory.
If the archive was created from a different directory, the paths inside may not match. Re-run the tar -czf command after running cd /root/practice.
help me troubleshoot tar archive extraction in my Linux container
- Practice text analysis tools on /etc/passwd by running:
head -3 /etc/passwd
tail -3 /etc/passwd
wc -l /etc/passwd
cat /etc/passwd | cut -d: -f1 | sort
cat /etc/passwd | cut -d: -f7 | sort | uniq -c | sort -rn
What does this code do?
The head command shows the first N lines. The tail command shows the last N lines. The wc -l command counts total lines in a file.
The cut -d: -f1 extracts the first colon-delimited field. Piping through sort alphabetizes the output. The uniq -c counts duplicates, and sort -rn sorts numerically in reverse order.
The last command answers: "What shells are used on this system and how many users have each?" This kind of pipeline is exactly what the LFCS exam tests.
You should see a sorted list of shells with counts next to each one. The /usr/sbin/nologin shell should have the highest count because most system accounts cannot log in interactively.
You now have the essential file operations down. Next up, you will create user accounts and groups to turn this into a proper multi-user server.
Create Team User Accounts and Groups
You've practiced navigating the filesystem and manipulating text. Now it's time to simulate a real sysadmin responsibility: onboarding a development team onto the server.
Every Linux server needs user accounts and groups to control who can access what. In this step, you'll create team groups, add user accounts, and inspect the system files that Linux uses to track it all. This maps directly to the LFCS Users & Groups domain (10% of the exam).
In this step, get ready to:
- Install user management packages and create team groups.
- Create individual user accounts and set their passwords.
- Inspect the system files that Linux uses to store account information.
Install packages and create team groups
The minimal Ubuntu container image doesn't include user management tools by default. You need to install them before creating any accounts.
- Update the package list and install the required packages by running this command:
apt update && apt install -y passwd sudo
What does this command do?
`apt update` refreshes the list of available packages from Ubuntu's repositories. The `&&` means the second command only runs if the first succeeds.
`apt install -y passwd sudo` installs two packages: passwd (for managing user passwords) and sudo (for granting elevated privileges). The -y flag auto-confirms the install so you don't have to type "yes" manually.
You should see a stream of output ending with lines showing the packages being unpacked and set up.
Seeing 'Unable to locate package'?
Make sure you included apt update before apt install. Without the update, your container doesn't know what packages are available.
help me fix a package installation error in my Ubuntu container
Now you have the tools to manage users. Before creating individual accounts, you'll create the groups they belong to. Groups let you manage permissions for multiple users at once.
- Create the two team groups by running these commands:
groupadd dev-team
groupadd ops-team
What does this do?
`groupadd` creates a new group on the system. You're creating dev-team for developers and ops-team for operations staff. Later, you'll assign directories and permissions to these groups.
- Verify the groups were created by running:
grep -E 'dev-team|ops-team' /etc/group
You should see two lines, each showing the group name and its numeric group ID.
Create user accounts and set passwords
With your groups ready, you'll now create three user accounts. Each user gets a home directory, a default shell, and membership in one of the team groups.
- Create the three user accounts by running these commands:
# Create alice and bob in the dev-team group
useradd -m -s /bin/bash -G dev-team alice
useradd -m -s /bin/bash -G dev-team bob
# Create charlie in the ops-team group
useradd -m -s /bin/bash -G ops-team charlie
What do these flags mean?
- `-m` creates a home directory for the user (e.g. /home/alice).
- `-s /bin/bash` sets their default shell to Bash.
- `-G dev-team` adds the user to the specified supplementary group.
- Confirm the home directories were created by running:
ls /home
You should see three directories: alice, bob, and charlie.
Each user needs a password. The passwd command will prompt you to type a password twice for each user.
- Set a password for alice by running:
passwd alice
What will I see?
You'll be prompted to type a new password, then retype it to confirm. The characters won't appear on screen as you type. This is normal Linux security behaviour. Use something simple like password123 since this is a practice environment.
- Set passwords for bob and charlie the same way:
passwd bob
passwd charlie
After each command, type your chosen password twice. You should see a confirmation message after each one.
Seeing 'password unchanged' or 'too short'?
Since you're running as root, these are just warnings. The password is still set. If you see an error about the passwd command not being found, go back and make sure the apt install -y passwd sudo command completed successfully.
help me troubleshoot the passwd command in my container
Inspect how Linux stores account information
Linux stores all user and group data in plain text files. Understanding these files is essential for troubleshooting account issues on the LFCS exam.
- View the user account entries by running:
# View user account records (username:password:UID:GID:comment:home:shell)
cat /etc/passwd | awk -F: '{print $1, $7}' | tail -5
What is /etc/passwd?
`/etc/passwd` contains one line per user account. Each line has seven colon-separated fields: username, password placeholder, UID, GID, comment, home directory, and shell.
The awk -F: '{print $1, $7}' part extracts just the username and shell columns. You should see alice, bob, and charlie all using /bin/bash.
- View the group file to see your new groups by running:
# View group records (groupname:password:GID:members)
cat /etc/group | grep -E 'dev-team|ops-team'
You should see dev-team with alice and bob listed as members, and ops-team with charlie.
- Check the shadow file where encrypted passwords are stored by running:
# View encrypted password hashes (only accessible as root)
cat /etc/shadow | grep -E 'alice|bob|charlie'
What is /etc/shadow?
`/etc/shadow` stores the actual encrypted password hashes. It's only readable by root, which is why the old practice of storing passwords in /etc/passwd was replaced. You'll see a long hash string for each user (the encrypted version of the password you set).
Finally, verify each user's group memberships using the dedicated commands for this purpose.
- Check alice's full identity and bob's group memberships by running:
# Show alice's UID, GID, and all group memberships
id alice
# Show bob's group memberships
groups bob
What's the difference between id and groups?
`id alice` shows numeric UIDs and GIDs plus all group names. It's the most complete view of a user's identity. `groups bob` shows just the group names in a simpler format.
The id alice output should show alice's UID, her primary group, and dev-team as a supplementary group. The groups bob output should list dev-team as well.
Don't see the group membership?
If id alice doesn't show dev-team in the groups list, the -G flag may have been missed during user creation. You can fix this with usermod -aG dev-team alice to add alice to the group after the fact.
help me fix user group membership in Linux
Your team is onboarded. You have three users across two groups, all with home directories and passwords. Next up, you'll create shared project directories and lock them down with the right permissions so each team can only access what they should.
Configure Permissions and ACLs
You have user accounts and groups ready to go. The next challenge is making sure each team can only access the files they should.
Linux permissions control who can read, write, and execute every file on the system. In this step, you'll create shared project directories and secure them using group ownership, special permission bits, and Access Control Lists.
In this step, get ready to:
- Create shared project directories with proper group ownership.
- Apply setgid and sticky bit permissions.
- Install ACLs and grant cross-team read access.
Create shared project directories
Every team needs a dedicated space to store their project files. You'll create two directories and assign the correct group ownership so that only the right team members can write to them.
- Create the shared project directories and set their group ownership by running these commands:
# Create the directory structure
mkdir -p /opt/projects/webapp /opt/projects/infra
# Set group ownership for each team's directory
chgrp dev-team /opt/projects/webapp
chgrp ops-team /opt/projects/infra
What do these commands do?
The mkdir -p command creates the full directory path, including any parent directories that don't exist yet.
The chgrp command changes the group owner of each directory. Now /opt/projects/webapp belongs to dev-team and /opt/projects/infra belongs to ops-team.
- Verify the group ownership by running:
ls -ld /opt/projects/webapp /opt/projects/infra
You should see dev-team as the group for webapp and ops-team as the group for infra in the output.
Directories not showing the right group?
Make sure you typed the group names exactly as they were created: dev-team and ops-team. Group names are case-sensitive. You can double-check which groups exist by running cat /etc/group.
help me fix group ownership on my project directories
Apply setgid and sticky bit permissions
Group ownership alone is not enough. Without extra protection, any file a team member creates inside the shared directory would belong to their personal group instead of the team group.
The setgid bit solves this by forcing new files to inherit the directory's group. The sticky bit protects shared scratch space by preventing users from deleting each other's files.
- Set the setgid bit on both project directories and create a sticky-bit scratch directory by running these commands:
# Set the setgid bit so new files inherit the directory's group
chmod g+s /opt/projects/webapp
chmod g+s /opt/projects/infra
# Create a shared scratch directory with the sticky bit
mkdir /tmp/team-scratch
chmod +t /tmp/team-scratch
What are setgid and sticky bit?
The setgid bit (chmod g+s) on a directory makes every new file created inside automatically inherit the directory's group ownership. Without it, files would belong to the creator's personal group, breaking team collaboration.
The sticky bit (chmod +t) on a directory means only a file's owner can delete or rename it, even if other users have write access to the directory. This is how /tmp works on most Linux systems.
- Verify the special permission bits by running:
ls -ld /opt/projects/webapp /opt/projects/infra /tmp/team-scratch
You should see an s in the group execute position for the project directories (e.g. drwxr-sr-x) and a t in the other execute position for team-scratch.
Seeing a capital S instead of lowercase s?
A capital S means the setgid bit is set but the group execute bit is missing. Fix it by running chmod g+x /opt/projects/webapp to add group execute permission.
help me fix permissions showing capital S instead of lowercase s
- Test that the setgid bit works by creating a file as root inside the webapp directory:
touch /opt/projects/webapp/testfile
ls -l /opt/projects/webapp/testfile
The file should show dev-team as its group, proving that new files inherit the directory's group automatically.
- Clean up the test file:
rm /opt/projects/webapp/testfile
Install ACLs and grant cross-team access
Standard Linux permissions only let you assign one owner and one group per file. What if the ops team needs read access to the webapp directory without being added to the dev-team group?
ACLs solve this by letting you grant fine-grained permissions to any number of users or groups on a single file or directory.
- Install the ACL package by running:
apt install -y acl
Package not found?
If you see an error about the package not being found, run apt update first to refresh the package list, then try the install command again.
help me install the acl package in my Ubuntu container
- Grant the ops-team read and execute access to the webapp directory using an ACL:
# Grant ops-team read/execute access to the webapp directory
setfacl -m g:ops-team:rx /opt/projects/webapp
What does this setfacl command do?
The -m flag means "modify" the ACL. The rule g:ops-team:rx breaks down as: g for group, ops-team for the target group, and rx for read and execute permissions.
This lets charlie (who is in ops-team) browse and read files in /opt/projects/webapp without being a member of dev-team.
- Verify the ACL was applied by running:
getfacl /opt/projects/webapp
You should see a group:ops-team:r-x line in the output, confirming that ops-team has read and execute access.
How to read getfacl output
The output shows the file name, owner, and group at the top. Below that, each line is an ACL entry. The user:: line shows the owner's permissions. The group:: line shows the owning group's permissions.
Named entries like group:ops-team:r-x are the extra ACL rules you added. The mask line shows the maximum effective permissions for all named users and groups.
Seeing an operation not supported error?
This can happen if the filesystem doesn't support ACLs. In Apple's container CLI, each container runs its own VM with a full filesystem, so ACLs should work. Make sure you installed the acl package successfully before running setfacl.
help me troubleshoot ACLs not working in my Linux container
Your shared directories are now locked down with layered security. Next up, you'll install software packages and learn how to manage running processes on your server.
Install Software and Manage Processes
Your server has users, groups, and secure directories. But a server without software is like a workshop without tools.
In this step, you will install packages your dev team needs, monitor what is running on the system, and learn to control processes. These skills map directly to the LFCS Operations & Deployment domain, which covers 25% of the exam.
In this step, get ready to:
- Search for, install, verify, and remove packages with apt.
- Monitor running processes and send signals to control them.
- Run processes in the background and keep them alive after shell exit.
Search for, install, and verify packages
Linux package managers let you find and install software from trusted repositories in seconds. On Ubuntu, the apt command handles searching, installing, updating, and removing packages.
- Search for a package called nginx by running this command:
apt search nginx
You should see a list of packages with "nginx" in the name or description. The one we want is simply nginx.
- Install three packages your team needs by running:
apt install -y nginx curl vim
What does this command do?
apt install downloads and installs packages from Ubuntu's repository. The -y flag automatically confirms the installation prompt so you do not have to type "yes" manually.
- nginx is a web server.
- curl is a tool for making HTTP requests from the terminal.
- vim is a powerful text editor.
- Verify that nginx was installed successfully by running:
dpkg -l | grep nginx
You should see a line starting with ii followed by the nginx package name and version. The ii means the package is fully installed.
What does dpkg -l do?
dpkg -l lists all installed packages on the system. Piping it through grep nginx filters the output to show only lines containing "nginx". This is how you confirm a specific package is present.
- Practice removing a package by running:
apt remove -y vim
You should see output confirming that vim was removed.
- Reinstall vim so your team still has it available:
apt install -y vim
Seeing 'Unable to locate package'?
If apt cannot find a package, your package lists may be outdated. Run apt update first to refresh the list of available packages, then try the install again.
help me fix an apt package installation error in my Ubuntu container
Monitor processes and send signals
Every running program on a Linux system is a process. As a sysadmin, you need to see what is running and stop misbehaving processes. This is a core part of the LFCS Operations domain.
- List all running processes by running:
ps aux
You should see a table of processes with columns like USER, PID, %CPU, %MEM, and COMMAND. Each row is one running process.
What do the ps aux columns mean?
- USER shows which account owns the process.
- PID is the unique process ID you use to reference or kill a process.
- %CPU and %MEM show how much of the system's resources this process is using.
- COMMAND shows the actual command that started the process.
- Try real-time monitoring with top by running:
top
You should see a live-updating view of processes sorted by CPU usage. Press q to exit.
- Install htop for a more user-friendly process viewer by running:
apt install -y htop
- Launch htop by running:
htop
You should see a colourful process viewer with CPU and memory bars at the top. Press q to exit when you are done exploring.
Why htop over top?
htop shows the same information as top but adds colour coding, mouse support, and easier process selection. Both are valid for the LFCS exam. Learn top because it is available everywhere, and use htop for everyday convenience.
Now let's practice controlling processes with signals. You will start a long-running process and then stop it.
- Start a process that runs forever by running:
sleep 300 &
The & at the end sends the process to the background. You should see a line showing a job number in square brackets and a PID number.
- Find the PID of your sleep process by running:
ps aux | grep sleep
Note the PID number in the second column of the output. You will use it in the next commands.
- Send a graceful termination signal to the process by running this command (replace PID with the number you noted):
kill -SIGTERM [[PID="the PID number from ps output"]]
What is SIGTERM?
SIGTERM asks a process to shut down gracefully. The process gets a chance to clean up (close files, release resources) before exiting. This is the polite way to stop something.
- Start another sleep process for practicing a forced kill:
sleep 300 &
- Find the new PID with ps aux | grep sleep, then force kill it by running:
kill -9 [[PID="the PID number from ps output"]]
When do you use kill -9?
kill -9 sends SIGKILL, which forces the operating system to terminate the process immediately. The process gets no chance to clean up. Use this only when SIGTERM does not work, because a stuck process cannot ignore SIGKILL.
- Confirm the process is gone by running:
ps aux | grep sleep
You should only see the grep command itself in the output, confirming no sleep processes remain.
Process still showing after kill?
Make sure you are using the correct PID. Run ps aux | grep sleep again to check. The PID is the number in the second column of the output.
help me figure out why my kill command did not stop the process
Run background processes and use job control
Sometimes you want to start a long-running task without it blocking your terminal. Linux gives you job control to manage processes running in the background.
- Start a background process by running:
sleep 600 &
You should see output like [1] 1234 where [1] is the job number and 1234 is the PID.
- List all background jobs by running:
jobs
You should see your sleep process listed with a Running status.
- Bring the background job to the foreground by running:
fg
Your terminal is now "stuck" on the sleep command. It is running in the foreground. Press Ctrl+C to kill it and get your prompt back.
What is the difference between fg and bg?
fg brings a background job into the foreground, meaning it takes over your terminal. bg resumes a stopped job in the background. You stop a foreground process with Ctrl+Z, then send it to background with bg.
There is one more tool to learn. What happens if you close your terminal while a background job is running? Normally, it dies. The nohup command prevents that.
- Start a process that will survive shell exit by running:
nohup sleep 600 &
You should see a message about appending output to nohup.out. This file captures any output the process produces.
- Verify the nohup process is running by running:
ps aux | grep sleep
You should see your sleep process in the list. Even if you closed and reopened your shell, this process would keep running because of nohup.
- Clean up the remaining sleep processes by running:
pkill sleep
When would you use nohup in real life?
nohup is useful when you SSH into a remote server, start a long-running task (like a database migration or backup), and want it to keep running after you disconnect. Without nohup, closing your SSH session would kill the process.
Terminal seems stuck?
If your terminal appears frozen or unresponsive, you may have a foreground process blocking it. Press Ctrl+C to kill it, or press Ctrl+Z to suspend it and get your prompt back.
help me get my terminal prompt back after running a process
Your server now has software installed and you know how to monitor and control any process on the system. Next up, you will schedule automated maintenance tasks so the server can take care of itself.
Schedule Automated Maintenance
Your server now has users, groups, permissions, and software installed. A real production server also needs tasks that run automatically without anyone watching.
In this step, you'll use cron to schedule a maintenance script that cleans up old temporary files. This maps directly to the LFCS Operations domain: "Manage/schedule jobs."
In this step, get ready to:
- Install cron and create a cleanup script.
- Schedule the script with crontab entries for different intervals.
- Verify cron is running and update to a production schedule.
Install cron and create the cleanup script
Cron is the standard job scheduler on Linux systems. It reads a schedule file (your "crontab") and runs commands at the times you specify.
- Install the cron package and start the service by running these commands:
apt install -y cron
/etc/init.d/cron start
What does this command do?
The first line installs cron from Ubuntu's package repository. The second line starts the cron service using a SysV init script.
On a standard Ubuntu server you would use systemctl start cron instead. This container uses a different init system, so we use the /etc/init.d/ script directly.
- Verify the cron service is running:
/etc/init.d/cron status
You should see output indicating that cron is running.
Seeing "cron is not running"?
Run /etc/init.d/cron start again. If that still fails, confirm cron is installed with dpkg -l | grep cron.
Help me troubleshoot why cron won't start in my Ubuntu container
Now you need a script for cron to run. This cleanup script will remove temporary files older than 7 days from /tmp.
- Create the cleanup script by running this command:
cat > /usr/local/bin/cleanup.sh << 'EOF'
#!/bin/bash
# Remove temporary files older than 7 days
find /tmp -type f -mtime +7 -delete
echo "Cleanup complete: $(date)" >> /var/log/cleanup.log
EOF
What does this script do?
The find /tmp -type f -mtime +7 -delete command searches for regular files in /tmp that were last modified more than 7 days ago, then deletes them.
The echo line logs a timestamp each time the script runs. This gives you a way to confirm cron is executing it.
- Make the script executable by running:
chmod +x /usr/local/bin/cleanup.sh
- Verify the script works by running it manually:
/usr/local/bin/cleanup.sh
- Check the log to confirm it ran:
cat /var/log/cleanup.log
You should see a line with "Cleanup complete:" followed by the current date and time.
Permission denied when running the script?
Make sure you ran the chmod +x command on the correct path. Run ls -l /usr/local/bin/cleanup.sh and confirm you see -rwxr-xr-x at the beginning of the line.
Help me fix the permissions on my cleanup script
Schedule the script with crontab
A crontab is the file where you define your scheduled jobs. Each line follows a five-field time pattern followed by the command to run.
What is cron syntax?
Every cron entry has five time fields, then a command:
# minute hour day-of-month month day-of-week command
# (0-59) (0-23) (1-31) (1-12) (0-7) (what to run)
- An asterisk (*) means "every" for that field.
- A number means "only at this value" (e.g., 0 in the minute field means "at minute zero").
- Day-of-week uses 0 or 7 for Sunday, 1 for Monday, through 6 for Saturday.
- Create a crontab file with three schedule entries by running this command:
cat > /tmp/my-crontab << 'EOF'
# Run every minute (for testing only)
* * * * * /usr/local/bin/cleanup.sh
# Run daily at 2:00 AM
0 2 * * * /usr/local/bin/cleanup.sh
# Run weekly on Sunday at 3:00 AM
0 3 * * 0 /usr/local/bin/cleanup.sh
EOF
What do these three entries mean?
- * * * * * means every minute of every hour of every day. This is only for testing so you can quickly see cron working.
- 0 2 * * * means at minute 0, hour 2 (2:00 AM), every day.
- 0 3 * * 0 means at 3:00 AM on Sunday (day 0).
- Install the crontab by running:
crontab /tmp/my-crontab
- Verify your crontab was installed by running:
crontab -l
You should see all three schedule entries printed to the terminal.
Seeing "no crontab for root"?
This means the crontab file was not loaded. Make sure the file at /tmp/my-crontab exists by running cat /tmp/my-crontab. If it's empty, recreate it with the heredoc command above, then run crontab /tmp/my-crontab again.
Help me install my crontab in Ubuntu
Verify cron execution and update the schedule
The every-minute entry is running right now. You can confirm this by watching the log file your script writes to.
- Wait about 60 seconds, then check the log file by running:
cat /var/log/cleanup.log
You should see multiple "Cleanup complete" entries with different timestamps. Each one is a separate cron execution.
- Confirm the cron service itself is healthy by running:
/etc/init.d/cron status
Why check both the log and the service status?
The service status tells you cron is running as a process. The log file tells you your specific job actually executed. A running cron service with no log entries means your crontab has an error or the script path is wrong.
The every-minute schedule is useful for testing, but you would never leave it running on a production server. Time to update to a sensible interval.
- Set vim as your default editor by running:
export EDITOR=vim
- Open your crontab for editing by running:
crontab -e
What is crontab -e?
The crontab -e command opens your crontab in a text editor. When you save and exit, cron automatically picks up the changes. This is the standard way to manage cron schedules on a Linux server.
- In vim, move your cursor to the line that starts with * * * * * (the every-minute entry).
- Press dd to delete that line.
- Also delete the comment line above it (# Run every minute) by pressing dd again.
- Save and exit by pressing Esc, then typing :wq and pressing Enter.
- Verify the updated schedule by running:
crontab -l
You should now see only the daily (2:00 AM) and weekly (Sunday 3:00 AM) entries. The every-minute test line is gone.
Still seeing the every-minute entry?
If you exited vim without saving (using :q! instead of :wq), your changes were discarded. Run crontab -e again, delete the line with dd, and save with :wq.
Help me edit my crontab with vim
Vim basics for crontab editing
- Use j and k to move up and down.
- Press dd to delete the current line.
- Press i to enter insert mode (for typing new text).
- Press Esc to return to command mode before saving.
Your server now cleans itself up on autopilot. Next up, you'll diagnose a disk space issue, putting your file-finding and text-processing skills to the test.
Troubleshoot a Disk Space Issue
Your cron jobs are now handling scheduled maintenance automatically. In this step, you'll face every sysadmin's recurring challenge: a server running out of disk space.
You'll simulate a realistic disk space problem, then use diagnostic tools to track down the offending files and reclaim the space. This maps directly to the LFCS Essential Commands domain, which specifically lists "Troubleshoot diskspace issues" as a required skill.
In this step, get ready to:
- Create a simulated disk space problem with large files spread across directories.
- Diagnose the issue using filesystem and file search tools.
- Resolve the problem and document your findings in a report file.
Create a simulated disk space problem
In a real server, disk space issues happen gradually as logs grow, build artifacts pile up, and debug dumps get forgotten. You'll recreate this scenario by generating large files in different directories.
- Generate large files scattered across your filesystem by running these commands:
# Create a 50MB file using dd (writes zeros byte-by-byte)
dd if=/dev/zero of=/opt/projects/largefile1.img bs=1M count=50
# Create a 100MB file using fallocate (allocates space instantly)
fallocate -l 100M /var/log/old-app.log
# Simulate a forgotten build artifact
fallocate -l 75M /opt/projects/webapp/build-cache.tar
# Simulate a debug dump in /tmp
dd if=/dev/zero of=/tmp/debug-dump.raw bs=1M count=60
What do these commands do?
You used two different tools to create large files.
- dd copies data block by block. Here it reads from /dev/zero (an endless stream of zeros) and writes it to a file. The bs=1M count=50 means "write 50 blocks of 1 megabyte each."
- fallocate -l allocates disk space instantly without writing any data. It is much faster than dd for creating large placeholder files.
- Confirm the files were created by running:
ls -lh /opt/projects/largefile1.img /var/log/old-app.log /opt/projects/webapp/build-cache.tar /tmp/debug-dump.raw
You should see four files listed with sizes of 50M, 100M, 75M, and 60M respectively.
Seeing 'No space left on device'?
Your container's filesystem may genuinely be too small. Try reducing the file sizes in the commands above (change count=50 to count=10 and 100M to 20M). The exercise works the same way with smaller files.
Help me troubleshoot my container disk space
Diagnose the disk space issue
Now pretend you just received an alert that the server is running low on disk space. Your job is to find out what is consuming it. You will work from a high-level overview down to specific files.
- Check overall filesystem usage by running:
df -h
What does df -h show?
df -h displays filesystem disk space usage in human-readable format (KB, MB, GB). Look at the Use% column to see how full each filesystem is. The root filesystem / is the one you care about here.
- Identify which top-level directories are consuming the most space by running:
du -sh /*
What does du -sh show?
du -sh /* shows the total size of each top-level directory. The -s flag summarizes (rather than listing every file), and -h makes sizes human-readable. You should see /opt and /var standing out as larger than expected.
- Locate all files larger than 10MB anywhere on the system by running:
find / -type f -size +10M
You should see the four files you created listed in the output, along with any system files that happen to be larger than 10MB.
- Get a sorted breakdown of the largest items in /opt by running:
du -a /opt | sort -rn | head -20
What does this pipeline do?
This chains three commands together with pipes.
- du -a /opt lists sizes of ALL files and directories under /opt.
- sort -rn sorts the output by size in reverse numerical order (largest first).
- head -20 shows only the top 20 entries so you see the biggest offenders at a glance.
This pattern is extremely common in real sysadmin work. Memorize it for the LFCS exam.
Resolve the issue and document your findings
You have identified the problem files. Now you need to decide what to do with each one. Some can be deleted outright. Others (like logs) should be compressed to preserve the data while reclaiming space.
- Remove the files that are clearly unnecessary by running:
# Remove the placeholder image file (no real data)
rm /opt/projects/largefile1.img
# Remove the debug dump from /tmp (temporary by nature)
rm /tmp/debug-dump.raw
Why not delete everything?
In real scenarios, you cannot blindly delete large files. Log files may contain important audit data. Build artifacts may be needed for rollback. The sysadmin's job is to triage: delete what is disposable, compress what should be kept, and leave what is actively needed.
- Compress the old application log using tar with gzip to preserve it while saving space:
# Archive and compress the log file
tar -czf /var/log/old-app.log.tar.gz /var/log/old-app.log
# Remove the original now that it's compressed
rm /var/log/old-app.log
What does tar -czf do here?
tar -czf creates (-c) a gzip-compressed (-z) archive file (-f). The compressed .tar.gz file will be a fraction of the original 100MB. You practised this same command back in Step 2.
- Verify that space was reclaimed by running:
df -h
Compare the Use% column to what you saw earlier. You should see the usage percentage has dropped now that approximately 210MB of files have been removed or compressed.
- Document your findings by writing a report file. Run these commands to create /root/disk-report.txt:
# Create the report file with a header
echo "Disk Space Report - $(date)" > /root/disk-report.txt
echo "=========================" >> /root/disk-report.txt
# Document what you found
echo "" >> /root/disk-report.txt
echo "Large files found:" >> /root/disk-report.txt
echo "- /opt/projects/largefile1.img (50MB) - removed" >> /root/disk-report.txt
echo "- /var/log/old-app.log (100MB) - compressed to .tar.gz" >> /root/disk-report.txt
echo "- /opt/projects/webapp/build-cache.tar (75MB) - kept (needed for builds)" >> /root/disk-report.txt
echo "- /tmp/debug-dump.raw (60MB) - removed" >> /root/disk-report.txt
# Document actions and results
echo "" >> /root/disk-report.txt
echo "Actions taken:" >> /root/disk-report.txt
echo "- Removed unnecessary files (largefile1.img, debug-dump.raw)" >> /root/disk-report.txt
echo "- Compressed old logs with tar/gzip" >> /root/disk-report.txt
echo "- Space reclaimed: approximately 210MB" >> /root/disk-report.txt
What is the difference between > and >>?
> overwrites the file (or creates it if it does not exist). >> appends to the end of the file without erasing what is already there. The first line uses > to start fresh, and every line after uses >> to add content below.
- Verify your report by viewing its contents:
cat /root/disk-report.txt
You should see your full report printed to the terminal with the date, list of files found, and actions taken.
Report file is empty or missing?
Check that your first echo command used > (single angle bracket) and all subsequent lines used >> (double angle bracket). If you accidentally used > on a later line, it overwrote everything above it with just that one line.
Help me fix my disk report file
Secret mission
Configure User Resource Limits and Advanced ACLs
Your server is functional, but it's not hardened. What happens when a user accidentally forks a process bomb, or when teams need overlapping access without messy group memberships? Take your server to production-grade by configuring per-user resource limits and building inherited ACL structures.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your Mac using Apple's container CLI. There are no cloud resources or ongoing costs.
Decide whether to keep your container running, pause it to come back later, or delete it entirely.
Resources you used:
- The linux-lab Ubuntu 24.04 container.
- The Ubuntu 24.04 container image stored locally.
Keep everything running
No action needed. Choose this if you plan to keep practicing Linux commands or want to start the next project in the series.
- Your linux-lab container stays running and you can reconnect at any time.
- To reconnect, open your terminal and run:
container exec -ti linux-lab bash
- All your users, files, permissions, and cron jobs remain intact.
Pause - I'll come back to this later
Stop the container to free up system resources. Your work is preserved inside the stopped container.
- Stop the container by running:
container stop linux-lab
- Verify it stopped by running:
container ls -a
You should see linux-lab listed with a stopped state.
- When you are ready to resume, start it again by running:
container start linux-lab
- Then reconnect with:
container exec -ti linux-lab bash
Delete - I don't want to use this again
Remove the container and image completely. This deletes all the users, files, and configurations you created inside the container.
- Stop the container by running:
container stop linux-lab
- Delete the container by running:
container rm linux-lab
- Remove the Ubuntu image by running:
container image delete ubuntu:24.04
- Verify everything is gone by running:
container ls -a
You should see no linux-lab entry in the output.
- Optionally, stop the container service entirely by running:
container system stop
Nice Work!
Nice Work!
You just configured a Linux server from scratch. That is real sysadmin work.
You've learned how to:
- Built a reusable Linux lab environment on your Mac using Apple's container CLI.
- Created and managed user accounts and groups, then secured shared directories with permissions and ACLs.
- Installed packages, managed processes, scheduled cron jobs, and diagnosed a simulated disk space issue.
- Secret Mission: Configured per-user resource limits and advanced inherited ACL structures.
Ready to quiz yourself?