GitHub Actions で DNS・SSL・メール認証を定期チェックする CI ワークフローの作り方
DNS レコード、SSL 証明書の到達性、メール認証(SPF・DKIM・DMARC)の設定状態は、デプロイや移行のたびに壊れるリスクがあります。この記事では、dig や openssl を使った CI でのチェック方法と、その制約を補う HTTPS 経由の API 呼び出しを組み合わせて、GitHub Actions で定期的に検証するワークフローの作り方を解説します。
手動チェックの限界と CI 化の動機
DNS レコードの確認は dig、SSL 接続の確認は openssl s_client が標準的な手段です。手元のターミナルからであれば、dig A example.com +short で A レコードを、openssl s_client -connect example.com:443 で SSL 接続をすぐに確認できます。
開発チームで複数のドメインを運用していると、デプロイ後やインフラ変更後に「DNS が正しいか」「HTTPS で到達できるか」「メール認証が欠落していないか」を毎回手動で確認する作業が発生します。ドメインが 5 個以上になると、毎回すべてに対してコマンドを打つのは現実的ではありません。確認の抜け漏れが起き、SSL の到達不能やメール認証の欠落に気づくのが遅れるケースが出てきます。
GitHub Actions の schedule トリガーと workflow_dispatch を組み合わせれば、定期チェックと手動実行の両方に対応できます。以下のセクションでは、DNS・SSL・メール認証のそれぞれについて、dig/openssl を使ったチェック方法を先に紹介し、CI 環境での制約がある場合の代替手段として HTTPS 経由の API を併記します。
DNS レコードのチェック
DNS レコードの確認は dig が最も手軽です。Google Public DNS(8.8.8.8)を指定すれば、ローカルのキャッシュリゾルバーを経由せずに外部の値を取得できます。
result=$(dig +short A example.com @8.8.8.8)
if [ -z "$result" ]; then
echo "::error::A record not found for example.com"
exit 1
fi
echo "A record: $result"
MX や TXT も同様に確認できます。
dig +short MX example.com @8.8.8.8
dig +short TXT example.com @8.8.8.8
CI 環境での制約
この方法は CI 環境から 8.8.8.8 への UDP 53 が通る前提です。GitHub Actions のランナーではファイアウォールで DNS の外部問い合わせがブロックされていることがあり、dig がタイムアウトで失敗する場合があります。
UDP 53 がブロックされている環境では、HTTPS 経由で DNS を確認する方法が確実です。Labee Dev Toolbox の DNS API は認証不要で、curl だけで動作します。
result=$(curl -fsS "https://labee.dev/api/dns?domain=example.com&type=A")
count=$(echo "$result" | jq '.data.records.A | length')
if [ "$count" -eq 0 ]; then
echo "::error::A record not found for example.com"
exit 1
fi
echo "A record count: $count"
{
"success": true,
"data": {
"domain": "example.com",
"records": {
"A": [
{ "address": "203.0.113.1", "ttl": 3600 }
]
}
},
"error": null,
"meta": { "responseTime": 45 }
}
data.records はレコード種別ごとの配列です。レコードが存在しない種別は null で返ります。type パラメーターを省略すると A, AAAA, MX, TXT, NS, CNAME, SOA, CAA の 8 種類を一括取得できます。API キーは不要なので、Secrets の設定なしで動きます。
SSL 到達性のチェック
openssl s_client を使えば、SSL/TLS 接続の確認と証明書の詳細を一度に取得できます。
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | head -5
接続が成功すれば証明書チェーンの情報が出力されます。CI のステップで使う場合は、終了コードで判定します。
if echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -q "Verify return code: 0"; then
echo "SSL connection verified"
else
echo "::error::SSL verification failed for example.com"
exit 1
fi
証明書の有効期限も取得できます。
expiry=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_left" -lt 30 ]; then
echo "::warning::SSL certificate expires in $days_left days"
fi
CI 環境での制約
openssl s_client はタイムアウトの設定やスクリプトでの終了コード判定が煩雑です。-servername の指定漏れで SNI が効かなかったり、2>/dev/null を忘れて標準エラーがログに混ざるなど、CI スクリプトとしての保守性に課題があります。
「HTTPS で到達できるか」だけを確認したい場合は、Labee Dev Toolbox の SSL API が簡潔です。
result=$(curl -fsS "https://labee.dev/api/ssl-cert?hostname=example.com")
reachable=$(echo "$result" | jq -r '.data.reachable')
status=$(echo "$result" | jq '.data.status')
if [ "$reachable" != "true" ]; then
echo "::error::HTTPS unreachable for example.com"
exit 1
fi
echo "HTTPS reachable, status: $status"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 120 }
}
data.reachable が true なら HTTPS 接続が成功しています。接続に失敗した場合は reachable が false になり、error フィールドにエラー内容が入ります。この API は到達性の確認に特化しているため、証明書の有効期限を監視したい場合は前述の openssl による確認を併用してください。
メール認証のチェック
SPF、DKIM、DMARC はそれぞれ dig で確認できます。
# SPF
dig +short TXT example.com @8.8.8.8 | grep "v=spf1"
# DMARC
dig +short TXT _dmarc.example.com @8.8.8.8
# DKIM(セレクターを指定)
dig +short TXT google._domainkey.example.com @8.8.8.8
CI のステップでは、出力が空かどうかで判定します。
spf=$(dig +short TXT example.com @8.8.8.8 | grep "v=spf1")
if [ -z "$spf" ]; then
echo "::error::SPF record not found"
exit 1
fi
dmarc=$(dig +short TXT _dmarc.example.com @8.8.8.8)
if [ -z "$dmarc" ]; then
echo "::error::DMARC record not found"
exit 1
fi
echo "SPF: found, DMARC: found"
CI 環境での制約
DNS チェックと同様に、UDP 53 がブロックされている環境では dig が使えません。DKIM は使用しているセレクター名を事前に把握しておく必要があり、セレクターが不明な場合は候補を総当たりで試すスクリプトが必要になります。
Labee Dev Toolbox のメール認証 API を使うと、SPF・DKIM・DMARC・BIMI を 1 回のリクエストで確認できます。DKIM はセレクターを省略すると google, selector1, default など 12 種類のセレクターを自動で試行します。
result=$(curl -fsS "https://labee.dev/api/mail-auth?domain=example.com")
spf=$(echo "$result" | jq -r '.data.spf.exists')
dmarc=$(echo "$result" | jq -r '.data.dmarc.exists')
if [ "$spf" != "true" ]; then
echo "::error::SPF record not found"
exit 1
fi
if [ "$dmarc" != "true" ]; then
echo "::error::DMARC record not found"
exit 1
fi
echo "SPF: $spf, DMARC: $dmarc"
{
"success": true,
"data": {
"spf": {
"record": "v=spf1 include:_spf.google.com ~all",
"exists": true
},
"dkim": {
"mode": "auto",
"record": "v=DKIM1; k=rsa; p=...",
"exists": true,
"selector": "google",
"matches": [
{ "selector": "google", "record": "v=DKIM1; k=rsa; p=...", "exists": true }
],
"checkedSelectors": ["google", "selector1", "selector2", "s1", "s2", "k1", "k2", "k3", "default", "dkim", "mail", "smtp"],
"exhaustive": false
},
"dmarc": {
"record": "v=DMARC1; p=reject; rua=mailto:dmarc@example.com",
"exists": true
},
"bimi": {
"record": null,
"exists": false
}
},
"error": null,
"meta": { "responseTime": 200 }
}
dig と openssl で組むワークフロー
CI 環境から UDP 53 が通る場合は、dig と openssl だけでワークフローを組めます。
name: Domain Health Check (dig/openssl)
on:
schedule:
- cron: '0 9 * * 1' # 毎週月曜 09:00 UTC
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: DNS A record
run: |
result=$(dig +short A example.com @8.8.8.8)
if [ -z "$result" ]; then
echo "::error::A record not found"
exit 1
fi
echo "A record: $result"
- name: SSL connection
run: |
if echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -q "Verify return code: 0"; then
echo "SSL OK"
else
echo "::error::SSL verification failed"
exit 1
fi
- name: SSL expiry
run: |
expiry=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_left" -lt 30 ]; then
echo "::warning::SSL certificate expires in $days_left days"
fi
- name: SPF record
run: |
spf=$(dig +short TXT example.com @8.8.8.8 | grep "v=spf1")
if [ -z "$spf" ]; then
echo "::error::SPF record not found"
exit 1
fi
- name: DMARC record
run: |
dmarc=$(dig +short TXT _dmarc.example.com @8.8.8.8)
if [ -z "$dmarc" ]; then
echo "::error::DMARC record not found"
exit 1
fi
workflow_dispatch を追加しておくと、Actions のタブから手動実行もできます。schedule の cron は UTC で解釈されるため、日本時間の月曜 18:00 に実行したい場合は 0 9 * * 1 を指定します。
この方法は CI 環境から 8.8.8.8 への UDP 53 が通る前提です。GitHub Actions の ubuntu-latest ランナーでは通常動作しますが、セルフホストランナーやファイアウォールで制限されている環境では dig がタイムアウトで失敗します。
HTTPS 経由の API で組むワークフロー
UDP 53 がブロックされている環境や、dig のインストールが保証されない環境では、HTTPS 経由の API を使う方法もあります。Labee Dev Toolbox の API は認証不要で、curl だけで動作します。
name: Domain Health Check (API)
on:
schedule:
- cron: '0 9 * * 1'
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: DNS A record
run: |
result=$(curl -fsS "https://labee.dev/api/dns?domain=example.com&type=A")
count=$(echo "$result" | jq '.data.records.A | length')
if [ "$count" -eq 0 ]; then
echo "::error::A record not found"
exit 1
fi
- name: SSL reachability
run: |
result=$(curl -fsS "https://labee.dev/api/ssl-cert?hostname=example.com")
reachable=$(echo "$result" | jq -r '.data.reachable')
if [ "$reachable" != "true" ]; then
echo "::error::HTTPS unreachable"
exit 1
fi
- name: Mail authentication
run: |
result=$(curl -fsS "https://labee.dev/api/mail-auth?domain=example.com")
spf=$(echo "$result" | jq -r '.data.spf.exists')
dmarc=$(echo "$result" | jq -r '.data.dmarc.exists')
if [ "$spf" != "true" ] || [ "$dmarc" != "true" ]; then
echo "::error::Mail auth incomplete: SPF=$spf DMARC=$dmarc"
exit 1
fi
API 版では SSL の到達性のみを確認します。証明書の有効期限を監視したい場合は、dig/openssl 版の SSL expiry ステップを追加してください。
複数ドメインへの対応
管理するドメインが複数ある場合は、matrix strategy でドメインの一覧を定義します。上記の dig/openssl 版・API 版のどちらのステップでも matrix と組み合わせられます。
jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
domain:
- example.com
- example.jp
- api.example.com
fail-fast: false
steps:
- name: DNS check
run: |
result=$(dig +short A ${{ matrix.domain }} @8.8.8.8)
if [ -z "$result" ]; then
echo "::error::A record not found: ${{ matrix.domain }}"
exit 1
fi
- name: SSL check
run: |
if echo | openssl s_client -connect ${{ matrix.domain }}:443 -servername ${{ matrix.domain }} 2>/dev/null | grep -q "Verify return code: 0"; then
echo "SSL OK: ${{ matrix.domain }}"
else
echo "::error::SSL failed: ${{ matrix.domain }}"
exit 1
fi
- name: Mail auth
run: |
spf=$(dig +short TXT ${{ matrix.domain }} @8.8.8.8 | grep "v=spf1")
dmarc=$(dig +short TXT _dmarc.${{ matrix.domain }} @8.8.8.8)
if [ -z "$spf" ] || [ -z "$dmarc" ]; then
echo "::error::Mail auth incomplete: ${{ matrix.domain }}"
exit 1
fi
fail-fast: false を設定すると、1 つのドメインが失敗しても残りのドメインのチェックは続行されます。3 ドメインのうち 2 つに問題がある場合でも、1 回の実行で全体像を把握できます。
matrix の配列に新しいドメインを追加するだけでチェック対象が増えるため、ドメインが増えても管理コストはほとんど変わりません。UDP 53 がブロックされている環境では、前述の API 版ワークフローのステップに置き換えてください。
デプロイ後に自動でチェックを実行する
定期チェックに加えて、デプロイ直後にもチェックを走らせておくと、設定ミスの検知が速くなります。workflow_run トリガーを使うと、別のワークフローの完了を起点にできます。
name: Post-Deploy Check
on:
workflow_run:
workflows: ["Deploy"]
types: [completed]
jobs:
verify:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Wait for propagation
run: sleep 60
- name: DNS check
run: |
result=$(dig +short A example.com @8.8.8.8)
if [ -z "$result" ]; then
echo "::error::A record not found after deploy"
exit 1
fi
- name: SSL check
run: |
if echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -q "Verify return code: 0"; then
echo "SSL OK after deploy"
else
echo "::error::SSL failed after deploy"
exit 1
fi
workflows にはトリガー元のワークフロー名を配列で指定します。types: [completed] は成功・失敗に関わらず完了時に発火するため、if 条件で conclusion == 'success' に絞っています。
DNS 変更を伴うデプロイの場合、変更直後はまだキャッシュが残っていることがあります。sleep 60 で 1 分待つのは簡易的な対策です。TTL を 300 秒(5 分) に事前に下げておけば、sleep 300 で確実にキャッシュが入れ替わった状態を確認できます。
Slack 通知の追加
チェックが失敗したとき、Slack に通知を送ることで対応を速められます。Incoming Webhook の URL をリポジトリの Secrets に SLACK_WEBHOOK_URL として登録し、失敗時のステップで呼び出します。
- name: Notify on failure
if: failure()
run: |
curl -fsS -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Domain check failed: ${{ matrix.domain }}\\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}"
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() は、同じジョブ内の先行ステップが 1 つでも失敗した場合に実行されます。matrix strategy と組み合わせると、失敗したドメインごとに通知が飛びます。
Webhook の URL は平文でワークフローファイルに書かず、Secrets 経由で渡します。$SLACK_WEBHOOK は環境変数として展開されるため、Actions のログにも表示されません。
ワークフロー設計の注意点
レート制限
Labee Dev Toolbox の API にはレート制限があります。matrix で 10 ドメイン以上を一括チェックする場合は、ステップ間に sleep 1 を挟むと安全です。
cron の最短間隔
GitHub Actions の schedule トリガーは 5 分間隔が最短です。実行が混雑時には数分から数十分遅延することがあります。正確なタイミングが必要な場合は、外部のスケジューラーから workflow_dispatch を呼び出す構成を検討してください。
SSL API の確認範囲
SSL API は HTTPS 接続の到達性を確認するエンドポイントです。証明書の有効期限や発行者の詳細は返しません。証明書の有効期限を監視したい場合は、「SSL 到達性のチェック」セクションで紹介した openssl s_client によるチェックを併用してください。SSL 証明書の自動更新が失敗するパターンや対策は「SSL 証明書の期限切れを防ぐために知っておくべきこと」で詳しく解説しています。
メール認証の 3 要素(SPF・DKIM・DMARC)を dig で個別に確認する手順や、Gmail のバルクセンダー要件との照合方法は「SPF・DKIM・DMARC を外部から確認する方法」にまとめています。
手元のドメインでまずは 1 つワークフローを動かしてみてください。example.com を自分のドメインに置き換えれば、そのまま使えます。ブラウザーから確認する場合は Labee Dev Toolbox の DNS チェック画面、SSL 到達確認画面、メール認証チェック画面で API が返す値を事前に確認できます。