Investigate a Traffic Spike with Linux

Navigate a Linux server and analyze Nginx logs to identify an attack.

Introduction

30 Second Summary

When something goes wrong on a server, the first thing any engineer does is check the logs. It is like reviewing security camera footage after something suspicious happens at a building.

In this project, you will set up a Linux environment on your Windows machine, build a realistic server directory structure, and investigate a suspicious traffic spike using only terminal commands.

What You'll Build

Imagine running a single command that instantly reveals which IP addresses are hammering your server and which pages they are targeting, then saving that intelligence to a clean incident report you can hand to your team.

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

  • A fully structured deployment workspace mirroring how real cloud servers organize web applications.
  • A bash script that generates realistic Nginx access log data for investigation.
  • A complete incident report generated entirely from piped terminal commands, identifying who hit the server, what they targeted, and how bad the damage was.
  • Secret Mission: Build a live traffic monitor using tail -f and grep that watches for new attacks in real time.

Are there any prerequisites?

You need a Windows 10 version 2004 (Build 19041) or later, or Windows 11. No prior Linux experience is required.

You will install WSL (Windows Subsystem for Linux) with Ubuntu in the first step. Everything runs locally on your machine at no cost.

Before We Start

Before you dive into the terminal, take a moment to commit to what you're about to build and why it matters for your DevOps career.

Set Up Your Linux Environment with WSL

Every DevOps command you will use in this project runs on Linux. Production servers, cloud instances, and container environments all run Linux. To practice these commands, you need a Linux terminal on your machine.

WSL (Windows Subsystem for Linux) gives you a full Ubuntu terminal directly on Windows. No virtual machine, no cloud costs, no dual-booting. In this step, you will install WSL, set up Ubuntu, and confirm your Linux environment is working.

In this step, get ready to:
  • Install WSL with Ubuntu on your Windows machine.
  • Create your Linux username and password.
  • Verify the environment is running a real Linux kernel.
Open PowerShell and install WSL

WSL requires an Administrator terminal to install because it enables a system-level feature on Windows. You will use PowerShell with elevated privileges to run the install command.

  • Right-click the Start button in your taskbar (or press Win+X).
  • Select Terminal (Admin) from the menu. If you see PowerShell (Admin) instead, select that.
  • A User Account Control prompt appears asking for permission. Click Yes to allow it.
  • Install WSL by running this command:
wsl --install

What does this command do?

The wsl --install command enables the WSL feature on Windows and downloads Ubuntu as your default Linux distribution. It handles everything in one step.

You will see download progress messages in the terminal. When it finishes, you will see a message telling you that changes require a reboot.

  • Restart your computer when the terminal prompts you to.

Seeing an error about virtualization?

If you see an error mentioning virtualization or that WSL 2 requires a kernel update, your system may need a BIOS setting enabled. Search your computer manufacturer's name plus "enable virtualization in BIOS" for specific instructions.

If the command says WSL is already installed, that is fine. Run wsl --update to ensure it is current, then restart anyway.

Help me troubleshoot my WSL installation error.

Create your Linux username and password

After your computer restarts, Ubuntu launches automatically in a new terminal window. It needs a few moments to finish its first-time setup.

  • Wait for Ubuntu to display Enter new UNIX username: in the terminal.
  • Type a username (all lowercase, no spaces) and press Enter.
  • Type a password when prompted and press Enter.
  • Retype the same password to confirm and press Enter.

Why can't I see my password as I type?

Linux hides password input completely. You will not see dots, asterisks, or any characters as you type. This is normal security behavior. Just type your password and press Enter.

Once you confirm your password, you will see a coloured prompt ending with $. This is your Linux terminal. You are now inside Ubuntu.

Verify your Linux environment

Three quick commands will prove that WSL is running a real Linux kernel and that your user account is set up correctly. Before you run the first one, think about what information a Linux system might report about itself.

  • Check your system information by running:
uname -a

Look at the output. Do you see the words "Linux" and "microsoft" in it? Those two words confirm WSL is running a genuine Linux kernel provided by Microsoft.

  • Confirm your username by running:
whoami

The output shows the username you just created.

  • Check your current location in the filesystem by running:
pwd

The output shows /home/yourusername (with your actual username). This is your home directory, the default starting point every time you open Ubuntu.

✔️ I see 'Linux' and 'microsoft' in uname -a

Your WSL environment is working correctly. You have a fully functional Linux terminal on your Windows machine.

ⓧ Ubuntu didn't launch after restart

If Ubuntu did not open automatically, open it manually.

  • Press the Windows key to open your search bar.
  • Type Ubuntu and press Enter to launch it.
  • If Ubuntu is not found, open PowerShell as Administrator again and re-run wsl --install.

Help me get Ubuntu running after installing WSL.

ⓧ I see an error when running uname -a

If you see a "command not found" error or you are still in PowerShell, you may not be inside the Ubuntu terminal.

  • Make sure you are in the Ubuntu app, not in PowerShell or Command Prompt. The prompt should end with $, not >.
  • If you are in the wrong terminal, press the Windows key, type Ubuntu, and press Enter.

Help me figure out why uname -a is not working.

Your Linux environment is ready to go. Next up, you will build a deployment directory structure that mirrors how real cloud servers organize web applications.

Build the Deployment Directory Structure

Your Linux environment is ready. Now it's time to build the workspace where your investigation will happen.

Every production server organizes web applications into standard directories. Logs go in one place, configuration files in another, scripts in another. You need to build this structure so you have somewhere realistic to put the evidence you'll analyze later.

In this step, get ready to:
  • Create a full deployment directory structure using mkdir.
  • Install the tree utility and visualize your workspace.
  • Create placeholder configuration files inside the structure.
Create the deployment directory structure

On a real cloud server, you'd find web applications organized into directories like logs/, configs/, and scripts/. You're going to build this same structure from scratch.

  • Create the full webapp directory and all its subdirectories in one command by running:
mkdir -p ~/webapp/{configs,logs,scripts,backups,reports}

What does this command do?

This single command creates six directories at once.

  • mkdir stands for "make directory".
  • -p tells it to create parent directories as needed (so ~/webapp gets created first, then the subdirectories inside it).
  • The curly braces {configs,logs,scripts,backups,reports} expand into five separate paths. This is called brace expansion.
  • Navigate into your new workspace by running:
cd ~/webapp
  • Explore what you just built by running:
ls -la

You should see the five directories listed: backups, configs, logs, reports, and scripts. Each line also shows the owner, permissions, and timestamp.

Not seeing any directories?

  • Make sure there are no spaces after the commas inside the curly braces. {configs, logs} (with spaces) will fail silently.
  • Check that you typed the tilde ~ before /webapp. Without it, the folder is created relative to wherever your terminal was, not your home directory.
  • help me figure out why mkdir didn't create my directories
Install tree and visualize your structure

The ls command shows a flat list. The tree utility draws a visual map of directories and files, exactly like a senior engineer would review a server's layout.

  • Install tree by running:
sudo apt update && sudo apt install -y tree

What does this command do?

  • sudo runs the command with administrator privileges. It will ask for the Linux password you created in Step 1. The characters won't appear as you type. This is normal.
  • apt update refreshes the list of available packages.
  • apt install -y tree installs the tree package. The -y flag skips the confirmation prompt.

You'll see output scrolling as apt downloads and installs the package. Wait for it to finish and return you to the prompt.

  • Visualize your deployment structure by running:
tree ~/webapp

You should see a tree diagram showing webapp at the top with five subdirectory branches (backups, configs, logs, reports, scripts) and a summary line showing "5 directories, 0 files".

Seeing 'command not found' for tree?

  • If the install failed, try running sudo apt update and sudo apt install -y tree as two separate commands to see which one errors.
  • If sudo asks for a password and seems to reject it, remember: nothing shows when you type. Type your password carefully and press Enter.
  • help me fix my tree installation
Create placeholder configuration files

Real web servers have configuration files for the application, the web server, and the database. You'll create empty placeholders now to fill in the directory structure.

  • Create three config files by running:
touch configs/app.conf configs/nginx.conf configs/db.conf

What does touch do?

The touch command creates empty files. If the file already exists, it updates its timestamp without changing its contents. It's the fastest way to create placeholder files from the terminal.

  • Confirm the files exist by running:
ls -lh configs/

You should see three files listed: app.conf, db.conf, and nginx.conf. Each shows a size of 0 bytes because they are empty placeholders.

Not seeing all three files?

  • Check that your current directory is ~/webapp by running pwd. If you're somewhere else, the configs/ path won't resolve.
  • Make sure you used .conf (with the dot) for the file extensions.
  • help me fix my config file creation

Before you run the final check, what do you expect the tree output to look like now that you've added files to the configs/ directory?

  • Verify your complete structure by running:
tree ~/webapp

You should see all five directories with three .conf files nested inside configs/. The summary line now reads "5 directories, 3 files".

Your deployment workspace is built. Next up, you'll create a bash script that generates the suspicious access log you need to investigate.

Generate the Access Log for Investigation

Your deployment workspace is built and ready. Now you need something to investigate.

In production, server logs already exist on disk when an incident happens. For this project, you will write a bash script that generates a realistic 1000-line Nginx access log. The log contains normal traffic mixed with suspicious patterns, simulating what a real attack looks like.

In this step, get ready to:
  • Create a bash script that generates realistic Nginx-format log entries.
  • Run the script to populate your logs directory with 1000 lines of mixed traffic data.
Create the log generator script

You will use nano, a terminal-based text editor that comes pre-installed on Ubuntu. It opens directly inside your terminal window.

  • Open the script file for editing by running this command:
nano scripts/generate-log.sh

What is nano?

Nano is a simple text editor that runs inside your terminal. When you run the command above, your terminal transforms into a text editing view. You will see a blank editing area with shortcut hints at the bottom of the screen.

You type and paste text just like a normal editor. The key difference is that you save and exit using keyboard shortcuts instead of menus.

  • Paste the following configuration section into nano. This defines the data arrays the script will randomly pick from:
#!/bin/bash
# Generate a mock Nginx access log with 1000 entries
# Includes normal traffic mixed with suspicious patterns

LOG_FILE="$HOME/webapp/logs/access.log"

# Realistic IP addresses (some appear more often to simulate an attack)
IPS=("192.168.1.100" "10.0.0.55" "203.0.113.42" "198.51.100.7" "172.16.0.99" "203.0.113.42" "203.0.113.42" "192.168.1.100" "10.0.0.55" "185.220.101.34" "185.220.101.34" "185.220.101.34" "185.220.101.34" "45.33.32.156" "91.189.88.181")

# HTTP methods
METHODS=("GET" "GET" "GET" "GET" "POST" "GET" "GET" "DELETE")

# URL paths (includes suspicious probing paths)
PATHS=("/index.html" "/about" "/api/users" "/login" "/api/data" "/products" "/contact" "/admin/config" "/api/users" "/.env" "/wp-login.php" "/admin/config" "/api/data" "/favicon.ico" "/style.css")

# HTTP status codes (weighted toward 200 but includes errors)
STATUSES=("200" "200" "200" "200" "200" "200" "200" "200" "301" "404" "404" "403" "500" "200" "304")

# Response sizes in bytes
SIZES=("1024" "2048" "512" "4096" "256" "3072" "1536" "0" "0" "0" "0" "0" "128" "1024" "768")

# Clear existing log file
> "$LOG_FILE"

What does this code do?

This section sets up the building blocks for fake log data:

  • LOG_FILE points to where the generated log will be saved.
  • The IPS array lists IP addresses. Notice that 185.220.101.34 appears four times. This weighting makes it show up more often in the log, simulating an attacker hammering the server.
  • The PATHS array includes suspicious endpoints like /.env and /wp-login.php that real attackers probe for.
  • The > "$LOG_FILE" line clears any existing log so you start fresh each time.
  • Directly below what you just pasted (with no blank lines between), paste the generation loop:
# Generate 1000 log entries
for i in $(seq 1 1000); do
    IP=${IPS[$((RANDOM % ${#IPS[@]}))]}
    METHOD=${METHODS[$((RANDOM % ${#METHODS[@]}))]}
    PATH_ENTRY=${PATHS[$((RANDOM % ${#PATHS[@]}))]}
    STATUS=${STATUSES[$((RANDOM % ${#STATUSES[@]}))]}
    SIZE=${SIZES[$((RANDOM % ${#SIZES[@]}))]}

    # Generate timestamp within a one-hour window
    MINUTE=$((RANDOM % 60))
    SECOND=$((RANDOM % 60))
    TIMESTAMP="09/Aug/2026:14:$(printf '%02d' $MINUTE):$(printf '%02d' $SECOND) +0000"

    echo "$IP - - [$TIMESTAMP] \"$METHOD $PATH_ENTRY HTTP/1.1\" $STATUS $SIZE" >> "$LOG_FILE"
done

echo "Generated 1000 log entries in $LOG_FILE"

What does the loop do?

This loop runs 1000 times. On each pass it:

  • Picks a random element from each array using $RANDOM (a built-in bash variable that returns a random number).
  • Builds a random timestamp within a one-hour window using printf '%02d' to pad minutes and seconds with leading zeros.
  • Writes one log line in Nginx combined log format and appends it (>>) to the log file.
  • Save the file by pressing Ctrl+O.
  • Nano will display the filename at the bottom of the screen. Press Enter to confirm.
  • Exit nano by pressing Ctrl+X.

You are back at your normal terminal prompt.

  • Make the script executable by running this command:
chmod +x scripts/generate-log.sh
  • Confirm the script file exists by running:
ls -lh scripts/

You should see generate-log.sh listed with an x in its permissions (something like -rwxr-xr-x).

Don't see the file or missing the x permission?

  • If generate-log.sh is not listed, make sure you saved in nano before exiting. Re-open the file with nano scripts/generate-log.sh and check if your content is there.
  • If the file exists but does not have x in its permissions, run chmod +x scripts/generate-log.sh again.

Help me troubleshoot why my script file is not showing up or not executable.

✔️ Awesome, I've got everything!

Great. Make sure your file is saved and you are back at the normal terminal prompt before continuing.

ⓧ I'd like to double check the full code

Your script should contain two main sections, in this order:

  • Configuration section (first chunk): starts with #!/bin/bash and ends with > "$LOG_FILE". Contains the five arrays (IPS, METHODS, PATHS, STATUSES, SIZES) and the LOG_FILE variable.
  • Generation loop (second chunk): starts with # Generate 1000 log entries and ends with echo "Generated 1000 log entries in $LOG_FILE". Contains the for loop that writes 1000 random log lines.

Scroll up and confirm both chunks are present in order. If anything looks off, re-open the file with nano scripts/generate-log.sh and fix it.

Run the script and verify the output

The script is ready. Time to generate your investigation data.

  • Run the log generator script by executing:
./scripts/generate-log.sh

You should see the message Generated 1000 log entries in /home/yourusername/webapp/logs/access.log (with your actual username in the path).

Seeing Permission denied or command not found?

  • If you see Permission denied, run chmod +x scripts/generate-log.sh and try again.
  • If you see No such file or directory, make sure your current directory is ~/webapp. Run cd ~/webapp then try the command again.
  • If you see a syntax error, re-open the script with nano scripts/generate-log.sh and check that the code matches the two chunks above exactly.

Help me fix an error when running my generate-log.sh bash script.

Before you verify the output, what do you expect wc -l to report for a log file generated by a loop that runs 1000 times?

  • Verify the log file has exactly 1000 lines by running:
wc -l logs/access.log

You should see 1000 logs/access.log confirming all entries were written.

  • Peek at the first 5 lines to confirm the Nginx log format by running:
head -5 logs/access.log

Each line should look like a real Nginx log entry: an IP address, a timestamp in brackets, a quoted HTTP request, a status code, and a response size.

What should I see?

Each line follows this pattern:

203.0.113.42 - - [09/Aug/2026:14:23:07 +0000] "GET /api/users HTTP/1.1" 200 512

Your exact IPs, paths, and timestamps will differ because the script picks randomly. The important thing is that the format matches: IP, dash, dash, bracketed timestamp, quoted request, status, size.

Your investigation data is ready. You have 1000 lines of server traffic sitting in logs/access.log, with a suspicious attacker hiding somewhere in the noise. Next up, you will try to find them the hard way.

Attempt the Investigation (and Hit a Wall)

Your access log is generated and loaded with 1000 lines of traffic data. Time to investigate the spike and find the attacker.

As a DevOps engineer responding to an incident, your first move is always to look at the logs. You have a 1000-line Nginx access log sitting in your logs/ directory. Let's dig in and find out who is responsible.

In this step, get ready to:
  • View the access log to understand the raw traffic data.
  • Search for error patterns using Linux viewing and search commands.
  • Count the errors and assess whether you can identify the attacker.
Try viewing the raw log

The most intuitive way to read a file on Linux is to print its entire contents to the terminal. That's exactly what cat does.

  • View the full access log by running this command:
cat logs/access.log

Your terminal just flooded with 1000 lines of log data scrolling past faster than you can read. Try scrolling back. Good luck finding anything useful in that wall of text.

This is what happens on a real production server. Except production logs are often millions of lines, not just a thousand.

Seeing 'No such file or directory'?

Make sure you are in the ~/webapp directory. Run cd ~/webapp and try the command again.

Help me figure out why cat can't find my access.log file

Narrow down with head, tail, and grep

Dumping the entire file clearly does not work. What if you just look at the beginning or end of the log instead?

  • View the first 20 lines of the log by running:
head -20 logs/access.log

Better. You can actually read these lines. You see IPs, timestamps, request paths, and status codes. But 20 lines out of 1000 is a tiny sample.

  • View the last 20 lines of the log by running:
tail -20 logs/access.log

You can see the most recent traffic, but you still cannot answer the key question: which IPs are attacking you? You need to search for specific patterns.

  • Search for all lines containing 404 errors by running:
grep "404" logs/access.log

You get results. Every line with a 404 status code prints to your terminal. But there are too many of them to count manually or identify any pattern from just reading.

What is grep?

The grep command searches through files and prints every line that matches a pattern. It is one of the most-used commands in Linux. Think of it as a search bar for text files.

Count the errors with grep -c

Reading individual matches is overwhelming. What if you just count how many 404 errors occurred?

Before you run this next command, think: will knowing the total count of 404 errors be enough to identify who is attacking your server and what they are targeting?

  • Count the total number of 404 errors by running:
grep -c "404" logs/access.log

You get back a single number. That is the total count of lines containing 404 in your log.

What does the -c flag do?

The -c flag tells grep to print a count of matching lines instead of printing the lines themselves. It answers "how many" instead of "which ones".

Now you know HOW MANY errors occurred. But you still cannot answer two critical questions: "Which IPs are causing the 404 errors?" and "Which pages are they targeting?"

A number alone does not tell you who the attacker is. You need a way to extract specific fields from each line, group them, and rank them by frequency. That requires something more powerful than grep alone.

Why this obstacle is intentional

Every DevOps engineer has had this exact moment. You are staring at a log file, you know something is wrong, but basic commands cannot extract the patterns hiding in the data. This is why Linux pipelines exist.

The tools you need to crack this case are coming in the next step.

You have hit the wall. You know there are errors, but you cannot break down who caused them or what they were targeting. In the next step, you will learn the pipeline technique that DevOps engineers use to crack cases exactly like this.

Crack the Case with Pipelines

You know the 404 count from the previous step, but that single number cannot tell you who is responsible or what they are targeting.

Pipes solve this by chaining simple commands into powerful data-processing workflows. Each command in the chain does one small job, then passes its output to the next command. The pipeline pattern you learn here is the same one senior SREs use during real incident response.

In this step, get ready to:
  • Identify the top 5 IP addresses hitting the server using piped commands.
  • Find the top 5 endpoints returning 404 errors.
  • Generate a professional incident report file using output redirection.
Find the top 5 IP addresses hitting the server

The first question any engineer asks during a traffic spike is: which IPs are sending the most requests? A single pipeline answers this in one line.

  • Extract and rank the top 5 IPs by request count by running this command:
awk '{print $1}' logs/access.log | sort | uniq -c | sort -rn | head -5

You should see five IP addresses listed with their request counts, with 185.220.101.34 likely at or near the top.

What does this pipeline do?

  • awk '{print $1}' extracts the first field from each log line, which is the IP address.
  • sort groups identical IPs together (required for uniq to work).
  • uniq -c counts how many times each grouped IP appears.
  • sort -rn sorts the counts in reverse numeric order (highest first).
  • head -5 shows only the top 5 results.

Each command does one tiny job, then the pipe (|) passes its output to the next command. Together they form a data-processing assembly line.

Seeing no output or an error?

  • Confirm you are in the ~/webapp directory by running pwd. The path should end with /webapp.
  • Make sure you typed single quotes around '{print $1}' and not double quotes. Double quotes cause the shell to interpret $1 as a variable.
  • help me debug my awk pipeline command
Find the top 5 endpoints returning 404 errors

Now you know which IPs are busiest, but you still need to know what pages the attacker is probing. By adding grep at the front of the pipeline, you can filter to only 404 errors before extracting endpoints.

  • Find the top 5 URL paths that returned 404 errors by running this command:
grep "404" logs/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -5

You should see paths like /.env, /wp-login.php, and /admin/config in the output. These are classic attack probing paths.

What changed in this pipeline?

  • grep "404" filters the log to only lines containing a 404 status code before anything else runs.
  • awk '{print $7}' extracts the seventh field, which is the requested URL path in Nginx log format.
  • The rest of the pipeline (sort | uniq -c | sort -rn | head -5) is the same counting-and-ranking pattern from the previous command.

Once you learn this pattern, you can swap out the filter and the field number to answer almost any log question.

Seeing unexpected paths in the output?

  • Make sure you used $7 (not $1). Field 7 holds the URL path in the Nginx format used here.
  • If you see GET or HTTP/1.1 in the output, you have the wrong field number.
  • help me figure out why my grep and awk pipeline shows wrong fields
Generate the incident report using redirection

You have the data you need. Now turn it into a professional incident report file that you could hand to a team lead. Output redirection (> and >>) lets you capture command output directly into a file instead of printing it to the screen.

  • Create the incident report header by running these commands:
echo "=== INCIDENT REPORT ===" > reports/incident-report.txt
echo "Generated: $(date)" >> reports/incident-report.txt
echo "Log file: $HOME/webapp/logs/access.log" >> reports/incident-report.txt
echo "Total requests: $(wc -l < logs/access.log)" >> reports/incident-report.txt

What is the difference between > and >>?

> creates a new file (or overwrites an existing one). >> appends to the end of the file without erasing what is already there.

The first line uses > to start fresh. Every line after uses >> to add content below.

  • Confirm the header was written by running:
cat reports/incident-report.txt

You should see four lines: the report title, the current date, the log file path, and the total request count of 1000.

  • Append the top IPs section and the top 404 endpoints section by running these commands:
echo "" >> reports/incident-report.txt
echo "--- TOP 5 IPs BY REQUEST COUNT ---" >> reports/incident-report.txt
awk '{print $1}' logs/access.log | sort | uniq -c | sort -rn | head -5 >> reports/incident-report.txt
echo "" >> reports/incident-report.txt
echo "--- TOP 5 ENDPOINTS RETURNING 404 ---" >> reports/incident-report.txt
grep "404" logs/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -5 >> reports/incident-report.txt

What is happening with the pipeline redirection?

When you put >> reports/incident-report.txt at the end of a pipeline, only the final output gets written to the file. The intermediate pipes still pass data between commands as normal.

  • Append the summary section to finish the report by running:
echo "" >> reports/incident-report.txt
echo "--- SUMMARY ---" >> reports/incident-report.txt
echo "Total 404 errors: $(grep -c '404' logs/access.log)" >> reports/incident-report.txt

Before you view the report, what do you think it will contain? You ran two major pipelines and a count. Predict the three data sections you expect to see.

  • View your completed incident report by running:
cat reports/incident-report.txt

You should see three sections: a header with the date and total requests, the top 5 IPs ranked by request count, and the top 5 endpoints returning 404 errors with a summary count at the bottom.

You just produced a professional incident summary using only the terminal. No spreadsheet, no GUI, no external tools.

Report is empty or missing sections?

  • If the file is empty, you may have used > (overwrite) instead of >> (append) on one of the later commands. Rerun all the commands from the header onward.
  • If a pipeline section shows no data, confirm logs/access.log still has 1000 lines by running wc -l logs/access.log.
  • help me fix my incident report file

✔️ Awesome, I've got everything!

Great. Make sure the report file is saved at reports/incident-report.txt and contains all three data sections.

ⓧ I'd like to double check the full code

Your completed incident report should look like this (the actual numbers and date will differ based on your generated log):

=== INCIDENT REPORT ===
Generated: [date from your system]
Log file: /home/[your username]/webapp/logs/access.log
Total requests: 1000

--- TOP 5 IPs BY REQUEST COUNT ---
[ranked list of 5 IPs with counts]

--- TOP 5 ENDPOINTS RETURNING 404 ---
[ranked list of 5 paths with counts]

--- SUMMARY ---
Total 404 errors: [a number]

If yours does not match this structure, scroll up and rerun the commands starting from the header (the first echo "=== INCIDENT REPORT ===" > command).

Secret mission

Build a Live Traffic Monitor

In production, attacks don't wait for you to finish analyzing yesterday's logs. Can you build a real-time monitor that catches suspicious traffic the instant it arrives? You'll need two terminals running simultaneously and the tail -f command that DevOps engineers use during active incidents.

Clean Up Your Resources

Clean Up Your Resources

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

Resources you used:

  • ~/webapp/ directory containing all project files (configs, logs, scripts, backups, reports).
  • tree utility installed via apt inside Ubuntu.
  • WSL Ubuntu distribution (the entire Linux environment installed in Step 1).

Keep everything running

No action needed. Choose this if you're still actively building or plan to continue experimenting with Linux commands.

  • Your WSL environment persists between reboots. You can return to ~/webapp anytime by opening Ubuntu from the Start menu.
  • The tree package and all files remain installed and ready to use.
  • If you still have tail -f running in a terminal, press Ctrl+C to stop it when you are done watching.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your files remain intact for next time.

  • Press Ctrl+C in any terminal running tail -f to stop the live monitor.
  • Close all Ubuntu terminal windows. WSL automatically shuts down when all terminals are closed.
  • Your ~/webapp directory and all files remain intact on disk. Open Ubuntu from the Start menu whenever you want to return.

Delete - I don't want to use this again

Remove all project resources and start fresh. You have two options: delete just the project files, or remove the entire WSL Ubuntu installation.

To remove the project files only:

  • Press Ctrl+C in any terminal running tail -f to stop live processes.
  • Remove the project directory by running:
rm -rf ~/webapp
  • Remove the tree package by running:
sudo apt remove -y tree

To completely remove WSL and Ubuntu:

  • Close all Ubuntu terminal windows.
  • Open PowerShell as Administrator (right-click Start menu, select Terminal (Admin)).
  • Unregister the Ubuntu distribution by running:
wsl --unregister Ubuntu

This permanently deletes everything

Running wsl --unregister Ubuntu removes the entire Ubuntu filesystem, including all files, installed packages, and your Linux user account. This cannot be undone.

Nice Work!

Nice Work!

You just investigated a traffic spike on a web server using nothing but the Linux command line. From setting up your environment to producing a professional incident report, you worked through the exact workflow DevOps engineers use when responding to production issues.

You've learned how to:

  • Set up a Linux environment on Windows using WSL and Ubuntu, and build a deployment directory structure matching real cloud server organization.
  • Write and run a bash script that generates realistic Nginx access log data, then discover firsthand why raw log viewing fails at scale.
  • Master the pipe pattern (awk + sort + uniq -c + sort -rn + head) to extract actionable intelligence from logs and generate a professional incident report using output redirection.
  • Secret Mission: Monitor live log traffic with tail -f piped to grep, simulating real-time incident response.

Ready to quiz yourself?