How to Add DNS, SSL, and Domain Checks to CI/CD Pipelines
Prevent DNS misconfigurations and certificate outages from reaching production. Practical pipeline examples for pre-deployment validation.
The Problem: Infrastructure Checks Happen Too Late
Most teams verify DNS records and SSL certificates manually, after deployment, or only when something breaks. A misconfigured CNAME in a Kubernetes ingress, an expired SSL certificate on a staging domain, or a lapsed domain registration all share the same root cause: infrastructure validation is disconnected from the deployment process.
Moving DNS record validation, SSL certificate checking, and WHOIS expiration monitoring into your CI/CD pipeline turns these checks into deployment gates. Configurations that would cause outages get caught before they reach production, not after users report the symptoms.
What to Validate and When
| Check Type | Pipeline Stage | Failure Action |
|---|---|---|
| DNS record resolution | Pre-deploy | Block deployment |
| SSL certificate validity | Pre-deploy + nightly cron | Block deploy / alert |
| Domain expiration status | Weekly cron | Alert only |
| DNS propagation verification | Post-deploy | Rollback trigger |
DNS Record Validation Before Deployment
The most common infrastructure deployment failures involve DNS: a CNAME pointing to the wrong load balancer, an MX record misconfigured after a mail provider migration, or a TXT record missing for domain verification. Checking DNS records programmatically before deployment catches these issues immediately.
Validation script
#!/bin/bash
# infra-check.sh — Run as a CI pipeline step
set -euo pipefail
API_KEY="${DOMAIN_API_KEY}"
DOMAIN="${DEPLOY_DOMAIN:-example.com}"
EXPECTED_IP="${EXPECTED_A_RECORD}"
API="https://api.ops.tools/v1"
echo "Checking DNS A record for ${DOMAIN}..."
DNS_RESPONSE=$(curl -sf "${API}/dns/lookup?domain=${DOMAIN}&recordType=A" \
-H "x-api-key: ${API_KEY}")
RESOLVED_IP=$(echo "${DNS_RESPONSE}" | jq -r '.records[0].value // empty')
if [ "${RESOLVED_IP}" != "${EXPECTED_IP}" ]; then
echo "FAIL: Expected ${EXPECTED_IP}, got ${RESOLVED_IP}"
exit 1
fi
echo "PASS: DNS A record resolves to ${RESOLVED_IP}"Tip: Store your API key in CI/CD secrets (GitHub Secrets, GitLab CI/CD Variables, or your vault). Never commit API keys to source control. Most CI platforms inject secrets as environment variables at runtime.
SSL Certificate Checks in the Pipeline
Deploying behind an expired or misconfigured certificate causes immediate browser warnings and service disruption. Certificate validation in CI/CD should check three things: whether the certificate is valid, how many days remain until expiration, and whether the chain of trust is complete.
Certificate validation script
#!/bin/bash
# ssl-check.sh — Validate certificate before deployment
set -euo pipefail
API_KEY="${DOMAIN_API_KEY}"
DOMAIN="${DEPLOY_DOMAIN:-example.com}"
MIN_DAYS=30
API="https://api.ops.tools/v1"
SSL_RESPONSE=$(curl -sf "${API}/ssl/check?domain=${DOMAIN}" \
-H "x-api-key: ${API_KEY}")
VALID=$(echo "${SSL_RESPONSE}" | jq -r '.valid')
DAYS_LEFT=$(echo "${SSL_RESPONSE}" | jq -r '.daysUntilExpiration')
PROTOCOL=$(echo "${SSL_RESPONSE}" | jq -r '.protocol')
if [ "${VALID}" != "true" ]; then
echo "FAIL: SSL certificate is invalid for ${DOMAIN}"
exit 1
fi
if [ "${DAYS_LEFT}" -lt "${MIN_DAYS}" ]; then
echo "WARN: Certificate expires in ${DAYS_LEFT} days (threshold: ${MIN_DAYS})"
exit 1
fi
echo "PASS: Valid ${PROTOCOL} certificate, ${DAYS_LEFT} days remaining"WHOIS Domain Expiration Monitoring
Domain expiration is a silent killer. Unlike SSL certificates, which browsers flag immediately, domain expiration causes complete loss of service with no user-facing warning. Adding WHOIS expiration checks to a scheduled pipeline catches this risk early.
Expiration check script
#!/bin/bash
# whois-check.sh — Verify domain registration is active
set -euo pipefail
API_KEY="${DOMAIN_API_KEY}"
DOMAIN="${DEPLOY_DOMAIN:-example.com}"
API="https://api.ops.tools/v1"
WHOIS_RESPONSE=$(curl -sf "${API}/whois/lookup?domain=${DOMAIN}" \
-H "x-api-key: ${API_KEY}")
EXPIRY=$(echo "${WHOIS_RESPONSE}" | jq -r '.expirationDate // .expiresDate // empty')
REGISTRAR=$(echo "${WHOIS_RESPONSE}" | jq -r '.registrar // "unknown"')
STATUS=$(echo "${WHOIS_RESPONSE}" | jq -r '.status // empty')
if [ -z "${EXPIRY}" ]; then
echo "WARN: No expiration date found for ${DOMAIN}"
exit 1
fi
DAYS_LEFT=$(( ($(date -d "${EXPIRY}" +%s) - $(date +%s)) / 86400 ))
echo "Domain: ${DOMAIN}"
echo "Registrar: ${REGISTRAR}"
echo "Status: ${STATUS}"
echo "Expires: ${EXPIRY} (${DAYS_LEFT} days remaining)"
if [ "${DAYS_LEFT}" -lt 30 ]; then
echo "WARN: Domain expires in ${DAYS_LEFT} days"
fiGitHub Actions: Full Pre-Deploy Pipeline
Combine all three checks into a single GitHub Actions workflow that runs before every deployment.
name: Infrastructure Pre-Deploy Checks
on:
deployment:
types: [created]
workflow_dispatch:
inputs:
domain:
description: "Domain to validate"
required: true
default: "example.com"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: DNS Record Validation
run: |
DNS=$(curl -sf "https://api.ops.tools/v1/dns/lookup?domain=${{ inputs.domain }}&recordType=A" -H "x-api-key: ${{ secrets.DOMAIN_API_KEY }}")
echo "DNS response: ${DNS}"
IP=$(echo "${DNS}" | jq -r '.records[0].value // empty')
[ -n "${IP}" ] || (echo "FAIL: No A record found" && exit 1)
echo "PASS: A record resolves to ${IP}"
- name: SSL Certificate Validation
run: |
SSL=$(curl -sf "https://api.ops.tools/v1/ssl/check?domain=${{ inputs.domain }}" -H "x-api-key: ${{ secrets.DOMAIN_API_KEY }}")
VALID=$(echo "${SSL}" | jq -r '.valid')
DAYS=$(echo "${SSL}" | jq -r '.daysUntilExpiration')
echo "SSL valid: ${VALID}, expires in ${DAYS}d"
[ "${VALID}" = "true" ] || (echo "FAIL: Invalid certificate" && exit 1)
[ "${DAYS}" -gt 30 ] || (echo "FAIL: Certificate expires soon" && exit 1)
- name: WHOIS Domain Expiration Check
run: |
WHOIS=$(curl -sf "https://api.ops.tools/v1/whois/lookup?domain=${{ inputs.domain }}" -H "x-api-key: ${{ secrets.DOMAIN_API_KEY }}")
EXPIRY=$(echo "${WHOIS}" | jq -r '.expirationDate // empty')
[ -n "${EXPIRY}" ] || (echo "FAIL: No WHOIS data" && exit 1)
echo "Domain expires: ${EXPIRY}"
echo "PASS: WHOIS data available and parseable"GitLab CI: Infrastructure Validation Stage
The same checks work in GitLab CI with environment variables for secrets and multi-stage pipeline configuration.
# .gitlab-ci.yml
stages:
- validate
- deploy
variables:
DOMAIN: "example.com"
infra-validate:
stage: validate
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
DNS=$(curl -sf "https://api.ops.tools/v1/dns/lookup?domain=${DOMAIN}&recordType=A" \
-H "x-api-key: ${DOMAIN_API_KEY}")
IP=$(echo "${DNS}" | jq -r '.records[0].value // empty')
[ -n "${IP}" ] && echo "DNS: OK (${IP})" || exit 1
- |
SSL=$(curl -sf "https://api.ops.tools/v1/ssl/check?domain=${DOMAIN}" \
-H "x-api-key: ${DOMAIN_API_KEY}")
VALID=$(echo "${SSL}" | jq -r '.valid')
[ "${VALID}" = "true" ] && echo "SSL: OK" || exit 1
- |
WHOIS=$(curl -sf "https://api.ops.tools/v1/whois/lookup?domain=${DOMAIN}" \
-H "x-api-key: ${DOMAIN_API_KEY}")
EXPIRY=$(echo "${WHOIS}" | jq -r '.expirationDate // empty')
[ -n "${EXPIRY}" ] && echo "WHOIS: OK" || exit 1
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHPost-Deploy: DNS Propagation Verification
After deployment, verify that DNS changes have propagated to multiple regions. A post-deploy check that queries from distributed locations catches regional caching issues that would not appear in single-region pre-deploy validation.
#!/bin/bash
# propagation-check.sh — Run after deployment
API_KEY="${DOMAIN_API_KEY}"
DOMAIN="${DEPLOY_DOMAIN}"
EXPECTED_IP="${EXPECTED_A_RECORD}"
PROP_RESPONSE=$(curl -sf \
"https://api.ops.tools/v1/dns/propagation?domain=${DOMAIN}&recordType=A" \
-H "x-api-key: ${API_KEY}")
MISMATCHES=$(echo "${PROP_RESPONSE}" | jq \
'[.locations[] | select(.value != "'${EXPECTED_IP}'")]')
MISMATCH_COUNT=$(echo "${MISMATCHES}" | jq 'length')
if [ "${MISMATCH_COUNT}" -gt 0 ]; then
echo "WARN: ${MISMATCH_COUNT} locations still resolving to old IP"
echo "${MISMATCHES}" | jq -r '.[] | "\(.location): \(.value)"'
# Optionally trigger rollback or wait for propagation
fi
echo "Propagation: ${MISMATCH_COUNT} of $(echo "${PROP_RESPONSE}" | jq '.locations | length') locations updated"Scheduled Monitoring: Beyond Deployment Gates
Pipeline gates only catch problems at deploy time. Certificate expiration and domain registration changes happen between deployments. A scheduled cron-based pipeline that runs daily or weekly provides continuous coverage.
Daily SSL scan
Schedule a pipeline that checks SSL validity for all production domains. Set alert thresholds at 30, 14, 7, and 1 day before expiration. Route alerts to your on-call channel. This catches certificates that expire between deployments.
Weekly WHOIS audit
Query WHOIS data for all managed domains weekly. Track expiration dates and flag domains approaching renewal within 60 days. This is especially important for teams managing large domain portfolios where individual expiration dates are easy to lose track of.
On-demand DNS audit
Trigger a full DNS record audit on demand via pipeline dispatch. After any DNS provider change, nameserver migration, or infrastructure switch, run the audit to verify all records resolve correctly across multiple regions before considering the change complete.
Common pitfalls
Hardcoding domains: Maintain a domain manifest file or environment variable that the pipeline reads. This avoids updating the pipeline script every time a domain is added or removed. Ignoring rate limits: When checking large domain portfolios, add delays between requests to stay within the API rate limit. Silent failures: Always use set -euo pipefail in bash scripts so that any failed API call fails the pipeline step.
For deeper detail on each API capability, see the DNS record query guide, SSL monitoring walkthrough, and domain expiration protection guide.
Frequently Asked Questions
Q: Why should DNS and SSL checks run in CI/CD pipelines?
Running these checks as deployment gates catches DNS misconfigurations, expired certificates, and domain registration risks before they affect production. Manual checks are inconsistent and usually happen too late.
Q: How do I call domain data APIs from GitHub Actions?
Store your API key in GitHub Secrets under the repository settings. Reference it in workflow steps usingsecrets.DOMAIN_API_KEY (using the $ syntax). Use curl to call the API endpoint, parse the JSON response with jq, and exit with a non-zero code to fail the step if validation does not pass.
Q: What infrastructure checks should run before deployment?
At minimum: verify DNS records resolve to expected values, confirm SSL certificates are valid with more than 30 days until expiration, and check WHOIS data shows active domain registration. Add DNS propagation verification as a post-deploy step for DNS changes.
Q: How often should SSL certificate checks run in CI/CD?
Run SSL validation on every deployment for domains affected by the release. Additionally, schedule a daily or nightly cron pipeline that checks all production domains. Set alert thresholds at 30, 14, 7, and 1 day before expiration.
Related Articles
DNS Lookup API: How to Check DNS Records Programmatically
Complete developer guide to querying DNS records via API. Includes working code examples in Python, Node.js, Go, and PHP with caching best practices.
Read ArticleIP Geolocation API: Complete Developer Guide
Build location-aware applications with IP geolocation data. Get country, city, ISP, timezone, and VPN detection from any IP address. Code examples included.
Read ArticleWHOIS Lookup API: Domain Registration Data at Scale
Query WHOIS data programmatically for domain intelligence. Get registrar info, expiration dates, and ownership data. Python, Node.js, Go, and PHP examples.
Read ArticleStart Automating Infrastructure Checks Today
Professional plans starting at $29/month. One API key for DNS, WHOIS, IP, and SSL validation.