Build a Bash Log Security Analyzer

Analyze authentication logs and generate a security report using Bash.

Introduction

30 Second Summary

Every server connected to the internet faces a constant stream of login attempts from automated bots and malicious actors. The system logs recording these attempts hold the answers to who is attacking, from where, and when.

In this project, you will write a Bash script that analyzes authentication logs and generates a formatted security report. You will use the same text-processing tools that professional sysadmins and DevOps engineers rely on every day.

What You'll Build

You will run a single command and watch your terminal fill with a ranked security report showing the worst-offending attacker IPs, the most-guessed usernames, and the hours when attacks peak.

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

  • A one-command security analyzer that scans 500 log entries, counts failed vs. successful logins, and ranks the most aggressive attacker IPs from worst to least.
  • A clear view of which usernames attackers guess most often and an hourly breakdown of when attacks hit hardest.
  • A saved report file you can hand to a security team or review later.
  • Secret Mission: Add brute-force threshold alerting that flags any IP exceeding a configurable number of failed attempts.

Are there any prerequisites?

You need an Ubuntu VM running on VirtualBox. Any recent LTS version works (22.04, 24.04, or 26.04).

If you do not have one set up yet, search for a quick Ubuntu VirtualBox setup tutorial to get started.

Before We Start

You are about to learn Linux by building something real. Before you touch the terminal, take a moment to lock in what you are building and why it matters for your goals.

Get Oriented in Your Ubuntu System

You have a fresh Ubuntu VM, but before you can analyze any logs, you need to know how to move around the Linux filesystem.

In this step, you will open the terminal, learn the commands that let you navigate files and folders, and build the project workspace where all your scripts will live.

In this step, get ready to:
  • Open the terminal and learn basic filesystem navigation.
  • Install nano and create the project workspace.
  • Test that nano works by creating and deleting a quick file.
Open the terminal and learn basic navigation

Everything you do in Linux starts from the terminal. It is your main interface for running commands, navigating the filesystem, and managing files.

  • Press Ctrl+Alt+T to open the Terminal.

Another way to open Terminal

You can also click Activities in the top-left corner of your screen, type Terminal, and click the result.

Your project runs on Bash, the default shell on Ubuntu. A shell is the program that reads your commands and executes them.

  • Confirm Bash is installed by running this command:
bash --version

You should see a version number starting with 5. followed by additional details. The exact version depends on which Ubuntu release you are running.

  • Check which directory you are currently in by running:
pwd

You should see your home folder path, something like /home/your-username. This is where you start every time you open a terminal.

Ubuntu stores all its system logs in a special directory. Your project will teach you to analyze files just like the real ones stored there.

  • See what system logs Ubuntu has by running:
ls /var/log/

You should see a list of files including auth.log and syslog. These are real log files that record everything happening on the system.

What is auth.log?

The auth.log file tracks every authentication event on the system. It records SSH logins, sudo commands, and password attempts.

Your project will generate sample data that mimics the format of this file, so you can practice analyzing it safely.

  • Practice moving around the filesystem by running these commands one at a time:
# Move into the system logs directory
cd /var/log

# Confirm where you are now
pwd

# Return to your home folder
cd ~

What do these commands do?

  • cd changes your current directory. Think of it like opening a folder.
  • pwd prints your current location. After running cd /var/log, it should print /var/log.
  • cd ~ brings you back to your home folder. The tilde (~) is a shortcut for your home directory path.
Install nano and create your project workspace

You need a text editor to write your scripts, and you need a dedicated folder to keep everything organized. nano is a simple, beginner-friendly text editor that runs directly in the terminal.

  • Install nano by running this command:
sudo apt update && sudo apt install -y nano

What does this command do?

  • sudo runs the command with administrator privileges. Your terminal may ask for your password when you use it.
  • apt update refreshes the list of available packages so Ubuntu knows what is out there.
  • apt install -y nano installs nano. The -y flag automatically answers "yes" to any prompts. If nano is already installed, the command confirms that and moves on.

Seeing a password prompt or an error?

  • If the terminal asks for your password, type the password you use to log in to your Ubuntu VM. The characters will not appear as you type. This is normal.
  • If you see a network error, check that your VM has internet access. In VirtualBox, go to your VM's Settings, select Network, and confirm Attached to is set to NAT.

Help me troubleshoot my apt install command on Ubuntu.

Now you need a dedicated folder to hold your scripts, log data, and reports.

  • Create your project directory with two subdirectories by running this command:
mkdir -p ~/log-analyzer/logs ~/log-analyzer/reports

What does this command do?

  • mkdir creates new directories.
  • The -p flag creates any parent folders that do not exist yet. Without it, the command would fail because log-analyzer does not exist yet either.
  • logs/ will hold your sample log data. reports/ will hold the security reports your script generates.
  • Confirm your directories were created by running:
ls ~/log-analyzer

You should see logs and reports listed in the output.

Don't see the directories?

  • Check that you included the -p flag in the mkdir command. Without it, the nested directories will not be created.
  • Make sure the tilde (~) is at the start of the path. It refers to your home directory.

Help me create my project directories on Ubuntu.

Test that nano works by creating a quick file

Before you rely on nano to write real scripts, take it for a quick test drive. You will create a file, save it, read it back, and then clean it up.

  • Open a new file in nano by running:
nano ~/log-analyzer/test.txt

The nano editor opens and fills your terminal. You should see a blank editing area with a toolbar of commands at the bottom.

  • Type a short message, like Hello from my log analyzer project.
  • Press Ctrl+O to save the file.
  • Press Enter to confirm the filename.
  • Press Ctrl+X to exit nano and return to the terminal.
  • Verify your file was saved by printing its contents with this command:
cat ~/log-analyzer/test.txt

You should see your message printed back in the terminal. The cat command reads a file and prints its contents to the screen.

  • Clean up the test file by running:
rm ~/log-analyzer/test.txt

What does rm do?

rm removes (deletes) a file. Be careful with this command. Linux does not have a recycle bin for terminal deletions. Once you rm a file, it is gone.

Before you move on, take a moment to predict. What do you expect to see if you run ls ~/log-analyzer right now?

  • Confirm your workspace is clean by running:
ls ~/log-analyzer

You should see only logs and reports. The test.txt file is gone because you removed it with rm.

Still seeing test.txt?

  • Make sure you ran rm ~/log-analyzer/test.txt with the full path. If you shortened the path, the command may have targeted a different location.
  • Run the rm command again. If the file does not exist, you will see a "No such file or directory" message, which means it was already deleted.

Help me clean up my test file in the log-analyzer directory.

Your workspace is set up and you know how to navigate the filesystem, create directories, edit files with nano, and clean up after yourself. Next up, you will fill that empty logs/ folder with realistic attack data by writing your first real Bash script.

Generate Realistic Attack Log Data

In the last step, you got oriented in your Ubuntu terminal and built your project workspace. You can navigate the filesystem, create directories, and use nano to edit files.

Your fresh VM has no attack history. Real sysadmins analyze logs with hundreds or thousands of entries from multiple IP addresses and usernames. You need realistic data to work with, and building the generator script yourself teaches you Bash scripting fundamentals like variables, arrays, loops, and conditionals.

In this step, get ready to:
  • Write a Bash script that generates 500 realistic authentication log entries.
  • Run the script and confirm the log file was created.
  • Explore the generated data using basic Linux commands.
Create the log generator script

Real Linux servers store authentication events in /var/log/auth.log. Every failed SSH login, every successful login, and every sudo command gets recorded there. Your VM is too new to have interesting attack data, so you will write a script that generates it.

  • Open the log generator script in nano by running this command:
nano ~/log-analyzer/generate_logs.sh

You should see an empty editor with a blinking cursor. You will type the script into this editor in two sections.

  • Type the following configuration section into nano:
#!/bin/bash

# Set up file paths
LOG_DIR="$HOME/log-analyzer/logs"
LOG_FILE="$LOG_DIR/sample_auth.log"

# Create the logs directory if it doesn't exist
mkdir -p "$LOG_DIR"

# Define the data arrays for generating realistic log entries
USERNAMES=("admin" "root" "ubuntu" "deploy" "jenkins" "mysql" "postgres" "www-data" "ftpuser" "testuser")
ATTACKER_IPS=("203.0.113.42" "198.51.100.17" "192.0.2.88" "203.0.113.99" "198.51.100.55")
LEGIT_IPS=("10.0.1.5" "10.0.1.12")
HOSTNAME="web-server-01"

What does this code do?

  • #!/bin/bash is called a shebang. It tells Linux to run this file using Bash.
  • LOG_DIR and LOG_FILE store file paths as variables so you can reuse them throughout the script.
  • USERNAMES, ATTACKER_IPS, and LEGIT_IPS are Bash arrays. Each one holds a list of values the script will randomly pick from when generating log entries.
  • Leave a blank line after the last line you typed, then type the generation loop below the configuration section:
echo "Generating sample authentication log..."
# Clear any existing log file
> "$LOG_FILE"

# Generate 500 log entries with random timestamps
for i in $(seq 1 500); do
    DAY=$((RANDOM % 28 + 1))
    HOUR=$((RANDOM % 24))
    MIN=$((RANDOM % 60))
    SEC=$((RANDOM % 60))
    TIMESTAMP=$(printf "Aug %2d %02d:%02d:%02d" "$DAY" "$HOUR" "$MIN" "$SEC")
    PID=$((RANDOM % 9000 + 1000))
    PORT=$((RANDOM % 10000 + 40000))

    # 70% chance of a failed login, 30% chance of success
    if (( RANDOM % 10 < 7 )); then
        USER=${USERNAMES[$((RANDOM % ${#USERNAMES[@]}))]}
        IP=${ATTACKER_IPS[$((RANDOM % ${#ATTACKER_IPS[@]}))]}
        echo "$TIMESTAMP $HOSTNAME sshd[$PID]: Failed password for $USER from $IP port $PORT ssh2" >> "$LOG_FILE"
    else
        USER=${USERNAMES[$((RANDOM % 3))]}
        IP=${LEGIT_IPS[$((RANDOM % ${#LEGIT_IPS[@]}))]}
        echo "$TIMESTAMP $HOSTNAME sshd[$PID]: Accepted password for $USER from $IP port $PORT ssh2" >> "$LOG_FILE"
    fi
done

# Print confirmation
echo "Generated $(wc -l < "$LOG_FILE") log entries in $LOG_FILE"
echo "Done!"

What does this loop do?

  • > "$LOG_FILE" clears the log file before writing. This means you can re-run the script without duplicating entries.
  • for i in $(seq 1 500); do repeats the block inside it 500 times, once for each log entry.
  • $((RANDOM % 28 + 1)) generates a random number. RANDOM is a built-in Bash variable that gives you a random integer every time you read it.
  • The if (( RANDOM % 10 < 7 )) conditional makes 70% of entries failed logins and 30% successful logins. This ratio mimics a server under active attack.
  • Each echo line writes a log entry in the exact format that SSH uses in /var/log/auth.log, so the analysis tools you build later will work on real logs too.
  • Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
  • Exit nano by pressing Ctrl+X.

✔️ Awesome, I've got everything!

Great. Double check you saved the file before continuing.

ⓧ I'd like to double check the full code

The full script is built from the two chunks above. Scroll up and confirm you have both sections in order:

  • Section 1 (Configuration): starts with #!/bin/bash and ends with HOSTNAME="web-server-01".
  • Section 2 (Generation loop): starts with echo "Generating sample authentication log..." and ends with echo "Done!".

If anything looks off, re-open the file with nano ~/log-analyzer/generate_logs.sh and fix it.

Make the script executable and run it

By default, Linux does not let you run a new file as a program. You need to grant it execute permission first.

  • Grant execute permission to your script by running this command:
chmod +x ~/log-analyzer/generate_logs.sh
  • Move into your project directory and run the generator by running these commands:
cd ~/log-analyzer && ./generate_logs.sh

You should see two lines of output confirming that 500 log entries were generated and the path where they were saved.

Seeing a permission denied or syntax error?

  • If you see Permission denied, the chmod +x step was missed. Run it again with the full path: chmod +x ~/log-analyzer/generate_logs.sh.
  • If you see a syntax error with a line number, re-open the script with nano ~/log-analyzer/generate_logs.sh and check that line against the code chunks above. Common culprits are missing closing quotes, missing parentheses in array definitions, or missing fi or done at the end of blocks.

Help me debug my generate_logs.sh script

Explore the generated data with basic commands

Your log file now contains 500 entries. Before you start analyzing them, take a look at the raw data to understand its structure.

  • View the first 10 lines of the log file by running this command:
head -10 ~/log-analyzer/logs/sample_auth.log

Notice the format of each line: a timestamp, the hostname web-server-01, the service name sshd with a process ID, and then the event message describing a failed or successful login.

  • View the last 5 lines by running this command:
tail -5 ~/log-analyzer/logs/sample_auth.log

Before you run the next command, predict: how many lines do you think the log file contains?

  • Count the total number of lines by running this command:
wc -l ~/log-analyzer/logs/sample_auth.log

You should see 500 printed, confirming all 500 log entries were generated.

What are head, tail, and wc?

  • head -10 shows the first 10 lines of a file. Change the number to see more or fewer lines.
  • tail -5 shows the last 5 lines. Sysadmins use this constantly to check the most recent entries in a log.
  • wc -l counts the number of lines in a file. It stands for "word count" with the -l flag limiting it to lines only.

Not seeing 500 lines?

  • If you see 0 lines, the script may have hit a syntax error before writing any entries. Re-run ./generate_logs.sh and check for error output.
  • If the file does not exist at all, make sure you are in the ~/log-analyzer directory by running cd ~/log-analyzer and then run the script again.

Help me figure out why my log file was not generated correctly

You now have 500 realistic log entries sitting in sample_auth.log, packed with failed logins and a handful of successful ones. Next up, you will start hunting through this data for threats using grep.

Hunt for Threats with grep

You now have 500 realistic log entries sitting in your sample_auth.log file. Somewhere inside those 500 lines are failed login attempts from attackers trying to break into your server.

Your job now is to find those threats. grep is the first tool every sysadmin reaches for when searching through logs. It scans a file line by line and pulls out everything that matches a text pattern you give it.

In this step, get ready to:
  • Use grep to find and count failed login attempts in your log file.
  • Try to identify the most dangerous attacker from the raw output and discover what grep alone cannot do.
Search for failed login attempts

The log file contains both failed and successful login attempts mixed together. You need to pull out just the failed ones to find the threats.

  • Search for all failed login lines by running this command:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log

Watch the output scroll past. That is every failed login attempt in your log file, printed line by line. It is a flood of raw threat data.

What does grep do?

grep scans a file line by line and prints every line that contains your search pattern. Here, the pattern "Failed password" matches the exact text that SSH writes into the authentication log whenever a login attempt fails.

Seeing "No such file or directory"?

Double check the file path. The log file lives at ~/log-analyzer/logs/sample_auth.log. If it does not exist, return to Step 2 and run ./generate_logs.sh again.

help me figure out why grep can't find my sample_auth.log file

That wall of text is hard to work with. Instead of printing every matching line, you can ask grep to just count the matches.

  • Count the total number of failed login attempts by running:
grep -c "Failed password" ~/log-analyzer/logs/sample_auth.log

What does the -c flag do?

The -c flag counts matching lines instead of printing them. You get a single number instead of a flood of text.

You should see a number representing all the failed login attempts in your log. Now count the successful logins to compare.

  • Count successful logins by running:
grep -c "Accepted password" ~/log-analyzer/logs/sample_auth.log

Compare the two numbers. The failed attempts should heavily outnumber the successful logins. Your server is under attack.

Try to narrow down the threats manually

You know threats exist. But knowing that hundreds of failed logins happened is not the same as knowing which attacker is the most dangerous. Can you figure out which IP address attacked the most?

  • Look at just the first 20 failed attempts by running:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | head -20

What does the pipe do?

The pipe (|) takes the output of the command on the left and sends it as input to the command on the right. Here, grep finds all matching lines, then head -20 shows only the first 20.

Look at the 20 lines in front of you. You can see IP addresses and usernames buried in each line. Try scanning with your eyes to spot which IP appears most often. It is painful and error-prone.

Maybe you could filter for one specific IP to check if it is attacking. Before you run the next command, think about this: will filtering for a single IP tell you whether it is the worst offender?

  • Filter the failed attempts for a specific IP by running:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | grep "203.0.113.42"

You can see that 203.0.113.42 is definitely attacking. Lines scroll past confirming its failed attempts. But here is the problem: is this IP the worst offender, or just one of many? You have no way to compare it against the other attacker IPs.

This is the limitation of grep on its own. It finds matching lines and counts them, but it cannot extract specific fields, count duplicates, or rank results. You are staring at raw data with no way to summarize it.

Why is this frustrating on purpose?

This is exactly the wall that every new sysadmin hits. grep is powerful for finding needles in a haystack, but the real questions ("which IP attacked the most?" or "what username do attackers guess first?") need tools that can extract, count, and rank.

In the next step, you will learn the pipeline pattern that answers those questions in a single command.

You have confirmed that your server is under heavy attack. You know the threats are there. What you are missing is the intelligence layer that tells you which threats matter most. In the next step, you will chain simple tools into pipelines that answer that question in one line.

Extract Intelligence with Pipelines

In Step 3, you used grep to find every failed login attempt in your log file. You proved attackers are there. But when you tried to figure out which IP address attacked the most, you hit a wall of text with no way to count or rank anything.

This step introduces the Linux pipeline pattern. By chaining simple tools together with pipes, you can extract specific fields from log lines, count how often each value appears, and rank the results. One command will answer the question that grep alone could not.

In this step, get ready to:
  • Extract specific fields from log lines using awk.
  • Count and rank occurrences using sort, uniq -c, and sort -rn.
  • Apply the pipeline pattern to answer different security questions about attackers, usernames, and timing.
Extract IP addresses with awk

Right now, grep gives you entire log lines. To analyze the data, you need to pull out just the fields you care about. awk splits each line into fields by whitespace and lets you print any specific field.

  • Extract just the IP addresses from failed login lines by running this command:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | awk '{print $11}' | head -10

You should see a clean list of 10 IP addresses. Compare this to the full log lines from Step 3.

What does this command do?

  • grep "Failed password" filters for failed login lines, same as Step 3.
  • The pipe | sends grep's output into awk as input.
  • awk '{print $11}' prints only the 11th field from each line. In the auth log format, field $11 is the attacker's IP address.
  • head -10 shows just the first 10 results so the output stays manageable.
  • Extract usernames instead by changing the field number:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | awk '{print $9}' | head -10

You should see a list of usernames like admin, root, and ubuntu. Changing one number in the awk command extracts completely different data from the same log lines.

Why field $9?

In the log line format Failed password for USERNAME from IP port PORT ssh2, the username is the 9th field when split by whitespace. Field $9 is the username, and field $11 is the IP address.

Seeing no output or an error?

  • Make sure the quotes around Failed password are straight double quotes, not curly quotes.
  • The awk part uses single quotes on the outside and curly braces inside: '{print $11}'. Mixing up single and double quotes will cause unexpected results.
  • Check that each pipe | character is present between commands. A missing pipe will run only the first part.

Still stuck? Help me debug why my awk pipeline is not producing output from my auth log file.

Count and rank with sort, uniq, and sort again

You can now extract IP addresses and usernames from the log. But the extracted list still has duplicates scattered throughout. To answer "which IP attacked the most?", you need to count how many times each IP appears and sort by that count.

Think back to Step 3, where you could not answer which IP attacked the most. Before running the next command, predict: do you think one IP will stand out as the worst offender, or will the attempts be spread evenly across all five attacker IPs?

  • Build the full analysis pipeline by running this command:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -5

You should see a clean, ranked list of the top 5 attacker IPs with their attempt counts. The IP with the most failed attempts sits at the top. The question you could not answer in Step 3 is now answered in one command.

What does each step in the pipeline do?

  • grep "Failed password" finds the failed login lines.
  • awk '{print $11}' extracts just the IP addresses.
  • sort alphabetically sorts the IPs so identical ones are adjacent. This is required before uniq.
  • uniq -c counts consecutive identical lines and prefixes each with its count.
  • sort -rn sorts numerically (-n) in reverse (-r) order, putting the highest counts first.
  • head -5 shows only the top 5 results.

This is the most important pattern in Linux log analysis: extract a field, sort, count with uniq -c, and sort numerically in reverse. You will use this pattern over and over.

Seeing all counts as 1?

If every IP shows a count of 1, you probably left out the first sort before uniq -c. uniq only counts consecutive identical lines. Without sorting first, duplicate IPs scattered across the file each get counted separately.

Help me fix my pipeline so that uniq -c correctly counts duplicate IP addresses in my auth log.

Apply the pattern to other questions

The pipeline pattern works for any field in the log. By swapping the field number in awk, you can rank completely different data from the same log file.

  • Find the top 5 most targeted usernames by running this command:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | awk '{print $9}' | sort | uniq -c | sort -rn | head -5

Same pipeline, different field. You should see usernames like root and admin near the top. These are the most commonly guessed usernames by attackers.

  • See failed attempts broken down by hour by running this command:
grep "Failed password" ~/log-analyzer/logs/sample_auth.log | awk '{print $3}' | cut -d: -f1 | sort | uniq -c | sort -rn

You should see a list of hours (0 through 23) with counts next to each one. This reveals when attacks happen most frequently.

What is cut doing here?

  • Field $3 is the timestamp (like 14:30:01).
  • cut -d: -f1 uses : as a delimiter and extracts just the first part, which is the hour.
  • The rest of the pipeline (sort | uniq -c | sort -rn) counts and ranks the hours, showing you which hours have the most attack activity.

Hourly counts look wrong?

Make sure the cut command uses a colon as the delimiter (-d:) with no space between -d and the colon. If you see full timestamps instead of just hour numbers, the delimiter is not being recognized.

Help me troubleshoot why cut is not extracting just the hour from my log timestamps.

Your analysis pipelines can now answer specific security questions in a single command. Next up, you will combine everything into an automated script that generates a complete security report in one run.

Build the Automated Security Report

In Step 4, you discovered the power of piping grep, awk, sort, and uniq together to extract ranked intelligence from raw log data. Each pipeline answered a specific question in a single command.

Typing those pipelines from memory every time you need a security check is tedious and error-prone. A real sysadmin wraps these commands into a reusable script that generates a complete report in one run. In this step, you will combine everything into a single automated tool.

In this step, get ready to:
  • Create a Bash script that combines all your analysis pipelines into one report.
  • Run the script to generate a formatted security report.
  • Verify the report was saved to a file.
Create the security report script

This script will read your sample_auth.log file, run every pipeline you learned in Steps 3 and 4, and print a formatted report to both the screen and a saved file. You will build it in two sections.

  • Open a new file in nano by running this command:
nano ~/log-analyzer/security_report.sh
  • Type the first section of the script, which sets up variables, checks that the log file exists, and counts log entries:
#!/bin/bash

# Path to the log file and where to save the report
LOG_FILE="$HOME/log-analyzer/logs/sample_auth.log"
REPORT_FILE="$HOME/log-analyzer/reports/security_report.txt"

# Create the reports directory if it doesn't exist
mkdir -p "$HOME/log-analyzer/reports"

# Check that the log file exists before proceeding
if [ ! -f "$LOG_FILE" ]; then
    echo "Error: Log file not found at $LOG_FILE"
    echo "Run generate_logs.sh first to create sample data."
    exit 1
fi

# Count total entries, failed attempts, and successful logins
TOTAL=$(wc -l < "$LOG_FILE")
FAILED=$(grep -c "Failed password" "$LOG_FILE")
ACCEPTED=$(grep -c "Accepted password" "$LOG_FILE")

What does this code do?

LOG_FILE and REPORT_FILE store the paths to your input log and output report. Using variables means you only change the path in one place if you ever point the script at a different log.

The if [ ! -f "$LOG_FILE" ] block is a safety check. If the log file is missing, the script prints a helpful error message and exits instead of crashing with a confusing error later.

The three counting lines use command substitution with $() to run a command and capture its output into a variable. These are the same wc -l and grep -c commands you ran interactively in earlier steps.

  • Continue typing directly below the code you just entered. This section generates the formatted report and writes it to both the screen and a file:
{
    echo "============================================"
    echo "   SECURITY LOG ANALYSIS REPORT"
    echo "   Generated: $(date)"
    echo "============================================"
    echo ""
    echo "--- OVERVIEW ---"
    echo "Total log entries:       $TOTAL"
    echo "Failed login attempts:   $FAILED"
    echo "Successful logins:       $ACCEPTED"
    echo ""
    echo "--- TOP 10 ATTACKING IPs ---"
    grep "Failed password" "$LOG_FILE" | awk '{print $11}' | sort | uniq -c | sort -rn | head -10
    echo ""
    echo "--- TOP 10 TARGETED USERNAMES ---"
    grep "Failed password" "$LOG_FILE" | awk '{print $9}' | sort | uniq -c | sort -rn | head -10
    echo ""
    echo "--- FAILED ATTEMPTS BY HOUR ---"
    grep "Failed password" "$LOG_FILE" | awk '{print $3}' | cut -d: -f1 | sort | uniq -c | sort -rn
    echo ""
    echo "--- SUCCESSFUL LOGINS ---"
    grep "Accepted password" "$LOG_FILE" | awk '{print $9, "from", $11}' | sort | uniq -c | sort -rn
    echo ""
    echo "============================================"
    echo "   END OF REPORT"
    echo "============================================"
} | tee "$REPORT_FILE"

echo ""
echo "Report saved to: $REPORT_FILE"

What does this code do?

The curly braces { ... } group all the echo statements and pipelines into a single block of output. Piping that entire block into tee sends it to both the terminal screen and the report file at the same time.

Inside the block, you will recognize the exact pipelines you built in Step 4. The IP ranking pipeline uses awk '{print $11}' to extract attacker IPs. The username pipeline uses awk '{print $9}' to extract targeted usernames. The hourly breakdown uses cut -d: -f1 to extract just the hour from each timestamp.

The last section extracts successful logins so you can compare legitimate activity against attack patterns.

  • Save the file by pressing Ctrl+O, then press Enter.
  • Exit nano by pressing Ctrl+X.

✔️ Awesome, I've got everything!

Great. Double check you have saved security_report.sh and exited nano before moving on.

ⓧ I'd like to double check the full code

Your security_report.sh file should contain two sections, in this order:

  • Section 1 (Setup): The shebang line (#!/bin/bash), the LOG_FILE and REPORT_FILE variables, the mkdir -p line, the if [ ! -f ... ] safety check, and three counting variables (TOTAL, FAILED, ACCEPTED). Scroll up to the first code block in this substep to compare.
  • Section 2 (Report output): The { ... } | tee "$REPORT_FILE" block containing the header, OVERVIEW, TOP 10 ATTACKING IPs, TOP 10 TARGETED USERNAMES, FAILED ATTEMPTS BY HOUR, SUCCESSFUL LOGINS, and the footer. Scroll up to the second code block to compare.

If anything is missing, reopen the file with nano ~/log-analyzer/security_report.sh and correct it.

Make the script executable and run it
  • Grant execute permission to your script by running this command:
chmod +x ~/log-analyzer/security_report.sh

Before you run the script, think about what you expect to see. You built pipelines for attacker IPs, targeted usernames, hourly breakdowns, and successful logins in Step 4. What will the report look like when all of those run together?

  • Run the security report by running these commands:
cd ~/log-analyzer && ./security_report.sh

A formatted report prints to your terminal. You should see the OVERVIEW section with total entries, failed attempts, and successful logins. Below it, the TOP 10 ATTACKING IPs section shows a ranked list of attacker IPs with their attempt counts.

Keep scrolling down. You should also see sections for TOP 10 TARGETED USERNAMES, FAILED ATTEMPTS BY HOUR, and SUCCESSFUL LOGINS. The report ends with a message confirming it was saved to a file.

Why does the report appear on screen and save to a file?

The tee command reads from standard input and writes to both standard output (your screen) and a file simultaneously. This is the standard approach for generating reports that you can read immediately and also keep for later.

Seeing a permission denied error?

  • Make sure you ran chmod +x ~/log-analyzer/security_report.sh before trying to execute the script.
  • Check that the first line of your script is exactly #!/bin/bash with no spaces before the #.
  • If you see the error about the log file not being found, run ./generate_logs.sh first to regenerate the sample data.

Why is my security report script not running?

Verify the saved report
  • Confirm the report was saved to a file by running this command:
cat ~/log-analyzer/reports/security_report.txt

You should see the same formatted report printed again. This is the file a sysadmin would attach to a security ticket or email to a team lead.

  • Check the file details by running this command:
ls -la ~/log-analyzer/reports/

You should see security_report.txt listed with its file size and timestamp.

Report file is empty or missing?

  • If the file does not exist, make sure your script includes the mkdir -p "$HOME/log-analyzer/reports" line and the | tee "$REPORT_FILE" at the end of the curly-brace block.
  • If the file is empty, check that the closing } brace is on its own line, directly followed by | tee "$REPORT_FILE" on the same line.

Help me fix my security report file output.

Secret mission

Add Brute-Force Threshold Alerting

Your report shows the data, but it does not make judgments. A real security tool should flag IPs that cross a danger threshold and call them out explicitly. Can you add configurable brute-force alerting to your script?

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 on your Ubuntu VM, so there are no ongoing costs.

Resources you used:

  • ~/log-analyzer/ project directory (contains your scripts, sample log data, and generated reports).
  • nano text editor (installed via apt in Step 1).

Keep everything running

No action needed. Choose this if you want to keep practicing with your scripts or modify them further.

  • Your project files stay in ~/log-analyzer/. You can re-run ./generate_logs.sh any time to create fresh log data, then run ./security_report.sh to analyze it.
  • nano remains installed and available for editing files across your Ubuntu system.

Pause - I'll come back to this later

There are no running processes to stop. All your project files are stored on disk and will be there when you come back.

  • If you want to free up resources on your host machine, shut down your Ubuntu VM from within the VM by running:
sudo shutdown now
  • Your ~/log-analyzer/ directory and all its contents will still be there when you start the VM again.

Delete - I don't want to use this again

Remove all project files from your Ubuntu system. This deletes your scripts, generated logs, and saved reports.

  • Remove the entire project directory by running:
rm -rf ~/log-analyzer
  • Verify the directory is gone by running:
ls ~/log-analyzer
  • You should see an error message confirming the directory no longer exists.
  • (Optional) If you installed nano as part of this project and no longer need it, remove it by running:
sudo apt remove -y nano
  • Skip this if nano was already installed on your system before this project. It is a useful general-purpose text editor.

Nice Work!

Nice Work!

You just built a working security log analyzer on Ubuntu from scratch. Your script chews through 500 authentication log entries and spits out a clean, ranked security report in seconds.

You've learned how to:

  • Navigate the Linux filesystem and manage files using core sysadmin commands like cd, ls, mkdir, cat, and chmod.
  • Build analysis pipelines chaining grep, awk, sort, uniq, and cut to extract ranked threat intelligence from raw log data.
  • Write a reusable Bash security report script that analyzes authentication logs and saves a formatted report to a file using tee.
  • Secret Mission: Added brute-force threshold alerting with configurable detection logic that flags IPs exceeding a set number of failed attempts.

Ready to quiz yourself?