深夜2時のコネクションタイムアウト
ホテルの部屋で、自宅のオフィスから3つのタイムゾーンを越えた場所に座り、点滅するカーソルを眺めていました。重大なバグを修正するために、開発サーバーから設定ファイルを取得する必要がありました。<a href="https://itnotes.dev/ja/ssh%e3%83%88%e3%83%b3%e3%83%8d%e3%83%aa%e3%83%b3%e3%82%b0%ef%bc%9a%e3%81%82%e3%82%89%e3%82%86%e3%82%8b%e3%83%8d%e3%83%83%e3%83%88%e3%83%af%e3%83%bc%e3%82%af%e7%b5%8c%e7%94%b1%e3%81%a7%e3%83%aa/">ssh dev.itfromzero.com</a>と入力してエンターを押し、待ちました。5秒、10秒。そして、恐れていたメッセージが表示されました:ssh: connect to host dev.itfromzero.com port 22: Connection timed out。
原因はすぐに分かりました。自宅のISPがまたパブリックIPアドレスを更新したのです。dev.itfromzero.comのDNSレコードは古い(無効な)アドレスを指したままで、サーバーは私の知らない新しいアドレスになっていました。自分の機材から締め出されてしまったのです。単に煩わしいだけでなく、リモートワークフローにおいて致命的な問題でした。
IPのメリーゴーランド:なぜ自宅の接続は切れるのか
家庭用ISPはこれ(IP変更)で有名です。彼らはアドレスプールを管理し、DHCP経由で割り当てます。多くの場合、24時間のリースサイクルです。リースが期限切れになるか、あるいはわずか10秒の停電でモデムが再起動すると、IPが変わります。Netflixを見ているだけの人には気づかれませんが、エンジニアにとっては、致命的な変更(破壊的変更)です。日常的にVPNやプライベートクラウドを運用しているなら、この変動は無視できません。
本当に厄介なのは、変更から更新までのタイムラグです。午前1時にIPが切り替わったのにDNSレコードが古いままなら、サーバーは実質的に「幽霊」のような存在になります。これを解決するには、変更を検知してDNSプロバイダーにほぼリアルタイムでレコードを更新させる方法が必要です。
DDNSの現状:なぜ既存の選択肢はイマイチなのか
自分でコードを書く前に、標準的なソリューションを検討しました。現状は以下の通りです:
- レガシープロバイダー (No-IP, DynDNS): 動作はしますが、無料枠はまるで「人質」のようです。ホスト名を維持するためだけに30日ごとにメールのリンクをクリックさせられたり、
myhome.ddns.netのようなサブドメインの使用を強制されたりします。 - ルーター内蔵DDNS: ほとんどの家庭用ルーターにはサポートが組み込まれていますが、特定の(有料)プロバイダーにハードコードされていることが多いです。さらに悪いことに、ログが全く残りません。失敗したとき、何が原因か推測するしかありません。
- Cloudflare API + Bash: これがエンジニアの選択です。すでにCloudflareを使用しているなら、強力なAPIを自由に利用できます。無料ですし、自分のドメインで動作し、思い通りに動作するようにスクリプトを組むことができます。
私はこのセットアップを自分の機材で2年以上運用していますが、一度も失敗したことはありません。商用サービスの制限を回避しつつ、更新ロジックやエラーレポートを完全にコントロールできます。
30行のソリューション:独自のクライアントを構築する
これを実行するには、Cloudflare APIトークン、数行のBash、精度を高めるためのDNSトラブルシューティングの知識、そしてスケジュール実行の手段という3つのものだけで十分です。Bashはこれに最適なツールです。あらゆるLinuxディストリビューションに標準搭載されており、非常に高速で、オーバーヘッドもありません。
ステップ1:権限の取得
安全第一:Global API Keyは使用しないでください。漏洩した場合、ハッカーにアカウント全体を削除される危険があります。代わりに、スコープを絞ったトークンを作成します:
- Cloudflareにログインし、マイプロフィール > APIトークンに移動します。
- ゾーンDNSを編集するテンプレートを使用してトークンを作成します。
- 更新したいドメインに限定してスコープを設定します。
- トークンをコピーし、パスワードマネージャーに保存します。
ドメインのダッシュボードの概要ページにあるゾーンIDも必要になります。
ステップ2:ロジックの核
ロジックは単純です。現在のパブリックIPを取得し、ローカルのキャッシュファイルと比較します。差異がある場合にのみ、Cloudflareにリクエストを送信します。以下がそのスクリプトです。何が起こっているか正確に把握できるよう、最小限の構成にしています。
#!/bin/bash
# 設定
AUTH_TOKEN="your_api_token_here"
ZONE_ID="your_zone_id_here"
RECORD_NAME="dev.itfromzero.com"
IP_FILE="/tmp/current_ip.txt"
# 信頼性の高い外部サービスを使用して現在のパブリックIPを取得
CURRENT_IP=$(curl -s https://api.ipify.org)
# 比較用にキャッシュされたIPがあるか確認
if [ -f "$IP_FILE" ]; then
OLD_IP=$(cat "$IP_FILE")
else
OLD_IP=""
fi
# IPが一致しない?更新実行。そうでなければ?終了。
if [ "$CURRENT_IP" = "$OLD_IP" ]; then
echo "IPは変更されていません ($CURRENT_IP)。更新をスキップします。"
exit 0
fi
echo "IPが $OLD_IP から $CURRENT_IP に変更されました。Cloudflareを更新中..."
# Cloudflareから一意のレコードIDを取得
RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" | jq -r '.result[0].id')
# 更新をプッシュ
UPDATE_RESULT=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$CURRENT_IP\",\"ttl\":1,\"proxied\":false}")
if [[ $UPDATE_RESULT == *"\"success\":true"* ]]; then
echo "更新に成功しました。"
echo "$CURRENT_IP" > "$IP_FILE"
else
echo "更新に失敗しました!トークンの権限を確認してください。"
exit 1
fi
プロのヒント:JSONレスポンスを解析するためにjq(sudo apt install jq)をインストールしておく必要があります。これはターミナルでAPIデータを扱うための標準的なツールです。
ステップ3:このスクリプトが「APIフレンドリー」である理由
まず、ローカルファイル(/tmp/current_ip.txt)を使用して最後に知られたIPをキャッシュします。これにより、変更がない場合に数分おきにCloudflareのサーバーに負荷をかけることを防げます。Cloudflareは5分間に1,200リクエストを許可していますが、行儀の良いAPI利用者であることは、エンジニアリングの優れた慣行です。
"proxied": falseを設定することも重要です。SSHやVPNに使用する場合、トラフィックを直接IPに届ける必要があります。Cloudflareの標準プロキシはWebトラフィック(HTTP/S)のみを処理するため、Spectrumサービスを有料で利用していない限り、プロキシはSSH接続をブロックしてしまいます。
設定したらあとはお任せ:自動化
スクリプトを手動で実行しなければならないなら、それは無意味です。5分ごとにチェックするシンプルなcronジョブを使用することもできます:
*/5 * * * * /home/user/scripts/cloudflare-ddns.sh >> /var/log/ddns.log 2>&1
しかし、私はSystemdタイマーを好みます。なぜなら、Systemdは依存関係を適切に処理し、journalctlに直接ログを記録し、ネットワークがオンラインになるのを待ってから実行してくれるからです。モデムがまだ再起動中でスクリプトが失敗した場合でも、Systemdはエクスポネンシャルバックオフ(指数関数的後退)を伴う再試行を行えます。
サービスファイル (/etc/systemd/system/cf-ddns.service)
[Unit]
Description=Cloudflare DDNS更新
After=network-online.target
[Service]
Type=oneshot
ExecStart=/home/user/scripts/cloudflare-ddns.sh
User=user
タイマーファイル (/etc/systemd/system/cf-ddns.timer)
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
systemctl enable --now cf-ddns.timerで有効化します。これで、システムのサービスマネージャーに直接統合されたプロフェッショナル級のDDNSクライアントが完成しました。
安定性と安心感
これを導入して以来、一度も締め出されたことはありません。しかし、ここで満足しないでください。スクリプトの価値はその監視体制で決まります。シンプルなヘルスチェックの追加を検討してください。更新に失敗した場合、スクリプトからTelegramボットやDiscordのウェブフックに通知を飛ばすようにします。深夜2時にSSH接続を試みて問題に気づくのではなく、事前に問題を知っておきたいはずです。
自分で構築することで、仲介者を排除し、DNS自動化についての理解を深めることができました。これは、ISPがIPアドレスで「椅子取りゲーム」を始めるたびに報われる、わずか15分のプロジェクトです。

