Configure a Multi-User Linux Server

Set up users, permissions, SSH, and services on a Linux server.

Introduction

30 Second Summary

Every server in the cloud starts as a blank slate. Someone has to create user accounts, lock down who can access what, and make sure the right services are running.

In this project, you will configure a Linux server for a small development team. You will create users, set file permissions, configure SSH access, manage services, and write a bash script that audits the entire setup.

What You'll Build

A fully configured multi-user Linux environment with proper permissions, SSH key-based authentication, managed system services, and an audit script that reports on the whole setup.

By the end of this project, you'll have:

  • User accounts with proper group membership, file permissions, and sudo access for a shared project directory.
  • SSH key-based authentication configured and tested for secure passwordless login.
  • A bash audit script that uses text processing tools like grep and awk to report on users, permissions, and services.
  • Secret Mission: An automated user onboarding script that adds new team members with full permissions and SSH setup.

Are there any prerequisites?

This project requires Windows 10 or 11 with WSL 2 (Windows Subsystem for Linux) already installed with an Ubuntu distribution. You also need VS Code installed on Windows.

Before We Start

Before We Start...

Set Up Your Environment

Every Linux server needs a working init system, a code editor connected to the filesystem, and up-to-date packages before you can start configuring anything.

In this step, you will verify that systemd is active in your WSL environment, connect VS Code to your Linux filesystem, and update all system packages.

In this step, get ready to:
  • Verify systemd is running in your WSL Ubuntu terminal.
  • Install the VS Code WSL extension and create your project directory.
  • Update your system packages to the latest versions.
Open your WSL terminal and verify systemd

Systemd is the init system that manages services (like SSH servers and web servers) on Linux. Many of the commands you will use later in this project depend on systemd being active.

  • Press the Windows key to open your search bar.
  • Type Ubuntu and press Enter to open your WSL terminal.
  • Check if systemd is running by running this command:
systemctl status

What does this command do?

systemctl status queries the systemd service manager for the overall system state. If systemd is active, you will see output including State: running or similar status information.

Check your output against one of these scenarios:

✔️ I see status output with no errors

Systemd is active and running. You are ready to move on to the next substep.

ⓧ I see "System has not been booted with systemd"

Systemd is not enabled yet. You need to edit your WSL configuration file to turn it on.

  • Open the WSL configuration file by running this command:
sudo nano /etc/wsl.conf
  • Add these two lines to the file:
[boot]
systemd=true
  • Save the file by pressing Ctrl+O, then press Enter to confirm.
  • Exit nano by pressing Ctrl+X.
  • Close your WSL terminal.
  • Open PowerShell on Windows (press the Windows key, type PowerShell, press Enter).
  • Shut down WSL completely by running this command:
wsl --shutdown

What does wsl --shutdown do?

This command closes all running WSL distributions. Changes to /etc/wsl.conf only take effect after a full restart of WSL.

  • Reopen your Ubuntu terminal (press the Windows key, type Ubuntu, press Enter).
  • Verify systemd is now running:
systemctl status

You should now see status output without any error messages about systemd not being available.

Still seeing the same error?

Make sure you saved /etc/wsl.conf correctly with Ctrl+O before exiting nano. You can verify the file contents by running cat /etc/wsl.conf and checking that both [boot] and systemd=true appear on separate lines.

Also confirm you ran wsl --shutdown from PowerShell (not from inside WSL itself). Help me troubleshoot systemd not starting in WSL

Install the VS Code WSL extension and create your project

The VS Code WSL extension lets you edit Linux files directly from VS Code on Windows. This is the same workflow cloud engineers use when connecting VS Code to remote servers over SSH.

  • Press the Windows key to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open the Extensions panel by pressing Ctrl+Shift+X.
  • Type WSL in the search bar at the top of the Extensions panel.
  • Find the extension named WSL by Microsoft and click Install.

What does the WSL extension do?

Once installed, this extension allows you to run code . from any WSL terminal directory. VS Code opens on Windows but reads and writes files directly on the Linux filesystem.

  • Switch back to your WSL Ubuntu terminal.
  • Create your project directory and move into it by running this command:
mkdir -p ~/projects/server-config && cd ~/projects/server-config
  • Confirm the directory was created by running:
pwd

You should see /home/your-username/projects/server-config printed in the terminal.

  • Open this directory in VS Code by running:
code .

VS Code opens with the server-config folder loaded. Look at the bottom-left corner of the VS Code window. You should see a green indicator showing WSL: Ubuntu (or your distribution name). This confirms VS Code is connected to your Linux filesystem.

VS Code not opening from the terminal?

If you see a "command not found" message, VS Code may not be added to your system PATH. Close and reopen your WSL terminal, then try code . again.

If that does not work, make sure the WSL extension is installed in VS Code on the Windows side. The first time you run code . from WSL, it installs a VS Code Server component on the Linux side. This may take a moment. Help me get code . working in my WSL terminal

Update your system packages

The first command you run on any new Linux server is a package update. This refreshes the list of available packages and installs newer versions of everything already on the system.

  • Update and upgrade all packages by running this command:
sudo apt update && sudo apt upgrade -y

What does this command do?

sudo apt update refreshes the list of available packages from the internet. It checks what new versions exist but does not install anything yet.

sudo apt upgrade -y installs newer versions of all packages you already have. The -y flag automatically answers "yes" to confirmation prompts so you do not have to type it manually.

This may take a minute or two depending on how many packages need updating. When it finishes, you will see a summary line showing how many packages were upgraded.

Seeing a connection error?

If you see errors about being unable to fetch packages, your WSL instance might not have internet access. Try running ping google.com to test your connection. Press Ctrl+C to stop the ping.

If the ping fails, try restarting WSL with wsl --shutdown from PowerShell, then reopening your Ubuntu terminal. Help me fix network issues in WSL

Your environment is ready. Next up, you will explore the Linux filesystem and learn how to navigate it like a cloud engineer.

Navigate the Linux Filesystem

Your environment is ready to go. You have a working WSL terminal with systemd, VS Code connected to your Linux filesystem, and up-to-date packages.

Now it is time to learn how to move around inside Linux. Every task you do on a server starts with knowing where you are in the filesystem and how to get where you need to be. In this step, you will explore the directory hierarchy, create a project folder structure, and practice the core file commands.

In this step, get ready to:
  • Explore the Linux directory tree using pwd, cd, and ls.
  • Create a structured project directory with subdirectories.
  • Practice file operations including creating, copying, moving, and deleting files.
Learn where you are and how to move

The Linux filesystem is a single tree rooted at /. Everything on the system lives somewhere in that tree. Three commands get you everywhere you need to go.

  • Check your current location by running this command:
pwd

What does pwd do?

pwd stands for "print working directory." It shows the full path of where you are right now. You should see something like /home/youruser/projects/server-config since that is where your terminal opened.

  • Navigate to the root of the filesystem and list what is there by running these commands:
cd /
ls

You should see a set of top-level directories like bin, etc, home, var, and more.

  • Explore four key directories by running these commands:
ls /etc
ls /var/log
ls /home
ls /usr/bin

What do these directories contain?

  • /etc holds system configuration files. This is where you find settings for services, users, and networking.
  • /var/log stores system logs. When something breaks, you look here for clues.
  • /home contains user home directories. Each user account on the server gets their own folder here.
  • /usr/bin is where installed programs live. Commands like grep, awk, and less are stored here.
Create a directory structure for your project

Real servers organize files into logical directory structures. You will now create a shared project folder with subdirectories for documentation, source code, and configuration.

  • Return to your project directory by running this command:
cd ~/projects/server-config
  • Create the shared project directory with three subdirectories by running this command:
mkdir -p shared-project/{docs,src,config}

What does this command do?

mkdir creates directories. The -p flag creates parent directories as needed and does not produce an error if the directory already exists.

The curly braces {docs,src,config} are called brace expansion. The shell expands this into three separate paths: shared-project/docs, shared-project/src, and shared-project/config.

  • Verify the structure was created by running:
ls -R shared-project/

You should see shared-project/ listed with config, docs, and src as subdirectories.

  • Install the tree command for a visual directory view by running:
sudo apt install tree -y
  • View your directory structure as a tree by running:
tree shared-project/

You should see a tree diagram showing shared-project at the top with three branches for config, docs, and src.

Tree command not found?

If you see an error after running sudo apt install tree, make sure you ran sudo apt update in Step 1. Run it again now and retry the install.

help me troubleshoot installing packages with apt in WSL

Practice file operations

Now that you have a directory structure, you will practice the commands for creating, viewing, copying, moving, and deleting files. These are the operations you will use constantly on any server.

  • Create a README file and add content to it by running these commands:
touch shared-project/docs/README.md
echo "# Server Config Project" > shared-project/docs/README.md

What do these commands do?

touch creates an empty file (or updates its timestamp if it already exists). echo with > writes text into a file, replacing any existing content.

Together they show two ways to create files from the terminal. In practice, you will use echo > or a text editor to create files with content in a single step.

  • Verify the file was created with the correct content by running:
cat shared-project/docs/README.md

You should see # Server Config Project printed in your terminal.

  • Practice copying a file by running:
cp shared-project/docs/README.md shared-project/docs/README-backup.md
  • Practice moving (renaming) a file by running:
mv shared-project/docs/README-backup.md shared-project/docs/BACKUP.md
  • Delete the backup file by running:
rm shared-project/docs/BACKUP.md
  • Create a test directory and then delete it recursively by running these commands:
mkdir shared-project/temp-test
rm -r shared-project/temp-test

Why is rm -r different from rm?

rm by itself only deletes individual files. Directories contain other files, so you need the -r (recursive) flag to delete a directory and everything inside it.

Be careful with rm -r. There is no recycle bin on a Linux server. Deleted files are gone permanently.

  • Confirm your final project structure by running:
tree shared-project/

You should see shared-project with three directories (config, docs, src) and one file (README.md inside docs).

Missing files or directories?

If your tree output does not match, double check that you ran the commands from inside ~/projects/server-config. Run pwd to confirm your location. If you accidentally deleted something, re-run the mkdir -p and echo commands from above to recreate them.

help me fix my directory structure in Linux

You can now navigate the filesystem confidently and manage files and directories. Next up, you will install and manage software packages on your server.

Install and Manage Packages

You can now navigate the Linux filesystem and create directory structures from the command line. The next skill every cloud engineer needs is installing software on a server.

Linux uses a package manager to install, update, and remove software. On Ubuntu, the package manager is called apt. In this step, you will install tools that cloud engineers use daily and learn the core commands for managing packages on any Ubuntu server.

In this step, get ready to:
  • Install packages using the apt package manager.
  • Understand the core package management commands.
  • Verify installed packages using dpkg and apt show.
Learn the apt package manager

Every Ubuntu server comes with apt pre-installed. It connects to online repositories of software and handles downloading, installing, and updating packages for you.

  • Check how many packages are already installed on your system by running this command:
apt list --installed | wc -l

You should see a number printed to the terminal. This is the total count of packages currently installed on your system.

  • Search for a package called htop by running:
apt search htop

You should see a list of packages related to htop with short descriptions next to each. This is how you discover available software before installing it.

  • Install three useful tools by running this command:
sudo apt install htop net-tools openssh-server -y

What does this command do?

  • sudo runs the command with administrator privileges. Installing software requires elevated permissions.
  • apt install tells the package manager to download and install the named packages.
  • htop is an interactive process viewer (you will use it in a later step).
  • net-tools provides classic networking commands like ifconfig.
  • openssh-server lets your machine accept incoming SSH connections. You will configure this for remote access later.
  • -y automatically answers yes to confirmation prompts so the install runs without pausing.
  • Verify that htop installed correctly by running:
which htop

You should see a file path like /usr/bin/htop. This confirms the system knows where the program lives.

  • Check the installed version by running:
htop --version

You should see the version number printed to the terminal.

Install command failing?

  • If you see "Unable to locate package", run sudo apt update first to refresh the package list, then try the install command again.
  • If you see "Permission denied", make sure you included sudo at the start of the command.
  • If your terminal seems frozen during install, give it a moment. Large packages take time to download depending on your connection speed.

Why is my apt install command failing?

Understand package management commands

You already ran sudo apt update && sudo apt upgrade -y in Step 1. Now that you have seen apt install in action, here is the full set of commands you will use regularly.

What are the core apt commands?

  • sudo apt update refreshes the package list. It checks what software is available and at what versions. It does not install anything.
  • sudo apt upgrade installs newer versions of packages you already have. Always run update before upgrade.
  • sudo apt install <package> downloads and installs a new package.
  • sudo apt remove <package> uninstalls a package but keeps its configuration files.
  • sudo apt autoremove cleans up packages that were installed as dependencies but are no longer needed.

Run update first on any new server

The first command you should always run on a new Linux server is sudo apt update && sudo apt upgrade -y. This ensures you are working with the latest security patches and package versions before installing anything else.

Check what you installed

When you log into a server that someone else set up, you need to know what software is already installed. Two commands help you investigate.

  • Search for OpenSSH packages on the system by running:
dpkg -l | grep openssh

You should see a table listing all installed packages with "openssh" in their name. Each row shows the package status, name, version, architecture, and a short description.

  • Get detailed information about the openssh-server package by running:
apt show openssh-server

You should see a detailed view including the version, size, dependencies, and a description of what the package does.

When would I use these commands?

Imagine you join a team and get access to a production server. You need to know what software is running before making changes.

dpkg -l | grep <name> quickly confirms whether a specific package is installed. apt show <package> gives you the full story including what version is running and what it depends on.

You now know how to install, remove, and investigate packages on any Ubuntu server. Next up, you will create user accounts and groups to manage who can access this server.

Create Users and Groups

Your system is loaded with the tools you need. Now it's time to think about who can access this server and what they're allowed to do.

On a real server, every team member gets their own user account. Groups let you manage permissions for multiple people at once instead of configuring each user individually. In this step, you'll create user accounts, organize them into a group, and configure access levels.

In this step, get ready to:
  • Examine how Linux stores user account information.
  • Create team member accounts and organize them into a shared group.
  • Configure sudo privileges and practice switching between users.
Examine existing users

Before creating new accounts, you need to understand how Linux tracks its users. Every user account on the system is recorded in a single file.

  • View all user accounts on your system by running this command:
cat /etc/passwd

What is /etc/passwd?

This file stores one line per user account. Each line has seven fields separated by colons. The format is username:x:UID:GID:comment:home:shell.

The x means the password is stored in a separate shadow file. The UID is the numeric user ID, the GID is the primary group ID, and the shell is the program that runs when the user logs in.

You'll notice most accounts have /usr/sbin/nologin or /bin/false as their shell. These are system accounts that cannot log in interactively.

  • Filter the list to show only users who can actually log in by running:
grep -v "nologin\|false" /etc/passwd

What does this command do?

The -v flag inverts the match. Instead of showing lines that contain the pattern, it shows lines that do NOT contain it. The \| is an OR operator in the pattern, so this filters out any line containing "nologin" or "false".

  • Check what groups your own account belongs to by running:
groups
  • Get more detailed information about your account (UID, GID, and all group memberships) by running:
id

You should see your username, a numeric UID, a primary GID, and a list of supplementary groups your account belongs to.

Seeing a very long list in /etc/passwd?

That's normal. Most Linux systems have dozens of system accounts for running background services. The filtered grep -v output should show only a handful of real users.

help me understand the /etc/passwd file format

Create team members

Your server needs accounts for two developers who will share project files. You'll create their accounts, then organize everyone into a group.

  • Create the first developer account by running:
sudo adduser developer1

What will adduser ask me?

The adduser command is interactive. It will prompt you to set a password for the new user. Type a simple password you can remember (you'll use it later to switch users).

After the password, it asks for optional fields like Full Name, Room Number, and Phone. Press Enter to skip each one, then type Y to confirm.

  • Create the second developer account by running:
sudo adduser developer2
  • Follow the same prompts. Set a password and press Enter through the optional fields.
  • Verify both users exist by running:
id developer1 && id developer2

You should see UID, GID, and group information for both accounts.

Seeing "user already exists"?

If you ran adduser before and the user already exists, that's fine. The account is already created and you can continue to the next command.

help me fix an issue creating users with adduser

Now you need a group to organize these users together. Groups let you assign permissions to multiple users at once instead of managing each account individually.

  • Create a group called devteam by running:
sudo groupadd devteam
  • Add both developers and yourself to the group by running these commands:
sudo usermod -aG devteam developer1
sudo usermod -aG devteam developer2
sudo usermod -aG devteam $USER

What does -aG mean?

The -aG flag is two options combined. The -a means "append" (add to the group without removing from other groups). The -G specifies the group name. Without -a, the user would be removed from all other supplementary groups.

  • Verify the group membership by running:
groups developer1
groups developer2

You should see devteam listed among the groups for both users.

Don't see devteam in the groups output?

Make sure you included the -a flag in usermod -aG. Without it, the command replaces group memberships instead of appending. If the group itself doesn't exist, re-run sudo groupadd devteam first.

help me troubleshoot group membership in Linux

Understand sudo and privileges

Not every user should have full administrative access. Linux uses the sudo group to control which users can run commands as root.

  • View the sudo configuration file by running:
sudo cat /etc/sudoers

Why cat instead of nano?

The /etc/sudoers file controls who has root access to the entire system. A typo in this file can lock everyone out. Always read it with cat and never edit it directly. If you ever need to edit it, use visudo which validates the syntax before saving.

  • Check which users are currently in the sudo group by running:
grep sudo /etc/group

You should see a line starting with sudo:x: followed by a list of usernames who have administrative access.

  • Give developer1 sudo (admin) access by running:
sudo usermod -aG sudo developer1
  • Verify developer1 now has sudo access by running:
grep sudo /etc/group

You should now see developer1 listed in the sudo group line.

Now practice switching between user accounts. This is how you test whether permissions are working correctly.

  • Switch to the developer1 account by running:
su - developer1
  • Enter the password you set for developer1 when prompted.
  • Confirm you are now developer1 by running:
whoami

You should see developer1 printed in the terminal.

  • Return to your original account by typing:
exit

What does su - do?

The su command means "switch user". The - (dash) tells it to start a full login shell, loading that user's environment variables and home directory. Without the dash, you'd switch users but stay in the current directory with the old environment.

Authentication failure when switching users?

Make sure you are typing the password you set during adduser, not your own account's password. The password does not appear as you type (no dots or asterisks). That is normal Linux behavior.

If you forgot the password, reset it with sudo passwd developer1 from your main account.

help me switch users in Linux

Your server now has named accounts with proper group organization. Next, you'll use these groups to control exactly who can read, write, and execute files in the shared project directory.

Set File Permissions and Ownership

Your team members exist on the server, but right now there is nothing stopping anyone from reading or modifying files they should not touch. Linux file permissions control exactly who can do what with every file and directory on the system.

In this step, you will lock down the shared project directory so only devteam members can access it. You will also configure it so new files automatically inherit the correct group ownership.

In this step, get ready to:
  • Understand the Linux permission model (rwx and octal notation).
  • Set group ownership and permissions on the shared project directory.
  • Test that permissions work correctly by switching between users.
Understand the permission model

Every file and directory in Linux has three permission groups: owner, group, and others. Each group gets a combination of read (r), write (w), and execute (x) access.

  • View the permissions on your shared project directory by running this command:
ls -la ~/projects/server-config/shared-project/

How to read the permission string

Each listing starts with a 10-character permission string like drwxr-xr-x.

  • The first character is the type: d for directory, - for file.
  • Characters 2-4 are the owner permissions (rwx).
  • Characters 5-7 are the group permissions (rwx).
  • Characters 8-10 are the permissions for everyone else (rwx).

Permissions also have numeric (octal) values: r=4, w=2, x=1. You add them together for each group. So 755 means rwxr-xr-x (owner gets 7, group gets 5, others get 5).

Common patterns: 644 for regular files, 755 for directories, 600 for private keys.

Notice that the current permissions probably allow "others" to read your shared directory. You will fix that next.

Set up the shared project directory with proper group ownership

Right now, your shared directory is owned by your personal user account. For a team project directory, it needs to be owned by the devteam group so all team members have equal access.

  • Change the ownership of the shared directory to root (owner) and devteam (group) by running this command:
sudo chown -R root:devteam ~/projects/server-config/shared-project/

What does this command do?

  • sudo runs the command as root (required to change ownership).
  • chown -R changes ownership recursively (all files and subdirectories).
  • root:devteam sets the owner to root and the group to devteam.
  • Set the directory permissions to 770 (full access for owner and group, nothing for others) by running:
sudo chmod -R 770 ~/projects/server-config/shared-project/

Why 770?

The permission 770 means rwxrwx--- in the permission string. The owner (root) and group (devteam) get full read, write, and execute access. Everyone else gets nothing.

  • Verify the ownership and permissions changed by running:
ls -la ~/projects/server-config/shared-project/

You should see root as the owner, devteam as the group, and permission strings starting with drwx (no permissions for others).

Still seeing your username as the owner?

  • Make sure you included sudo at the start of the chown command. Without it, you do not have permission to change ownership.
  • Check that you typed root:devteam with a colon separating the owner and group.

Help me troubleshoot my chown command

There is one more problem to solve. When a devteam member creates a new file in this directory, the file's group will default to their personal group instead of devteam. The SGID bit fixes this by making new files inherit the directory's group.

  • Set the SGID bit on the shared project directory by running:
sudo chmod g+s ~/projects/server-config/shared-project/

What is the SGID bit?

The SGID (Set Group ID) bit on a directory means every new file or subdirectory created inside it automatically inherits the directory's group. Without it, new files belong to the creator's primary group, which breaks shared access for the team.

  • Verify the SGID bit is set by running:
ls -la ~/projects/server-config/

Look at the shared-project entry. You should see an s in the group-execute position (e.g. drwxrws---) instead of a plain x.

Test permissions by switching between users

The only way to truly verify permissions work is to switch between users and see what they can (and cannot) do. You will now test access as both a devteam member and a non-member.

  • Switch to developer1 (a devteam member) by running:
su - developer1

Enter the password you created for developer1 in the previous step.

  • Navigate to the shared directory and try creating a file by running these commands:
cd ~/projects/server-config/shared-project/
touch docs/test-from-dev1.txt
ls -la docs/

The file should be created successfully because developer1 is in the devteam group. Notice the file's group is set to devteam (thanks to the SGID bit).

  • Exit back to your own user by typing:
exit
  • Switch to developer2 and verify they also have access by running:
su - developer2
  • Try navigating to the shared directory by running:
cd ~/projects/server-config/shared-project/
ls

Developer2 can also access the directory because they are in the devteam group.

  • Exit back to your own user by typing:
exit

Now test that someone outside the devteam group gets blocked.

  • Create a temporary user who is NOT in the devteam group by running:
sudo adduser testuser

Follow the prompts to set a password. Leave the other fields blank and press Enter through them.

  • Switch to the test user and try to access the shared directory:
su - testuser
cd /home/$SUDO_USER/projects/server-config/shared-project/

You should see Permission denied. This confirms your permissions are working correctly. Only devteam members can access the shared directory.

  • Exit back to your user and remove the test user by running:
exit
sudo deluser testuser

Not seeing Permission denied?

  • Make sure you ran sudo chmod -R 770 on the shared-project directory. If "others" still have read access, the permission change did not apply.
  • Verify that testuser is NOT in the devteam group by running groups testuser before switching to them.
  • Check that the path you used is correct. The directory lives at ~/projects/server-config/shared-project/.

Help me troubleshoot my file permission test

Your shared directory is now properly locked down. Next up, you will learn to manage processes and services running on your server.

Manage Processes and Services

Your file permissions are locked down and your shared directory only lets the right people in. Now it is time to manage what is actually running on your server.

Every Linux server runs dozens of background processes and services. As a cloud engineer, you need to know how to see what is running, start and stop services, and kill stuck processes. In this step, you will view processes, manage the SSH service with systemctl, and practice terminating processes safely.

In this step, get ready to:
  • View running processes with multiple tools.
  • Start, stop, and enable the SSH service.
  • Kill processes using SIGTERM and SIGKILL.
View running processes

A process is any program currently executing on your system. Linux gives you several tools to inspect what is running, how much memory each process uses, and which user owns it.

  • See the first 20 running processes by running this command:
ps aux | head -20

What does this command do?

ps aux shows all running processes with their user, PID, CPU usage, and memory usage. Piping it to head -20 limits the output to the first 20 lines so you can read it comfortably.

  • Filter for SSH-related processes by running:
ps aux | grep ssh

You should see any SSH-related processes listed. If the SSH server is not started yet, you may only see the grep command itself in the results.

  • Open the live process viewer by running:
top
  • Watch the process list update in real time. Press q to quit.
  • Try the improved process viewer by running:
htop
  • Explore the color-coded interface. Press q to quit when you are done.

What is the difference between top and htop?

top is included on every Linux system by default. htop is an enhanced version with color coding, easier navigation, and mouse support. Both show the same information. Cloud engineers typically install htop as one of their first tools on a new server.

  • Find SSH process IDs specifically by running:
pgrep -a ssh

This outputs only the PIDs and command names for SSH processes. It is faster than piping ps aux through grep when you just need the PID.

Not seeing any SSH processes?

That is normal if the SSH server has not been started yet. You will start it in the next substep. The pgrep command returns nothing when no matching processes exist.

Help me understand why pgrep returns no results for SSH

Manage services with systemctl

Services are long-running background programs that start automatically and keep your server functional. The systemctl command is how you control them.

  • Start the SSH server by running:
sudo systemctl start ssh
  • Check that the SSH service is running by running:
sudo systemctl status ssh

You should see green text that says "active (running)" in the output. Press q to exit the status view.

  • Enable the SSH service to start automatically at boot by running:
sudo systemctl enable ssh

What is the difference between start and enable?

start runs the service right now. enable tells the system to start it automatically every time the machine boots. You typically use both together when setting up a new server.

  • Practice the full service lifecycle. Stop the SSH service by running:
sudo systemctl stop ssh
  • Start it again by running:
sudo systemctl start ssh
  • Restart the service (stop then start in one command) by running:
sudo systemctl restart ssh
  • Confirm the service is running again by running:
sudo systemctl status ssh

You should see "active (running)" again. Press q to exit.

  • List all currently running services by running:
systemctl list-units --type=service --state=running

You should see ssh.service in the list along with other system services.

SSH service won't start?

Make sure systemd is running. Run systemctl status on its own. If you see an error about systemd not being active, revisit Step 1 where you enabled it in /etc/wsl.conf.

Also verify that openssh-server is installed by running dpkg -l | grep openssh-server. If nothing appears, install it with sudo apt install openssh-server -y.

Help me fix my SSH service in WSL

Practice process control

Sometimes a process gets stuck or needs to be stopped manually. Linux provides signals you send to a process to control its behavior.

  • Start a background process that runs for 5 minutes by running:
sleep 300 &

What does the & symbol do?

The & at the end puts the command in the background so you get your terminal prompt back immediately. Without it, your terminal would be locked for 5 minutes waiting for sleep to finish.

  • Find the sleep process and note its PID by running:
ps aux | grep sleep

You should see a line showing the sleep 300 process. The second column is the PID (process ID). Note that number.

  • Kill the process gracefully by running this command, replacing the PID with the number you noted:
kill [[PID="the PID number from the ps output"]]
  • Confirm the process is gone by running:
ps aux | grep sleep

The sleep 300 process should no longer appear. You may see only the grep command itself in the output.

  • Start another background process to practice the forced kill by running:
sleep 300 &
  • Find the new PID by running:
ps aux | grep sleep
  • Force kill the process using SIGKILL by running this command with the new PID:
kill -9 [[PID="the PID number from the ps output"]]

When should I use kill -9?

kill sends SIGTERM, which asks the process to shut down gracefully. The process can save data and clean up before exiting.

kill -9 sends SIGKILL, which forces the process to terminate immediately. The process cannot catch or ignore this signal. Use it as a last resort when a process ignores the normal kill.

  • Verify the process is terminated by running:
ps aux | grep sleep

The process should be gone. You now know both ways to stop a runaway process on a Linux server.

Process did not terminate?

Make sure you used the correct PID from the ps aux | grep sleep output. The PID is the number in the second column. If you accidentally used the PID of the grep process itself, the sleep process will still be running.

Help me find and kill the right process

Your server now has the SSH service running and you know how to inspect and control any process. Next up, you will configure SSH key-based authentication so you can connect to your server without typing a password.

Configure SSH Key Authentication

Your server is running services and you know how to manage their lifecycle. The next challenge is configuring secure remote access so you can connect to servers without sitting in front of them.

Cloud engineers rarely sit at a physical server. They connect remotely using SSH (Secure Shell), which encrypts everything between your machine and the server. In this step, you'll generate SSH keys and set up passwordless authentication to localhost.

In this step, get ready to:
  • Generate an SSH key pair for secure authentication.
  • Connect to localhost via SSH to practice the remote access workflow.
  • Set up passwordless login using key-based authentication.
Generate an SSH key pair

SSH uses a pair of keys instead of a password. You have a private key (stays on your machine, never share it) and a public key (you copy it to any server you want to access).

  • Generate your SSH key pair by running this command:
ssh-keygen -t ed25519 -C [[YOUR_EMAIL="your-email@example.com"]]

What does this command do?

ssh-keygen creates a new SSH key pair. The -t ed25519 flag specifies the key type. ed25519 is a modern algorithm that is smaller, faster, and more secure than older RSA keys.

The -C flag adds a comment (your email) to help you identify the key later.

What will I see?

The command will prompt you for a file location and a passphrase. Press Enter to accept the default location (~/.ssh/id_ed25519).

For the passphrase, press Enter twice to skip it (no passphrase). In production you would set one, but for this practice environment skipping it keeps things simple.

  • Press Enter to accept the default file location.
  • Press Enter twice to skip the passphrase.
  • Examine your new keys by running:
ls -la ~/.ssh/

What should I see?

You should see two new files: id_ed25519 (your private key) and id_ed25519.pub (your public key).

The private key must have permissions 600 (only you can read/write it). If it shows -rw------- in the listing, you're good. Never share this file with anyone.

The public key (.pub) is safe to share freely. You copy it to servers you want to access.

Don't see the .ssh directory?

If ls -la ~/.ssh/ says "No such file or directory", the ssh-keygen command may not have run successfully. Try running it again and make sure you press Enter at each prompt.

help me troubleshoot why my SSH key was not generated

Connect to localhost via SSH

Since you installed openssh-server in a previous step, your WSL instance is running an SSH server. You can connect to your own machine to practice the exact workflow you would use with a cloud VM.

  • Connect to your own machine via SSH by running:
ssh localhost

What will I see?

The first time you connect, SSH asks if you trust the server's fingerprint. Type yes and press Enter.

Then it will ask for your password (your WSL user password). Type it in and press Enter. You won't see characters as you type. That's normal.

  • Type yes when asked about the fingerprint and press Enter.
  • Enter your WSL user password when prompted.
  • Confirm you are in an SSH session by running:
whoami && hostname
  • You should see your username and your machine's hostname printed.
  • Disconnect from the SSH session by typing:
exit

You are back in your regular terminal. This is exactly how you connect to AWS EC2 instances, Azure VMs, or any remote Linux server.

Connection refused?

If you see "Connection refused", the SSH service may not be running. Start it with sudo systemctl start ssh and try connecting again.

help me fix my SSH connection to localhost

Set up key-based authentication

Typing a password every time you connect is slow and less secure. Key-based authentication lets the server verify your identity using your key pair instead.

  • Copy your public key to the server (localhost) by running:
ssh-copy-id localhost

What does this command do?

ssh-copy-id reads your public key and appends it to the ~/.ssh/authorized_keys file on the target server. Once your public key is in that file, the server trusts your machine.

This is the last time you will need to enter your password for this connection.

  • Enter your password one last time when prompted.
  • Test your passwordless connection by running:
ssh localhost

You should be logged in immediately with no password prompt. This is how cloud engineers access servers securely.

  • View the authorized keys file to see your public key stored on the server:
cat ~/.ssh/authorized_keys

What should I see?

You should see a long string starting with ssh-ed25519 followed by a block of characters and ending with your email comment. That is your public key.

When you connect via SSH, the server checks if your private key mathematically matches any public key in this file. If it does, you get in without a password.

  • Disconnect from the SSH session by typing:
exit

Still being asked for a password?

Check that your private key has the correct permissions by running ls -la ~/.ssh/id_ed25519. It must show -rw------- (permissions 600). If not, fix it with chmod 600 ~/.ssh/id_ed25519.

Also verify that ~/.ssh/authorized_keys exists and contains your key by running cat ~/.ssh/authorized_keys.

help me troubleshoot why my SSH key authentication is not working

Your SSH authentication is fully configured. Next up, you'll use powerful text processing tools to search, filter, and transform system information.

Process Text with Linux Tools

Your server now has users, permissions, services, and SSH all configured. The next challenge is extracting useful information from this setup.

Linux stores its configuration in plain text files. Cloud engineers use text processing tools to quickly search logs, filter user lists, and extract exactly the data they need. In this step, you will learn the core tools that make this possible.

In this step, get ready to:
  • Read system files with cat, less, head, tail, and wc.
  • Search and filter text with grep.
  • Extract and transform data with awk and sed.
Work with real system files

The /etc/passwd file is one of the most important files on any Linux system. It contains a record of every user account. You will use this real file to practice every text tool.

  • Dump the entire contents of the file by running:
cat /etc/passwd

What does this output mean?

Each line represents one user account. The format is username:x:UID:GID:comment:home:shell. You will see system accounts (like root and daemon) alongside the users you created earlier.

That output scrolled past quickly. For longer files, use less to scroll at your own pace.

  • Open the file in a scrollable viewer by running:
less /etc/passwd
  • Use the arrow keys to scroll up and down.
  • Press q to quit and return to your terminal.

Sometimes you only need the beginning or end of a file. The head and tail commands let you grab a specific number of lines from either end.

  • View the first 5 lines of the file by running:
head -5 /etc/passwd
  • View the last 5 lines by running:
tail -5 /etc/passwd
  • Count the total number of lines (total user accounts) by running:
wc -l /etc/passwd

You should see a number followed by the filename. That number is the total count of user accounts on your system.

Why so many accounts?

Most of these are system accounts used by services (like sshd or www-data). They cannot log in. The users you created (developer1 and developer2) are at the bottom of the file.

Stuck inside less?

If your terminal seems frozen or unresponsive, you might still be inside the less viewer. Press q to quit back to the command prompt.

help me understand why my terminal seems stuck

Search and filter with grep

Reading entire files is useful, but cloud engineers usually need to find specific lines. The grep command searches for patterns in text and returns only the matching lines.

  • Find your own user account in the passwd file by running:
grep $USER /etc/passwd

You should see a single line with your username, home directory, and shell.

  • Find all users that have a bash login shell by running:
grep "/bin/bash" /etc/passwd

This shows only accounts that can actually log in interactively. You should see your account plus developer1 and developer2.

Now try three powerful grep flags that you will use constantly.

  • Invert a search to show lines that do NOT match a pattern by running:
grep -v "nologin" /etc/passwd
  • Count how many lines match a pattern (without showing them) by running:
grep -c "/bin/bash" /etc/passwd
  • Search recursively through a directory to find where a term appears by running:
grep -r "devteam" /etc/group

What do these flags do?

  • -v inverts the match. It shows every line that does NOT contain the pattern.
  • -c counts matching lines instead of printing them. Useful for quick summaries.
  • -r searches recursively through all files in a directory. Essential for searching log folders.

The grep -r "devteam" /etc/group command should show the devteam group line with your users listed as members.

Seeing no output from grep?

No output means no lines matched your pattern. Double-check your spelling. Patterns are case-sensitive, so Bash and bash are different searches.

help me figure out why grep is returning nothing

Extract and transform with awk and sed

Finding lines is step one. Extracting specific fields from those lines is step two. The awk command splits each line into columns and lets you print only the ones you want.

  • Extract just the usernames from the passwd file by running:
awk -F: '{print $1}' /etc/passwd

What does -F: mean?

The -F: flag tells awk to split each line at colon characters. Since the passwd file uses colons as separators, $1 is the username (first field), $3 is the UID, and $7 is the shell.

  • Print both the username and shell for each account by running:
awk -F: '{print $1, $7}' /etc/passwd

You should see a clean two-column output with each username paired with its login shell.

The sed command replaces text in a stream. It processes text line by line and swaps patterns without opening a file editor.

  • Replace a word in a text stream by running:
echo "Hello World" | sed 's/World/Linux/'

You should see Hello Linux printed to your terminal. The s/old/new/ syntax means "substitute old with new."

The real power of Linux text tools comes from combining them with pipes. The pipe character (|) sends the output of one command directly into the next.

  • Combine grep, awk, and sort in a single pipeline to get a sorted list of real users by running:
cat /etc/passwd | grep "/bin/bash" | awk -F: '{print $1}' | sort

You should see an alphabetically sorted list of usernames that have login shells. This should include your username, developer1, and developer2.

How does this pipeline work?

  • cat /etc/passwd outputs the entire file.
  • grep "/bin/bash" filters to only lines with a bash shell (real users).
  • awk -F: '{print $1}' extracts just the username from each line.
  • sort arranges the names in alphabetical order.

Each tool does one small job. Piping them together gives you a complete data extraction workflow in a single line.

Seeing a syntax error with awk?

Make sure you are using single quotes around the awk program ('{print $1}'). Double quotes cause the shell to interpret $1 as a shell variable instead of an awk field reference.

help me fix my awk command

You now have a toolkit of text processing commands that work on any Linux system. Next up, you will combine everything you have learned into a single bash script that audits your entire server configuration automatically.

Write a User Audit Script

You now have a full toolkit of text processing commands at your disposal. The real power of Linux comes from combining these individual tools into scripts that automate repetitive work.

In this step, you will write a bash script that audits your entire server setup. It will report on all users, their group memberships, and whether they have admin privileges. This single script ties together variables, loops, conditionals, piping, grep, awk, and chmod.

In this step, get ready to:
  • Create the audit script file with variables and a formatted report header.
  • Add a loop that iterates over users and flags admins.
  • Make the script executable and run it to see the full report.
Create the script file with variables and header

Every bash script starts with a shebang line that tells the system which interpreter to use. After that, you define variables to capture dynamic data like the current date and user counts.

  • In VS Code (which is already connected to your WSL filesystem), create a new file called audit.sh inside your ~/projects/server-config/ folder.
  • Add the following code to audit.sh:
#!/bin/bash
# System User Audit Script
# Reports on all users with login shells and their group memberships

# Capture the current date/time and count users
AUDIT_DATE=$(date '+%Y-%m-%d %H:%M:%S')
TOTAL_USERS=$(wc -l < /etc/passwd)
LOGIN_USERS=$(grep "/bin/bash" /etc/passwd | wc -l)

# Print the report header with summary stats
echo "========================================="
echo "       SYSTEM USER AUDIT REPORT"
echo "       Generated: $AUDIT_DATE"
echo "========================================="
echo ""
echo "Total accounts on system: $TOTAL_USERS"
echo "Accounts with login shell: $LOGIN_USERS"
echo ""
echo "--- USERS WITH LOGIN ACCESS ---"
echo ""

What does this code do?

The shebang #!/bin/bash tells Linux to run this file using the bash interpreter.

The $() syntax is called command substitution. It runs the command inside and stores the output in the variable. So AUDIT_DATE captures today's date, TOTAL_USERS counts every line in /etc/passwd, and LOGIN_USERS counts only users who have /bin/bash as their shell.

  • Save audit.sh.
Add a loop that iterates over login users

The header gives you a summary, but the real value is a per-user breakdown. You need a loop that finds every user with a login shell, checks their groups, and flags anyone with admin access.

  • In audit.sh, add the following code below the last echo "" line:
# Loop through each user with a bash shell
grep "/bin/bash" /etc/passwd | awk -F: '{print $1}' | while read user; do
    # Get the user's group memberships
    USER_GROUPS=$(groups "$user" 2>/dev/null | cut -d: -f2)

    # Check if user is in the sudo group and label accordingly
    if echo "$USER_GROUPS" | grep -q "sudo"; then
        echo "  [ADMIN] $user"
    else
        echo "  [USER]  $user"
    fi
    echo "          Groups:$USER_GROUPS"
    echo ""
done

echo "========================================="
echo "       END OF AUDIT REPORT"
echo "========================================="

What does this code do?

The pipeline grep "/bin/bash" /etc/passwd | awk -F: '{print $1}' extracts just the usernames of accounts that can actually log in. The output gets piped into a while read loop that processes each username one at a time.

Inside the loop, grep -q "sudo" quietly checks if the user's groups contain "sudo". The -q flag means quiet. It does not print output, just sets an exit code that the if statement reads. If the user is in the sudo group, they get labeled [ADMIN]. Otherwise they get [USER].

  • Save audit.sh.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete audit.sh file. Make sure yours matches exactly:

#!/bin/bash
# System User Audit Script
# Reports on all users with login shells and their group memberships

AUDIT_DATE=$(date '+%Y-%m-%d %H:%M:%S')
TOTAL_USERS=$(wc -l < /etc/passwd)
LOGIN_USERS=$(grep "/bin/bash" /etc/passwd | wc -l)

echo "========================================="
echo "       SYSTEM USER AUDIT REPORT"
echo "       Generated: $AUDIT_DATE"
echo "========================================="
echo ""
echo "Total accounts on system: $TOTAL_USERS"
echo "Accounts with login shell: $LOGIN_USERS"
echo ""
echo "--- USERS WITH LOGIN ACCESS ---"
echo ""

grep "/bin/bash" /etc/passwd | awk -F: '{print $1}' | while read user; do
    USER_GROUPS=$(groups "$user" 2>/dev/null | cut -d: -f2)

    if echo "$USER_GROUPS" | grep -q "sudo"; then
        echo "  [ADMIN] $user"
    else
        echo "  [USER]  $user"
    fi
    echo "          Groups:$USER_GROUPS"
    echo ""
done

echo "========================================="
echo "       END OF AUDIT REPORT"
echo "========================================="
Make it executable and run it

A script file is just a text file until you give it execute permission. This is the same chmod command you used on directories earlier, now applied to make a script runnable.

  • In your WSL terminal, make sure you are in the server-config directory by running:
cd ~/projects/server-config
  • Make the script executable by running:
chmod +x audit.sh
  • Run the script by executing:
./audit.sh

You should see a formatted report that shows the date, total user accounts, and a per-user breakdown with [ADMIN] or [USER] labels next to each name.

What should I see?

Your output will list your own username and developer1 as [ADMIN] (because both are in the sudo group). developer2 will show as [USER] since they only have devteam membership.

This script uses variables, loops, conditionals, piping, grep, awk, and chmod. Everything you learned in this project, combined into one working tool.

Seeing "Permission denied" or a syntax error?

  • If you see "Permission denied", make sure you ran chmod +x audit.sh first. Without execute permission, the script cannot run.
  • If you see a syntax error, open the file in VS Code and check that every echo line has matching quotes. Also verify the if/then/else/fi structure is complete.
  • Make sure you are running the script from the ~/projects/server-config/ directory where the file lives.

Help me debug my audit script

Your audit script is working. You now have a single tool that reports on your entire server configuration. Next up, you can take on a Secret Mission to build an automated user onboarding script that applies everything you have learned.

Secret mission

Automated User Onboarding Script

You have configured a server manually. But what happens when your team hires five new engineers next month? Automate the entire onboarding process with a single script that creates users, sets permissions, generates SSH keys, and delivers a personalized welcome message.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely inside WSL on your local machine, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • User accounts created in WSL (developer1, developer2).
  • The devteam group.
  • Project directory at ~/projects/server-config.
  • SSH keys generated in ~/.ssh/.
  • Installed packages (htop, net-tools, openssh-server, tree).
  • VS Code WSL extension.

Keep everything running

No action needed. Choose this if you are still actively building or plan to continue practising Linux commands in WSL.

  • Your user accounts, group, project files, and SSH keys all remain in place.
  • The openssh-server service will stop automatically when WSL shuts down (WSL terminates idle instances). It restarts only when you open a new terminal session.
  • The VS Code WSL extension stays installed for future WSL projects.

Pause - I'll come back to this later

Stop the SSH service to free up resources, but keep all your files and users intact so you can pick up where you left off.

  • Stop the SSH server by running:
  • Disable it from starting at boot by running:
sudo systemctl stop ssh
sudo systemctl disable ssh

Everything else (users, groups, files, SSH keys, installed packages) stays on disk and costs nothing while idle.

Delete - I don't want to use this again

Remove all project resources and return your WSL environment to its previous state.

  • Remove the created user accounts and their home directories:
sudo deluser --remove-home developer1
sudo deluser --remove-home developer2
  • Remove the devteam group:
sudo groupdel devteam
  • Delete the project directory:
rm -rf ~/projects/server-config
  • Remove the SSH keys you generated:
rm -f ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
rm -f ~/.ssh/authorized_keys
  • Remove the installed packages:
sudo apt remove htop net-tools openssh-server tree -y
sudo apt autoremove -y
  • Uninstall the VS Code WSL extension. In VS Code, open the Extensions sidebar with Ctrl+Shift+X, find WSL by Microsoft, and click Uninstall.

That's a Wrap!

That's a Wrap!

Nice work! You just configured a multi-user Linux server the way cloud engineers do it on day one of any infrastructure project.

You've learned how to:

  • Created users and groups with proper permissions for secure team collaboration on a shared project directory.
  • Configured SSH key-based authentication for passwordless, secure remote access to a server.
  • Written a bash audit script that combines text processing tools, variables, loops, and conditionals to report on system users.
  • Built an automated user onboarding script that provisions new team members with proper permissions, SSH keys, and a welcome message.

Ready to quiz yourself?