Hello! Welcome to the final lesson in our Practical Bash Automation Projects module.
Over the past few lessons, we've built a powerful toolkit of scripts to handle essential system administration tasks, including system monitoring, automated backups, log rotation, and user onboarding. You've done excellent work applying core Bash concepts to solve practical, real-world problems.
Today, we'll build a script that lies at the heart of modern service reliability: a health checker. In DevOps, ensuring that your applications and services are running correctly is a top priority. The simplest way to do this for a web service is to check if it's responding to requests as expected.
Today's Learning Outcome: By the end of this lesson, you will be able to develop a Bash script to check the health status of a web service by making an HTTP request and checking the response code.
This project will combine your scripting skills with fundamental networking concepts, providing a perfect capstone for this module before we move on to our next topic.
1. The Basic Health Check: Using curl
The primary tool for making web requests from the command line is curl. We can use it to fetch a web page, but for a health check, we're not interested in the page's content. We only care about the HTTP status code the server returns. A 200 OK status means success, while other codes like 404 Not Found or 500 Internal Server Error indicate a problem.
Creating a Website Monitoring Script with Bash
The article 'Creating a Website Monitoring Script with Bash' provides the perfect starting point. It shows how to use curl to get only the HTTP status code without downloading the entire page.
Read the section 'The Basic Script'. Pay close attention to the curl command and the breakdown of its flags: -s, -o, and -w. This is the core command we will build upon.
As the resource explains, the command is:curl -s -o /dev/null -w "%{http_code}" <URL>
-s: Silent mode. Don't show progress meters or error messages.-o /dev/null: Redirect output. Send the actual page content (the response body) to/dev/null, which is a special file that discards everything sent to it.-w "%{http_code}": Write-out. After the transfer, print the HTTP response code to standard output.
Let's turn this into a very basic script. Create a new file named health_check.sh and make it executable (chmod +x health_check.sh).
#!/bin/bash
# A public API endpoint for testing
TARGET_URL="https://jsonplaceholder.typicode.com/posts/1"
echo "Checking status of ${TARGET_URL}..."
# Get the HTTP status code
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${TARGET_URL}")
# Check if the status code is 200
if [ "${HTTP_CODE}" -eq 200 ]; then
echo "SUCCESS: Service is up and running."
else
echo "FAILURE: Service is down or returned an error. HTTP Code: ${HTTP_CODE}"
fi
Run this script (./health_check.sh). It should report that the service is running. Now, try changing the URL to a non-existent one, like https://jsonplaceholder.typicode.com/invalid, and run it again. You should see a 404 error.
2. Building a More Informative Script
A simple "up" or "down" message is a good start, but we can make our script much more useful by providing specific feedback based on the type of error. In the world of web services, a 404 Not Found error means something very different from a 500 Internal Server Error.
Let's enhance our script to handle different categories of HTTP status codes.
Creating a Website Monitoring Script with Bash
Let's expand on our basic script by checking for more specific error codes. The article 'Creating a Website Monitoring Script with Bash' provides a great example of how to do this using an if/elif/else structure.
In the provided resource, examine the check_website() function. Focus on the if/elif/else block that checks for different response codes like 403, 404, and 500. We will use this pattern to improve our script.
Let's also make our script more flexible by accepting the URL as a command-line argument instead of hardcoding it. Here is the improved version of health_check.sh:
#!/bin/bash
# 1. Validate input
if [ -z "$1" ]; then
echo "Usage: $0 <URL>"
exit 1
fi
TARGET_URL=$1
echo "Checking status of ${TARGET_URL}..."
# 2. Get the HTTP status code
# Note: curl will return '000' if it can't connect (e.g., DNS error, host down)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${TARGET_URL}")
# 3. Interpret the code
if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 300 ]; then
echo "SUCCESS: Service is up and running (HTTP ${HTTP_CODE})."
exit 0
elif [ "${HTTP_CODE}" -ge 400 ] && [ "${HTTP_CODE}" -lt 500 ]; then
echo "CLIENT ERROR: Check the URL or permissions (HTTP ${HTTP_CODE})."
exit 1
elif [ "${HTTP_CODE}" -ge 500 ] && [ "${HTTP_CODE}" -lt 600 ]; then
echo "SERVER ERROR: The service is having issues on its end (HTTP ${HTTP_CODE})."
exit 1
else
echo "FAILURE: Could not connect or received an unexpected code (HTTP ${HTTP_CODE})."
exit 1
fi
Key Improvements:
- Argument Validation: The script now requires a URL as an argument, making it reusable.
- Status Code Ranges: Instead of checking for just
200, we check for ranges. This is more robust, as any2xxcode indicates success. Similarly,4xxcodes are client errors, and5xxcodes are server errors. - Exit Codes: The script now
exits with0on success and1on failure. This is a crucial practice in scripting, as it allows other scripts or automation tools (like a CI/CD pipeline) to know whether this check succeeded or failed.
Test your understanding!
A website you are monitoring is configured to automatically redirect from http:// to https://. When you check the http:// version, curl returns a status code of 301 (Moved Permanently). How would you modify the script's logic to treat a 3xx redirect as a success?
Show answer
You would add another elif condition to check for the 3xx range, similar to how we check for 2xx, 4xx, and 5xx. You could also modify the 2xx check to include 3xx codes.
For example, you could change the first condition to:if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 400 ]; then
This would treat any code from 200 to 399 as a success.
3. Making the Script Production-Ready
Our script is much better, but there are two common real-world failure scenarios it doesn't handle well:
- Network Unresponsiveness: What if the server we're checking is frozen and never responds? Our
curlcommand could hang indefinitely. - Local Connectivity Issues: What if the service is perfectly fine, but our own machine has lost its internet connection? Our script would incorrectly report that the service is down.
Let's address these by adding timeouts and a pre-flight connectivity check.
Script to Automatically Test if a Website Is Available
The article 'Script to Automatically Test if a Website Is Available' from Baeldung is excellent for learning how to build a truly robust monitoring script. We'll borrow two key ideas from it: checking for an internet connection first, and setting a timeout for the web request.
First, read section 4, 'Checking Internet Connection,' to see how ping can be used to verify connectivity before you even try to reach the target website. Then, read the first paragraph of section 5, 'Checking HTTP Code and Response Content,' which introduces the --max-time flag in curl to prevent the script from hanging.
Let's integrate these two concepts into our final script.
Final Health Check Script
Here is the complete health_check.sh script incorporating these robustness checks.
#!/bin/bash
# --- 1. Validate Input ---
if [ -z "$1" ]; then
echo "Usage: $0 <URL>"
exit 1
fi
TARGET_URL=$1
# Set a timeout for the curl command (in seconds)
TIMEOUT=10
# --- 2. Pre-flight Check: Internet Connectivity ---
# Ping a reliable public DNS server. -c 1 sends one packet, -W 1 sets a 1-sec timeout.
if ! ping -c 1 -W 1 8.8.8.8 &> /dev/null; then
echo "NETWORK ERROR: Could not reach the internet. Please check your connection."
exit 1
fi
echo "Internet connectivity OK. Checking status of ${TARGET_URL}..."
# --- 3. Perform Health Check with Timeout ---
HTTP_CODE=$(curl --max-time ${TIMEOUT} -s -o /dev/null -w "%{http_code}" "${TARGET_URL}")
CURL_EXIT_CODE=$?
# --- 4. Interpret Results ---
# Check curl's exit code first. A non-zero code indicates a curl-level error (e.g., timeout, DNS lookup failure).
# 28 is the exit code for a timeout.
if [ ${CURL_EXIT_CODE} -ne 0 ]; then
if [ ${CURL_EXIT_CODE} -eq 28 ]; then
echo "FAILURE: Connection timed out after ${TIMEOUT} seconds."
else
echo "FAILURE: curl command failed with exit code ${CURL_EXIT_CODE}. Unable to connect."
fi
exit 1
fi
# If curl succeeded, check the HTTP code
if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 300 ]; then
echo "SUCCESS: Service is up and running (HTTP ${HTTP_CODE})."
exit 0
elif [ "${HTTP_CODE}" -ge 400 ] && [ "${HTTP_CODE}" -lt 500 ]; then
echo "CLIENT ERROR: Check the URL or permissions (HTTP ${HTTP_CODE})."
exit 1
elif [ "${HTTP_CODE}" -ge 500 ] && [ "${HTTP_CODE}" -lt 600 ]; then
echo "SERVER ERROR: The service is having issues on its end (HTTP ${HTTP_CODE})."
exit 1
else
# Includes the '000' code from curl when it connects but gets no HTTP response
echo "FAILURE: Could not connect or received an unexpected code (HTTP ${HTTP_CODE})."
exit 1
fi
Final Improvements:
- Connectivity Check: The script now pings
8.8.8.8(Google's public DNS) to make sure there's a general internet connection before proceeding. - Timeout: The
curlcommand will now give up after 10 seconds, preventing the script from hanging. curlExit Code: We now also check the exit code ofcurlitself ($?). This allows us to specifically detect low-level errors like a connection timeout (exit code 28) before we even look at the HTTP status code.
Testing the Final Script
- Success Case:
./health_check.sh https://www.google.com - Server Error Case:
./health_check.sh http://httpstat.us/503(This site returns a 503 error). - Client Error Case:
./health_check.sh https://www.google.com/nonexistentpage - Timeout Case:
./health_check.sh http://httpstat.us/200?sleep=15000(This will take 15 seconds, exceeding our 10-second timeout). - Connectivity Case: Disconnect your computer from the network and run any check. You should see the "NETWORK ERROR" message.
Conclusion
Congratulations! You have successfully built a robust health-checking script, a fundamental tool in any DevOps engineer's arsenal. This project wraps up our journey into Bash automation by combining system commands, networking, error handling, and robust scripting practices.
Key Takeaways:
curlis the go-to command-line tool for interacting with web services.- Checking HTTP status codes is the primary method for basic service health monitoring.
- A production-ready script must be robust: it should handle timeouts, network failures, and different classes of errors gracefully.
- Using script exit codes (
0for success, non-zero for failure) is essential for integrating your scripts into larger automation workflows.
Next Up: Python for DevOps
You have built a fantastic foundation with Linux and Bash. You can navigate the system, manage resources, and automate a wide range of administrative tasks.
While Bash is the undisputed champion for orchestrating command-line tools, some tasks become complex. When you need to work with structured data like JSON, interact with complex APIs, or write more intricate logic, a general-purpose programming language is often a better choice.
This is the perfect time to transition to our next module: Python for DevOps Engineers. Python's clear syntax and powerful libraries have made it the de facto language for cloud automation, API interaction, and configuration management. We will start by learning the basics of Python and quickly see how it can make tasks like today's health check even more powerful and easier to write.