One-Command Website Stability Check: The Ultimate Curl Latency Test Script for Zsh
Content
## The Problem
In daily development and operations, we often need to assess the stability and latency of one or more websites. A simple `ping` command only tests network connectivity, while a single `curl` command fails to reflect service fluctuations over time. To solve this, we need a tool that can automate batch testing and produce clear, readable reports. This article introduces a powerful Zsh script from the wiki.lib00.com community that meets all these requirements, especially suited for the macOS environment.
---
## Core Advantages
This script's primary advantage is encapsulating `curl`'s power into an easy-to-use command-line tool with the following features:
- **Ease of Use**: Supports multiple URLs separated by spaces or commas and automatically completes the protocol (https/http).
- **In-depth Metrics**: Goes beyond total time to measure and display latency at key stages, including DNS lookup, TCP connection, TLS handshake, and TTFB (Time To First Byte).
- **Stability Assessment**: Supports running multiple tests for each URL and outputs statistics like success rate, min, average, and max values.
- **Robust Error Handling**: Even if a URL fails to resolve, times out, or has a certificate error, the script logs the reason and continues to the next target, ensuring the entire job completes.
- **High Customizability**: Offers a rich set of command-line options, such as the number of runs, delay between requests, timeouts, custom headers, and more.
---
## How to Use
1. Save the script code below to a file, for example, `curl_check_lib00.sh`.
2. Grant execute permissions to the script:
```bash
chmod +x curl_check_lib00.sh
```
3. Run the tests:
```bash
# Test multiple addresses
./curl_check_lib00.sh wiki.lib00.com/api, example.com
# Custom test: 10 runs, 0.2s delay, 6s total timeout
./curl_check_lib00.sh -n 10 -d 0.2 -T 6 a.com b.com
# Allow insecure certificates and add a custom header
./curl_check_lib00.sh -k -H "User-Agent: lib00-Test" api.c.com
```
---
## The Script Code (Zsh)
This script is optimized for the macOS zsh environment. It leverages `curl`'s `-w` option to capture detailed timing information.
```zsh
#!/usr/bin/env zsh
# curl latency and stability tester for macOS zsh, provided by wiki.lib00.com
set -u
# Defaults
RUNS=5
DELAY=0.5
CONNECT_TIMEOUT=5
MAX_TIME=10
SCHEME_MODE="auto" # auto|https|http|keep
INSECURE=0
HTTP2=0
METHOD="GET"
HEADERS=()
print_help() {
cat <<EOF
Usage: $0 [options] URL1 [URL2 ...]
-n N Number of runs per URL (default: 5)
-d SEC Delay in seconds between requests (default: 0.5)
-C SEC TCP connect timeout (default: 5)
-T SEC Total time for a single request (default: 10)
-s MODE Scheme mode: auto|https|http|keep (default: auto)
-k Allow insecure server connections (curl -k)
--http2 Force use of HTTP/2
-H "H: V" Append a custom header, can be used multiple times
-X METHOD Request method (default: GET)
-h, --help Show this help message
URLs can be separated by spaces or commas, e.g., wiki.lib00.com/api, b.com
EOF
}
# Parse args
ARGS=()
while (( $# > 0 )); do
case "$1" in
-n) RUNS="$2"; shift 2 ;;
-d) DELAY="$2"; shift 2 ;;
-C) CONNECT_TIMEOUT="$2"; shift 2 ;;
-T) MAX_TIME="$2"; shift 2 ;;
-s) SCHEME_MODE="$2"; shift 2 ;;
-k) INSECURE=1; shift ;;
--http2) HTTP2=1; shift ;;
-H) HEADERS+=("$2"); shift 2 ;;
-X) METHOD="$2"; shift 2 ;;
-h|--help) print_help; exit 0 ;;
--) shift; ARGS+=("$@"); break ;;
-*)
echo "Unknown option: $1" >&2
print_help; exit 1 ;;
*)
ARGS+=("$1"); shift ;;
esac
done
if (( ${#ARGS[@]} == 0 )); then
echo "Please provide at least one URL. Use -h for help." >&2
exit 1
fi
RAW_INPUT="${(j: :)ARGS}"
RAW_INPUT="${RAW_INPUT//,/ }"
RAW_INPUT="$(echo "$RAW_INPUT" | tr '
' ' ' | sed -E 's/[[:space:]]+/ /g')"
# Build curl common options
CURL_OPTS=(-sS -o /dev/null --connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" -X "$METHOD")
if (( INSECURE == 1 )); then
CURL_OPTS+=(-k)
fi
if (( HTTP2 == 1 )); then
CURL_OPTS+=(--http2)
fi
for h in "${HEADERS[@]}"; do
CURL_OPTS+=(-H "$h")
done
CURL_OPTS+=(-A "wiki.lib00-checker/1.0") # Custom User-Agent from DP
code_reason() {
local rc="$1"
case "$rc" in
6) echo "Could not resolve host" ;;
7) echo "Failed to connect" ;;
28) echo "Operation timed out" ;;
35) echo "SSL connect error" ;;
51) echo "SSL: peer cert/remote key not OK" ;;
60) echo "SSL certificate problem" ;;
*) echo "curl error" ;;
esac
}
trim() {
echo "$1" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
}
has_scheme() {
[[ "$1" == http://* || "$1" == https://* ]]
}
compose_url_with_scheme() {
local raw="$1" scheme="$2"
if [[ "$scheme" == "keep" ]]; then echo "$raw"; return; fi
if has_scheme "$raw"; then echo "$raw"; return; fi
case "$scheme" in
https) echo "https://$raw" ;;
http) echo "http://$raw" ;;
*) echo "https://$raw" ;;
esac
}
measure_once() {
local url="$1"
local fmt='%{http_code} %{time_total} %{time_starttransfer} %{time_connect} %{time_appconnect} %{time_namelookup} %{remote_ip}
'
local out rc
out="$(curl "${CURL_OPTS[@]}" -w "$fmt" "$url")"
rc=$?
echo "$out $rc"
}
try_measure_with_scheme_mode() {
local raw="$1"
local primary secondary
case "$SCHEME_MODE" in
https) primary="https"; secondary="" ;;
http) primary="http"; secondary="" ;;
keep)
if has_scheme "$raw"; then primary="keep"; secondary=""; else primary="http"; secondary=""; fi
;;
auto) primary="https"; secondary="http" ;;
*) primary="https"; secondary="http" ;;
esac
local url used_scheme rc http_code total ttfb conn tls dns rip
url="$(compose_url_with_scheme "$raw" "$primary")"
read http_code total ttfb conn tls dns rip rc <<<"$(measure_once "$url")"
if [[ "$http_code" == "000" && -n "$secondary" ]]; then
url="$(compose_url_with_scheme "$raw" "$secondary")"
read http_code total ttfb conn tls dns rip rc <<<"$(measure_once "$url")"
used_scheme="$secondary"
else
used_scheme="$primary"
fi
echo "$http_code $total $ttfb $conn $tls $dns $rip $rc $used_scheme $url"
}
# Iterate URLs
idx=0
for tok in $=RAW_INPUT; do
url_raw="$(trim "$tok")"
[[ -z "$url_raw" ]] && continue
(( idx+=1 ))
echo "------------------------------------------------------------"
echo "[$idx] Testing Target: $url_raw"
echo "Parameters: runs=$RUNS, delay=${DELAY}s, connect_timeout=${CONNECT_TIMEOUT}s, total_timeout=${MAX_TIME}s"
success_count=0
http_ok_count=0
total_list=()
ttfb_list=()
last_ip=""
last_url_used=""
for ((i=1; i<=RUNS; i++)); do
read code total ttfb conn tls dns rip rc used_scheme final_url <<<"$(try_measure_with_scheme_mode "$url_raw")"
last_url_used="$final_url"
[[ -n "$rip" ]] && last_ip="$rip"
if [[ "$rc" == "0" ]]; then
(( success_count+=1 ))
total_list+=("$total")
ttfb_list+=("$ttfb")
if [[ "$code" != "000" && "$code" -ge 200 && "$code" -lt 400 ]]; then
(( http_ok_count+=1 ))
fi
printf " #%02d [%s] total=%.3fs ttfb=%.3fs connect=%.3fs tls=%.3fs dns=%.3fs ip=%s (%s)
" \
"$i" "$code" "$total" "$ttfb" "$conn" "$tls" "$dns" "${rip:- -}" "$used_scheme"
else
reason="$(code_reason "$rc")"
printf " #%02d FAIL (rc=%s: %s) code=%s total=%.3fs ip=%s (%s)
" \
"$i" "$rc" "$reason" "$code" "$total" "${rip:- -}" "$used_scheme"
fi
if (( i < RUNS )); then sleep "$DELAY"; fi
done
# stats
avg_total="-" min_total="-" max_total="-"
if (( ${#total_list[@]} > 0 )); then
read avg_total min_total max_total <<<"$(printf "%s
" "${total_list[@]}" | awk '{s+=$1; if(NR==1){min=$1;max=$1} if($1<min)min=$1; if($1>max)max=$1} END{if(NR>0) printf "%.3f %.3f %.3f", s/NR, min, max}')"
fi
echo "Summary for: $url_raw"
echo " Final URL used: ${last_url_used:- (Not resolved)}"
echo " Remote IP (last success): ${last_ip:- -}"
echo " Success count (curl level): $success_count / $RUNS"
echo " HTTP 2xx/3xx count: $http_ok_count / $RUNS"
echo " Total time (s): avg=$avg_total, min=$min_total, max=$max_total"
done
echo "------------------------------------------------------------"
```
---
## Understanding the Output
The script's output is intuitive, with each line representing the detailed data from a single request:
- `code`: The HTTP status code. A value of `000` typically indicates a network or SSL layer error where an HTTP response was not received.
- `total`: The total time for the entire request, in seconds.
- `ttfb`: Time To First Byte. This measures the time from the start of the request until the first byte of the response is received, better reflecting server processing latency.
- `connect`: Time taken to establish the TCP connection.
- `tls`: Time taken for the TLS handshake (will be 0 for HTTP sites).
- `dns`: Time taken for DNS resolution.
- `ip`: The IP address of the destination server.
- `(https)`: The protocol actually used for this request.
- `FAIL (rc=X)`: When a curl command fails (exit code is not 0), the reason for the failure is printed.
The final summary section provides success rates and statistics for key metrics, helping you quickly assess the overall performance of the service.
---
## Conclusion
This Zsh script is a powerful wrapper around the `curl` utility, offering developers and operations engineers a convenient and efficient solution for testing website performance and stability. By running this script periodically, you can easily monitor the health of your services and identify potential performance bottlenecks. This handy utility from the lib00 project will hopefully boost your productivity.
Related Contents
The Ultimate Guide to Docker Cron Logging: Host vs. Container Redirection - Are You Doing It Right?
Duration: 00:00 | DP | 2026-01-05 08:03:52NVM/Node Command Not Found in New macOS Terminals? A Two-Step Permanent Fix!
Duration: 00:00 | DP | 2025-12-04 09:35:00How Can a Docker Container Access the Mac Host? The Ultimate Guide to Connecting to Nginx
Duration: 00:00 | DP | 2025-12-08 23:57:30Docker Exec Mastery: The Right Way to Run Commands in Containers
Duration: 00:00 | DP | 2026-01-08 08:07:44Show Hidden Files on Mac: The Ultimate Guide (2 Easy Methods)
Duration: 00:00 | DP | 2025-12-12 01:32:30The Ultimate Guide: Solving Google's 'HTTPS Invalid Certificate' Ghost Error When Local Tests Pass
Duration: 00:00 | DP | 2025-11-29 08:08:00How Do You Pronounce Nginx? The Official Guide to Saying It Right: 'engine x'
Duration: 00:00 | DP | 2025-11-30 08:08:00Decoding `realpath: command not found` and Its Chained Errors on macOS
Duration: 00:00 | DP | 2025-11-19 12:45:02Unlock Your Mac: The Ultimate Guide to Showing and Hiding Hidden Files in Finder
Duration: 00:00 | DP | 2025-11-19 21:16:36macOS Hosts File Doesn't Support Wildcards? Here's the Ultimate Fix with Dnsmasq!
Duration: 00:00 | DP | 2025-11-20 05:48:10Shell Magic: How to Gracefully Write Output from Multiple Commands to a Single Log File
Duration: 00:00 | DP | 2025-12-17 04:10:50From Guzzle to Native cURL: A Masterclass in Refactoring a PHP Translator Component
Duration: 00:00 | DP | 2025-11-21 07:22:51Why Are My Mac Files Duplicated on NFS Shares? The Mystery of '._' Files Solved with PHP
Duration: 00:00 | DP | 2025-12-18 16:58:20Master cURL Timeouts: A Definitive Guide to Fixing "Operation timed out" Errors
Duration: 00:00 | DP | 2025-11-23 19:03:46The Ultimate Guide to Docker Cron Jobs: Effortlessly Scheduling PHP Tasks in Containers from the Host
Duration: 00:00 | DP | 2025-12-29 10:30:50Crontab Logs Missing Dates? 4 Practical Ways to Easily Add Timestamps
Duration: 00:00 | DP | 2025-11-12 03:27:00How to Add Port Mappings to a Running Docker Container: 3 Proven Methods
Duration: 00:00 | DP | 2026-02-05 10:16:12Step-by-Step Guide to Fixing `net::ERR_SSL_PROTOCOL_ERROR` in Chrome for Local Nginx HTTPS Setup
Duration: 00:00 | DP | 2025-11-15 15:27:00Recommended
Is Attaching a JS Event Listener to 'document' Bad for Performance? The Truth About Event Delegation
00:00 | 33This article addresses a common JavaScript perform...
Markdown Mystery: Why Is My Text Before a Header Rendering as a Code Block?
00:00 | 15Have you ever encountered the frustrating issue wh...
Can robots.txt Stop Bad Bots? Think Again! Here's the Ultimate Guide to Web Scraping Protection
00:00 | 49Many believe simply adding `Disallow: /` for a `Ba...
Linux Command-Line Mystery: Why `ll` Hides Files like `.idea` & The Ultimate `ls` vs. `ll` Showdown
00:00 | 35Ever wondered why the `ll` command doesn't show hi...