Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Created July 19, 2025 13:07
Show Gist options
  • Save dzogrim/c3a841e72fac41e8259483fea8aa92fd to your computer and use it in GitHub Desktop.
Save dzogrim/c3a841e72fac41e8259483fea8aa92fd to your computer and use it in GitHub Desktop.
checks the expiration dates of TLS certificates
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2015-2025 dzogrim
# SPDX-FileContributor: Sébastien L. <[email protected]>
# -----------------------------------------------------------------------------
# File: certs_validity.sh
# Description:
# This script checks the expiration dates of TLS certificates
# for common services (IMAP, SMTP, HTTPS) on a list of domains.
# It performs DNS resolution and TLS negotiation using OpenSSL.
#
# ProtonMail-hosted domains are automatically detected via MX lookup
# and their IMAP/SMTP entries are skipped, since these services
# are handled internally and not exposed via standard subdomains.
#
# Domains in the SERVERS list can include options like:
# - with_srv=false # disables testing the "srv." subdomain
#
# Output is color-coded:
# - RED → certificate unavailable, invalid, or expired
# - BLACK on YELLOW → certificate valid but expires within $DAYS days
# - default (white) → certificate valid for more than $DAYS days
# - YELLOW → service skipped due to ProtonMail MX
#
# Requirements:
# - GNU bash
# - openssl
# - host (from bind-utils)
# - GNU date (e.g. via coreutils on macOS)
set -euo pipefail
# Domain list: domain|key=value[,key=value...]
SERVERS=(
"google.com|with_srv=false"
"microsoft.tld|with_srv=false"
"dzogrim.pw|with_srv=true"
)
# Services and ports to test
declare -A SERVICES=(
["imap"]="993"
["smtp"]="465"
["www"]="443"
["srv"]="443"
)
DAYS=30
# ANSI colors
RED='\033[0;31m'
YELLOW='\033[0;33m'
BLACK_ON_YELLOW='\033[30;43m'
NC='\033[0m'
# Ensure required commands are available
require_bin() {
local bin="$1"
if ! command -v "$bin" &>/dev/null; then
echo "❌ Missing required binary: $bin"
exit 1
fi
}
# Check MX for ProtonMail hosting
is_proton_dns() {
local domain="$1"
dig +short MX "$domain" | awk '{print $2}' | grep -qE '\.protonmail\.ch\.$'
}
# Extract key=value options from SERVER entry
get_domain_option() {
local entry="$1" key="$2"
local meta="${entry#*|}"
[[ "$meta" == "$entry" ]] && echo "" && return 0 # no metadata
IFS=',' read -ra kvs <<< "$meta"
for kv in "${kvs[@]}"; do
[[ "$kv" == "$key="* ]] && echo "${kv#*=}" && return 0
done
echo ""
}
# Check dependencies
require_bin host
require_bin openssl
require_bin date
if ! date --version >/dev/null 2>&1; then
echo "❌ GNU date required (e.g. install coreutils on macOS)"
exit 1
fi
# Main loop
for entry in "${SERVERS[@]}"; do
domain="${entry%%|*}"
with_srv=$(get_domain_option "$entry" "with_srv")
[[ -z "$with_srv" ]] && with_srv="true"
is_proton="no"
if is_proton_dns "$domain"; then
is_proton="yes"
fi
printf '\nDomain: %s\n' "$domain"
for service in "${!SERVICES[@]}"; do
fqdn="${service}.${domain}"
port="${SERVICES[$service]}"
# Skip srv if not supported
if [[ "$service" == "srv" && "$with_srv" != "true" ]]; then
continue
fi
# Skip imap/smtp for Proton domains
if [[ "$is_proton" == "yes" && ( "$service" == "imap" || "$service" == "smtp" ) ]]; then
printf ' %-28s: %bProtonMail MX → ignored%b\n' "$fqdn" "$YELLOW" "$NC"
continue
fi
# DNS check
if ! host "$fqdn" &>/dev/null; then
printf ' %-28s: %bUnavailable (NXDOMAIN)%b\n' "$fqdn" "$RED" "$NC"
continue
fi
# Cert expiration
# Cert expiration
set +e
output=$(timeout 5 bash -c "echo | openssl s_client -servername \"$fqdn\" \
-connect \"$fqdn:$port\" 2>/dev/null")
status=$?
set -e
if [[ $status -eq 124 ]]; then
printf ' %-28s: %bTimeout after 5s%b\n' "$fqdn" "$RED" "$NC"
continue
fi
expiry=$(echo "$output" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [[ -z "$expiry" ]]; then
printf ' %-28s: %bUnavailable (No cert)%b\n' "$fqdn" "$RED" "$NC"
continue
fi
expiry_ts=$(date -d "$expiry" +%s 2>/dev/null)
now_ts=$(date +%s)
if [[ -z "$expiry_ts" ]]; then
printf ' %-28s: %bInvalid date%b\n' "$fqdn" "$RED" "$NC"
continue
fi
delta_days=$(( (expiry_ts - now_ts) / 86400 ))
if (( delta_days < 0 )); then
color="$RED"
elif (( delta_days < ${DAYS} )); then
color="$BLACK_ON_YELLOW"
else
color="$NC"
fi
printf ' %-28s: %b%s%b\n' "$fqdn" "$color" "$expiry" "$NC"
done
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment