Ubuntu/Linuxで安全にDynamic DNSを運用する:Public IP・CGNAT・IPv6・systemd

Dynamic DNS(DDNS)が行うのは、address 変更後の DNS A/AAAA record 更新だけです。public address の割り当て、carrier-grade NAT(CGNAT)の通過、router/host firewall の開放、listen や認証のない service の安全化はできません。updater を選ぶ前に network path が到達可能だと証明します。DNS answer が正しくても接続は完全に失敗し得ます。

1. 経路全体の中で DDNS の位置を確認する

確認する質問DDNS で解決できるか
DNShostname は現在どの IPv4/IPv6 address を指すべきかできる:A/AAAA を更新する
Access networkrouter WAN が本当に public IPv4 を持つか、host に routed IPv6 があるかできない:address を割り当て・変更しない
NAT/CGNATInternet からの inbound connection が境界 device に届くかできない:NAT を通過しない
Routing/firewalltraffic が正しい host/port に明示的に forward/allow されるかできない:自動では開かない
Applicationservice が listen、暗号化、認証、更新され、最小範囲だけ公開されるかできない:application security を代替しない

目的を先に定義します。remote administration、home service、monitoring callback では risk 判断が異なります。DDNS が成功したという理由だけで SSH、database、admin panel、未認証 service を無条件に port forward しないでください。

2. Address・route・listener・既存 updater を読み取り専用で棚卸しする

通常ユーザーの shell で host の証拠を集めます。非特権の ss は一部 process name を表示しない場合があります。これは permission boundary と記録し、「service がない」証拠とはしません。

ip -brief address
ip route show default
ip -6 route show default
ss -lntup
resolvectl status
systemctl list-timers --all | grep -i ddns
systemctl list-unit-files | grep -i ddns

別途、router status page の WAN IPv4/IPv6 prefix、ISP 契約が inbound を許すか、zone の authoritative server、現在の A/AAAA/TTL、router/NAS/container/host updater の有無を記録します。screenshot や ticket に account、token、TSIG key、完全な config export、内部 address 表、device serial を公開しません。

3. Public IPv4、double NAT、CGNAT を区別する

RFC 1918 の 10.0.0.0/8172.16.0.0/12192.168.0.0/16 は private IPv4、RFC 6598 の 100.64.0.0/10 は shared address space です。router WAN がこれらの範囲にある、または trusted external HTTPS address check と異なる場合、中間に少なくとも一つ NAT があります。ISP CGNAT とは限らず、自分が管理する上位 router による double NAT の場合もあります。

external address check は service に source address と query time を知らせます。trusted router/ISP interface を優先します。外部 service が必要なら、1 address だけを返し、query parameter token が不要な承認済み HTTPS endpoint を選びます。次は IANA reserved .invalid を使うため意図的に実行不能です。review 済み endpoint に置換してください。

PublicCheckUrl="https://ip-check.example.invalid/address"
case "$PublicCheckUrl" in
  https://*) ;;
  *) echo "Stop: public IP discovery must use HTTPS"; exit 1 ;;
esac
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --max-time 15 "$PublicCheckUrl"

検索結果の「IP 表示」HTML を正規表現で取得したり、plaintext HTTP で問い合わせたりしません。page 変更、proxy、redirect、複数 address response が誤りを生み、query 自体にも privacy cost があります。

4. IPv6 は「NAT がないから到達できる」を意味しない

RFC 4193 の fc00::/7 は IPv6 Unique Local address であり、global Internet route の対象ではありません。global IPv6 でも ISP prefix rotation、temporary privacy address、router inbound policy、host firewall の影響を受けます。AAAA は公開を承認された意図的で安定した address を指すべきで、ip address の最初の IPv6 を選ぶものではありません。

AAAAA は別 owner として管理します。IPv6 service が動かないなら stale AAAA を残しません。client が先に試す可能性があります。host が temporary address を使う場合、current distribution/network-manager/ISP 文書に従い inbound service 用の stable address/prefix design を選びます。architecture 問題を隠すため privacy behavior を無効化しません。

5. Router updater か host updater か:owner は一つだけ

状況推奨 owner理由
Router が dynamic public IPv4 を直接持つ保守中の router native DDNSWAN 変更を最初に知り、LAN host に依存しない
Server が routed address を直接持つその server の単一 managed updateraddress owner と record owner が一致
DNS provider が専用 HTTPS API のみ提供managed router/plugin または制約した host serviceprovider の current API に従う必要がある
Authoritative DNS が RFC 2136 対応制限 TSIG key を使う nsupdate標準で監査でき、record 単位に制限可能
CGNAT で inbound IPv6 もないreverse tunnel/managed VPN/relayDDNS は inbound path を作らない

router、host、NAS、container が同じ record を同時に更新してはいけません。異なる address や timing を観測し、record が往復します。各 FQDN、record type、updater について owner、trigger、credential、rollback 担当、停止方法を一つずつ記録します。

6. Credential と update protocol の境界

保守中の device native integration、provider 公式の制限 client/API、または authoritative operator が明示対応する RFC 2136 の順に検討します。すべての provider が nsupdate を受け入れるとは仮定しません。HTTPS API の URL、method、authentication header、rate limit、response semantics は current provider 公式文書から取得します。token を URL、command line、unit Environment=、log に入れません。

RFC 2136 は DNS UPDATE、RFC 8945 は現在の TSIG 仕様です。BIND は nsupdate -y に secret を入れないよう明記しています。argv と shell history から漏れるためです。-k で key file を読み、DNS administrator に key 権限を zone 全体ではなく一つの name と A/AAAA type に制限してもらいます。global API key しかないなら停止し、より狭い権限または隔離 account を求めます。

updater ごとに別 credential を使い、file は root だけが読めるようにし、issue time、scope、rotation、revoke 手順を記録します。secret を example、Git、chat、diagnostic log、systemctl status に貼りません。

7. 再現可能な RFC 2136 IPv4 updater 例

この provider-neutral template は、authoritative service が RFC 2136 を明示対応し、name にこの updater だけが所有する一つの A RRset があり、least-privilege TSIG key が発行済みの場合だけ使えます。すべての .invalid は意図的に動かないため置換必須です。temporary IPv6 を誤って公開しないよう AAAA は自動化しません。

以下を local working copy ddns-update として保存し、install 前に review します。非 HTTPS discovery を拒否し、単一 global IPv4 を検証し、authoritative answer に新 address がない場合だけ更新し、temporary file 経由で command を nsupdate -k に渡します。secret は argv に入りません。

#!/bin/sh
set -eu

: "${DNS_SERVER:?}"
: "${DNS_ZONE:?}"
: "${DNS_NAME:?}"
: "${IP_DISCOVERY_URL:?}"
: "${CREDENTIALS_DIRECTORY:?}"

case "$IP_DISCOVERY_URL" in
  https://*'?'*|https://*'#'*|https://*'@'*) echo "IP discovery URL must not contain credentials or a query" >&2; exit 1 ;;
  https://*) ;;
  *) echo "IP discovery URL must use HTTPS" >&2; exit 1 ;;
esac

case "$DNS_SERVER$DNS_ZONE$DNS_NAME" in
  *[!A-Za-z0-9._-]*) echo "DNS values contain unsupported characters" >&2; exit 1 ;;
esac

case "$DNS_ZONE:$DNS_NAME" in
  *.:*.) ;;
  *) echo "DNS zone and name must be absolute" >&2; exit 1 ;;
esac

test -r "$CREDENTIALS_DIRECTORY/tsig.key" || { echo "TSIG credential is not readable" >&2; exit 1; }

PublicIPv4="$(curl --proto '=https' --tlsv1.2 --fail --silent --show-error --max-time 15 "$IP_DISCOVERY_URL")"
if ! python3 - "$PublicIPv4" <<'PY'
import ipaddress
import sys
try:
    value = ipaddress.ip_address(sys.argv[1])
except ValueError:
    raise SystemExit(1)
raise SystemExit(0 if value.version == 4 and value.is_global else 1)
PY
then
  echo "IP discovery did not return one global IPv4 address" >&2
  exit 1
fi

if dig @"$DNS_SERVER" "$DNS_NAME" A +short | grep -Fxq "$PublicIPv4"; then
  echo "DDNS record already current"
  exit 0
fi

umask 077
UpdateFile="$(mktemp)"
trap 'rm -f "$UpdateFile"' EXIT HUP INT TERM
cat >"$UpdateFile" <<EOF
server $DNS_SERVER
zone $DNS_ZONE
update delete $DNS_NAME A
update add $DNS_NAME 300 A $PublicIPv4
send
EOF

nsupdate -k "$CREDENTIALS_DIRECTORY/tsig.key" "$UpdateFile"
echo "DDNS record updated"

300 は administrator が選ぶ example TTL であり速度保証ではありません。provider/authoritative server の minimum TTL と update frequency を確認します。multi-value、load balance、health check、CDN、DNSSEC automation、または他 controller と共同所有する RRset にこの script を使いません。

8. File を install するが起動しない

まず curlpython3dignsupdate の実 path/version を確認します。Ubuntu package name は release により変わるため、random script ではなく current Ubuntu package index/apt-cache で確認します。DNS administrator は nsupdate -k compatible key file を安全な channel で渡します。secret を terminal に手入力しません。

command -v curl python3 dig nsupdate
curl --version
python3 --version
dig -v
nsupdate -V
sudo install -d -m 0750 -o root -g root /etc/ddns
sudo install -m 0600 -o root -g root PATH_TO_ISSUED_KEY_FILE /etc/ddns/tsig.key
sudo install -m 0755 -o root -g root ddns-update /usr/local/libexec/ddns-update

PATH_TO_ISSUED_KEY_FILE は置換必須の local path であり key content ではありません。install 後に owner/mode を確認し、/etc/ddns を Git、public object storage、複数 machine にコピーしません。

9. systemd oneshot service

local working copy ddns-update.service として保存します。四つの .invalid を review 済み設定に置換し、IP endpoint の query string に credential を入れません。LoadCredential= は service 起動時に key を提供し、DynamicUser= と hardening は継続 identity と filesystem view を制限します。

[Unit]
Description=Update one dynamic DNS A record
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
DynamicUser=yes
Environment=DNS_SERVER=nsupdate.example.invalid
Environment=DNS_ZONE=example.invalid.
Environment=DNS_NAME=home.example.invalid.
Environment=IP_DISCOVERY_URL=https://ip-check.example.invalid/address
LoadCredential=tsig.key:/etc/ddns/tsig.key
ExecStart=/usr/local/libexec/ddns-update
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
LockPersonality=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

これは generic provider API unit ではありません。HTTPS update API には provider 公式 client または systemd credential を読む別設計 wrapper を使います。bearer token をこの TSIG file のように扱いません。

10. systemd timer と単一 owner の頻度

ddns-update.timer として保存します。boot delay、前回実行後の interval、jitter を設け、全 machine が同時に request しないようにします。実 interval は provider rate limit と address change 特性に従います。

[Unit]
Description=Schedule dynamic DNS updates

[Timer]
OnBootSec=5min
OnUnitInactiveSec=15min
RandomizedDelaySec=90s
AccuracySec=30s
Unit=ddns-update.service

[Install]
WantedBy=timers.target

timer は DHCP/network event の代替ではなく、変更が正確に何秒後に見えるか保証しません。短い polling は provider load と privacy log を増やし、長い polling は stale-address window を広げます。選択理由を記録します。

11. 静的検証後に一度だけ実行する

.invalid が残らず、scope が正しく、key を revoke できることを確認します。install 前に verify し、timer を install しても直ちに enable しません。

if grep -R -nF '.invalid' ddns-update ddns-update.service ddns-update.timer; then
  echo "Stop: replace and review every .invalid placeholder"
  exit 1
fi
systemd-analyze verify ./ddns-update.service ./ddns-update.timer
sudo install -m 0644 -o root -g root ddns-update.service /etc/systemd/system/ddns-update.service
sudo install -m 0644 -o root -g root ddns-update.timer /etc/systemd/system/ddns-update.timer
sudo systemctl daemon-reload
sudo systemctl start ddns-update.service
systemctl status ddns-update.service --no-pager
journalctl -u ddns-update.service --since today --no-pager

grep に output があれば placeholder が残っているため停止します。output がない場合だけ続けます。初回実行は maintenance window で行い、provider console access、旧 RRset/TTL、第 2 management path、key revoke 権限を保持します。production の唯一の入口で実験しません。

12. Authoritative・recursive・外部 path から検証する

大文字 placeholder を review 済み値に置換します。authoritative query は zone 更新を証明し、recursive query は一つの resolver の current cache を示します。同じ証拠ではありません。

dig @AUTHORITATIVE_SERVER HOSTNAME A +noall +answer
dig @RECURSIVE_RESOLVER HOSTNAME A +noall +answer
dig @AUTHORITATIVE_SERVER HOSTNAME AAAA +noall +answer
getent ahosts HOSTNAME
systemctl list-timers ddns-update.timer --all

続いて authorized external network から application TLS、authentication、予定 port だけを確認します。同じ LAN の hairpin NAT test は public reachability を証明しません。public “port scanner” site で sensitive service を調べたり、authentication failure、全 hostname inventory、address history を公開したりしません。

これらの確認、external test、change approval がすべて通った後だけ timer を有効にします。

sudo systemctl enable --now ddns-update.timer
systemctl list-timers ddns-update.timer --all

13. TTL、cache、update result

RFC 1035 は TTL を RR が cache される時間の上限とし、RFC 2308 は negative caching も定義します。authoritative record の更新は、世界の resolver が保持する positive/NXDOMAIN cache を消しません。client、browser、OS、application にも追加 cache behavior があり得ます。

「更新後すぐ反映」を約束しません。安全かつ管理可能なら旧 endpoint を設計した overlap interval だけ保持し、authoritative answer と複数の controlled recursive resolver を別々に調べ、旧 TTL/negative TTL から待機を判断します。name の削除と再作成を繰り返すと新しい negative cache を生みます。DNS を連続変更して故障を直そうとしません。

14. NAT・firewall・application security は別の変更

DNS が正しくても、境界が対象 address を持つか、最小で明示的な rule が正しい host に forward/allow するか、host firewall が予定 IPv4/IPv6 path に合うか、service が予定 interface/port だけで listen するか、TLS、strong authentication、update、rate limit、audit が準備済みかを個別に確認します。

Ubuntu は ufw を host firewall frontend と説明しますが、この guide だけを理由に ufw allow を実行しません。sudo ufw status verbose、administrator review 済み read-only nftables/router view、ss で先に証拠を集め、別の承認済み変更で正確な source/protocol/port を開きます。database、container socket、router UI、未認証 dashboard を Internet に直接公開しません。

15. Log・monitoring・privacy

  • time、success/failure class、必要な record name 部分、provider request ID だけを記録する。secret、authentication header、完全 response body は通常不要です。
  • public DNS は name/address を公開し、address history は online time と network move を示し得ます。人名、住所、device type を含まない name を選ぶ。
  • curl -vnsupdate -d/-D、shell tracing を常時有効にしない。network/authentication metadata が増えます。
  • 連続 failure、authentication rejection、address の急速な往復、owner conflict に alert し、rate limit 回避の backoff を設ける。
  • credential rotation/revoke 後は一度だけ verification update する。旧 key を image、snapshot、log attachment に残さない。

monitoring は discovery failure、rejected update、authoritative update 済み、recursive cache stale、network path unreachable、application authentication failure を区別します。DNS resolve だけの監視は最重要の故障層を見落とします。

16. Rollback と故障マトリクス

症状想定層安全な対応
updater が private/shared IPv4 を観測double NAT/CGNATA update を止め、ISP/上位 router を確認
authoritative A は正しいが外部接続不可NAT/firewall/serviceDNS 変更を止め、path/listener を調べる
authoritative は新、recursive は旧TTL/cache公開済み TTL を待ち、controlled resolver を query
A は動くが AAAA は timeoutIPv6 route/firewall/stale AAAAAAAA owner を停止し change control で修復/復元
record が二 address 間を往復複数 updater owner全 automation を停止し、一 owner 選定後に復元
NOTAUTH/REFUSED/認証失敗zone/server/key scopekey を拡大せず DNS administrator に渡す
log に secret が出たcredential incidentservice 停止、key revoke、log 制限、露出評価

まず timer を停止して証拠を保持し、provider control plane から記録済み旧 RRset を復元します。不明な状態で zone 削除や DNS account 全体の reset をしません。

sudo systemctl disable --now ddns-update.timer
sudo systemctl stop ddns-update.service
systemctl is-enabled ddns-update.timer
systemctl is-active ddns-update.service
journalctl -u ddns-update.service --since today --no-pager

旧 record、TTL、external connection、single owner が戻った後で unit/key の保持/削除を決めます。key 露出の可能性があれば DNS 側で先に revoke し、新規発行します。再利用しません。

17. Reverse tunnel・VPN・relay を選ぶ場合

IPv4 が CGNAT 後方、inbound IPv6 なし、ISP が inbound block、edge firewall を安全に管理できない、または service を public にすべきでない場合、DDNS は正しい connectivity solution ではありません。内側から controlled endpoint に outbound connection を作る reverse tunnel、overlay VPN、authenticated relay を選び、trust、encryption、identity、authorization、logging、availability、cost、exit migration を別途評価します。

VPN/tunnel も自動的に安全ではありません。control-plane account、device key、ACL、DNS leak、relay jurisdiction、failure rollback が必要です。未監査の public port forward を同時に残しません。remote administration だけなら、Internet 全体に admin port を開くより明示 identity/device に制限します。

すでに relay とご自身の computer があり、network rule を変更する前に topology、listener 境界、recovery plan を第三者に確認してほしい場合は、固定 250 米ドルの LazyRemote Network Fit Review を提供しています。最初は password、private key、未編集 config を含まない metadata だけで構いません。deployment と hardware は対象外です。

18. 現行 protocol と公式資料

資料確認日は 2026-09-01 です。DNS provider API、router firmware、Ubuntu package、ISP policy は変化します。installed version と current provider/ISP 公式文書に従って実装してください。

19. 2014 年原文アーカイブ(来歴確認専用)

以下の outer fence は source_export の完全な可視本文を byte 単位で保持します。末尾空白の正規化も私的値の編集もしていません。plaintext HTTP、credential-like placeholder、obsolete provider endpoint、cron からの直接実行、脆弱な HTML/regex IP scraping を含みます。これは現行手順ではなく不活性な歴史資料です。これらの URL を実行、コピー、復元しないでください。

~~~~markdown

3322的更新还有更加简单的办法,连客户端都不用安装,也不用配置文件,不用知道IP。

www.3322.org网站上介绍的是用lynx(一般的Linux,BSD都自带),就可以了!
使用方法:

lynx -mime_header -auth=用户名:密码 "http://members.3322.net/dyndns/update?system=dyndns&hostname=域名"

而Ubuntu/Debian默认带的是w3m,所以要将命令改成:

w3m -no-cookie -dump http://username:password@members.3322.net/dyndns/update?system=dyndns&hostname=your_domain.f3322.org

把这条命令放在计划任务crontab(编辑用户的Crontab文件: crontab -e ,用户所建立的Crontab文件存于/var/spool/cron中,其文件名与用户名一致。 )里15分钟执行一次就能定时更新了。

*/15 * * * * w3m -no-cookie -dump 'http://username:password@members.3322.net/dyndns/update?system=dyndns&hostname=your_domain.f3322.org'>/dev/null

顺便,使用w3m查询自己公网IP的方法:

w3m -no-cookie -dump www.ip138.com|grep -o "[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}"

~~~~

Leave a Reply