Shell ScriptingReal-World ProjectAdvancedTLSSecurityMay 2026

Shell Scripting Real-World Projects: Certificate Management Script

Monitor TLS certificate expiry across all domains with configurable warning thresholds, Slack and email alerts, automated Certbot renewal triggers, post-renewal verification, and cron scheduling.

TLS certificate expiry causes outages that are entirely preventable. A certificate management script monitors expiry dates across all domains, alerts with configurable lead time, and can trigger automated renewals via Certbot — turning a recurring manual task into a zero-touch operation.

BASH
#!/usr/bin/env bash
# cert_check.sh — TLS certificate expiry monitoring

set -euo pipefail

DOMAINS=(
  "myapp.example.com"
  "api.example.com"
  "admin.example.com"
  "staging.example.com"
)
WARN_DAYS=30
CRITICAL_DAYS=14
ALERT_EMAIL="ops@example.com"
SLACK_URL="${SLACK_WEBHOOK_URL:-}"
ERRORS=0

get_expiry_days() {
  local domain="$1"
  local expiry_date

  expiry_date=$(echo | timeout 5 openssl s_client \
    -servername "${domain}" \
    -connect "${domain}:443" 2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | \
    cut -d= -f2)

  [[ -z "${expiry_date}" ]] && { echo "-1"; return; }

  local expiry_epoch now_epoch
  expiry_epoch=$(date -d "${expiry_date}" +%s 2>/dev/null || \
                 date -j -f "%b %d %H:%M:%S %Y %Z" "${expiry_date}" +%s)
  now_epoch=$(date +%s)
  echo $(( (expiry_epoch - now_epoch) / 86400 ))
}

echo "=== Certificate Expiry Report: $(date +%Y-%m-%d) ==="
printf "%-40s %10s %s\n" "Domain" "Days left" "Status"
printf "%.0s─" {1..60}; echo

for domain in "${DOMAINS[@]}"; do
  days=$(get_expiry_days "${domain}")

  if (( days < 0 )); then
    printf "%-40s %10s %s\n" "${domain}" "ERROR" "⚠ Cannot check"
    (( ERRORS++ ))
  elif (( days <= CRITICAL_DAYS )); then
    printf "%-40s %10d %s\n" "${domain}" "${days}" "✘ CRITICAL — renew immediately"
    [[ -n "${SLACK_URL}" ]] && curl -s -X POST "${SLACK_URL}" \
      -d "{\"text\":\"🔴 CRITICAL: ${domain} cert expires in ${days} days\"}" &>/dev/null
    (( ERRORS++ ))
  elif (( days <= WARN_DAYS )); then
    printf "%-40s %10d %s\n" "${domain}" "${days}" "⚠ WARNING — renew soon"
    [[ -n "${SLACK_URL}" ]] && curl -s -X POST "${SLACK_URL}" \
      -d "{\"text\":\"🟡 WARNING: ${domain} cert expires in ${days} days\"}" &>/dev/null
  else
    printf "%-40s %10d %s\n" "${domain}" "${days}" "✔ OK"
  fi
done

echo ""
(( ERRORS > 0 )) && { echo "Action required: ${ERRORS} domain(s)"; exit 1; }
echo "All certificates OK"
BASH
#!/usr/bin/env bash
# cert_renew.sh — Automated Let's Encrypt renewal + service reload

set -euo pipefail

LOG="/var/log/cert-renewal.log"
SLACK_URL="${SLACK_WEBHOOK_URL:-}"

log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "${LOG}"; }
slack() { [[ -n "${SLACK_URL}" ]] && curl -s -X POST "${SLACK_URL}" \
            -d "{\"text\":\"$*\"}" &>/dev/null; }

log "=== Certificate renewal check ==="

# ── Renew all certs approaching expiry ────────────────────
if certbot renew \
    --quiet \
    --non-interactive \
    --pre-hook "systemctl stop nginx" \
    --post-hook "systemctl start nginx" \
    --deploy-hook "systemctl reload nginx" \
    2>> "${LOG}"; then
  log "Renewal check complete"
else
  log "Renewal had errors — check ${LOG}"
  slack "⚠ Certbot renewal errors on $(hostname) — check ${LOG}"
  exit 1
fi

# ── Verify renewed certs ──────────────────────────────────
for domain in myapp.example.com api.example.com; do
  days=$(echo | timeout 5 openssl s_client \
    -servername "${domain}" -connect "${domain}:443" 2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | \
    cut -d= -f2 | xargs -I{} bash -c \
    'echo $(( ($(date -d "{}" +%s) - $(date +%s)) / 86400 ))')

  log "  ${domain}: ${days} days remaining"
  (( days < 7 )) && {
    slack "🔴 CRITICAL: ${domain} still expiring in ${days} days after renewal!"
    exit 1
  }
done

log "All certificates verified"
slack "✅ Certificates renewed and verified on $(hostname)"

# Cron entry:
# 0 3 * * * /opt/scripts/cert_renew.sh >> /var/log/cert-renewal.log 2>&1
cert_check.sh
vriddh@prod-01:~/scripts$./cert_check.sh
=== Certificate Expiry Report: 2026-05-01 ===
Domain Days left Status
myapp.example.com 87 ✔ OK
api.example.com 87 ✔ OK
admin.example.com 22 ⚠ WARNING — renew soon
staging.example.com 8 ✘ CRITICAL — renew immediately
Action required: 2 domain(s)
✔ Certificate management rules — Check expiry with openssl s_client and openssl x509 -noout -enddate — this tests the live certificate actually being served, not just the file on disk. Set WARN at 30 days and CRITICAL at 14 days to give ample renewal time. Use certbot renew --pre-hook and --post-hook for standalone mode (when nginx is stopped for renewal) or --deploy-hook for webroot mode (nginx stays running). Schedule renewal checks daily — Certbot only renews when expiry is within 30 days.