When using openssl s_client
for HTTP requests, many developers encounter the frustrating behavior where the command doesn't terminate after receiving the response. The root cause lies in SSL's connection persistence combined with the -quiet
flag's implicit -ign_eof
setting.
Here are three reliable approaches to force termination while still capturing the response:
Method 1: Timeout with head
echo -e "GET /test HTTP/1.1\\r\\nHost:$(hostname)\\r\\nConnection: close\\r\\n\\r\\n" | \
openssl s_client -quiet -connect server-url:443 | \
(head -n1 > /dev/null && cat) | \
grep -o -P '(?<=Result>).*(?=)'
Method 2: HTTP/1.0 with Connection: close
echo -e "GET /test HTTP/1.0\\r\\nHost:$(hostname)\\r\\nConnection: close\\r\\n\\r\\n" | \
openssl s_client -quiet -connect server-url:443 | \
grep -o -P '(?<=Result>).*(?=)'
Method 3: Content-Length Parsing
For more complex responses where you need to read exactly the right amount of data:
response=$(echo -e "GET /test HTTP/1.1\\r\\nHost:$(hostname)\\r\\n\\r\\n" | \
openssl s_client -quiet -connect server-url:443)
content_length=$(echo "$response" | grep -i 'content-length:' | awk '{print $2}' | tr -d '\r')
if [ -n "$content_length" ]; then
echo "$response" | head -c $((${#response} - $(echo "$response" | wc -c) + $content_length + 1))
else
echo "$response"
fi | grep -o -P '(?<=Result>).*(?=)'
When implementing this in production scripts:
- Always add timeout protection
- Validate SSL certificates unless in controlled environments
- Consider using
curl
instead if available (though openssl is valuable in minimal environments)
If you're still seeing hanging connections:
# Add full HTTP headers for debugging
echo -e "GET /test HTTP/1.1\\r\\nHost:$(hostname)\\r\\nUser-Agent: openssl-test\\r\\nAccept: */*\\r\\nConnection: close\\r\\n\\r\\n" | \
openssl s_client -debug -connect server-url:443
Remember that some servers may ignore HTTP/1.1's Connection: close
header, making HTTP/1.0 the more reliable choice for immediate termination.
When using OpenSSL's s_client
for HTTP requests, many developers encounter the frustrating behavior where the connection remains open indefinitely after receiving the response. This occurs because:
-quiet
mode implicitly sets-ign_eof
- HTTP/1.1 connections are persistent by default
- The server waits for additional requests
Here are three working approaches to force connection closure:
1. Using Connection: close header
printf "GET /test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" | \
openssl s_client -quiet -connect server-url:443 2>/dev/null
2. Forcing HTTP/1.0 protocol
printf "GET /test HTTP/1.0\r\nHost: example.com\r\n\r\n" | \
openssl s_client -quiet -connect server-url:443 2>/dev/null
3. Timeout-based solution
echo -e "GET /test HTTP/1.1\r\nHost: example.com\r\n\r\n" | \
timeout 5 openssl s_client -quiet -connect server-url:443
For parsing XML responses like <Result>success</Result>
:
response=$(printf "GET /test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" | \
openssl s_client -quiet -connect server-url:443 2>/dev/null)
# Extract content between Result tags
result=$(echo "$response" | grep -oP '(?<=<Result>).*(?=</Result>)')
echo "Server response: $result"
Here's a complete bash function for reliable HTTPS requests:
https_get() {
local host="$1"
local path="$2"
local port="${3:-443}"
exec 3<> /dev/tcp/"$host"/"$port"
printf "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n" "$path" "$host" >&3
cat <&3
exec 3>&-
}
# Usage:
response=$(https_get "example.com" "/api/test")