Route 53 のレコードは、aws route53 change-resource-record-sets に「変更内容を書いた JSON(change-batch)」を渡して作成・更新・削除します。 レコードの種類ごとにコマンドが分かれているわけではなく、JSON の Action(CREATE / UPSERT / DELETE)と ResourceRecordSet の中身を書き分けるのがポイントです。

この記事では、ホストゾーンの作成から、A・CNAME・TXT・MX・エイリアスの各レコードをコピーして使える JSON と実行出力つきで並べます。CREATE と UPSERT の違い、DELETE を一度で通す書き方、InvalidChangeBatch の読み方、--query での絞り込み、エイリアス先ごとの HostedZoneId の調べ方までを 1 ページにまとめました。

確認環境:Ubuntu 24.04、AWS CLI 1.46.1、Python 3.11。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しました。掲載するコマンドは、ローカル向けの接続先指定を外して AWS でそのまま使える形にしています。ホストゾーン ID や変更 ID は作成のたびに変わります。

Route 53 レコード操作の早見表

やりたいことと、使うコマンド・Action の対応は次のとおりです。

やりたいこと コマンド ポイント
ホストゾーンを作る aws route53 create-hosted-zone --name example.com --caller-reference 一意な文字列 --caller-reference は毎回違う値にする
ホストゾーン ID を調べる aws route53 list-hosted-zones-by-name --dns-name example.com 返る Id の /hostedzone/ より後ろを使う
レコードを新規作成する change-resource-record-sets + "Action": "CREATE" 同じ名前・タイプが既にあるとエラーで止まる
あれば更新、無ければ作成する change-resource-record-sets + "Action": "UPSERT" 繰り返し実行しても同じ結果になる
レコードを削除する change-resource-record-sets + "Action": "DELETE" 名前・タイプ・TTL・値を登録内容と一致させる
レコードを一覧する aws route53 list-resource-record-sets --hosted-zone-id ID --query で名前やタイプを絞り込める
反映を待つ aws route53 wait resource-record-sets-changed --id 変更ID 状態が INSYNC になるまで待つ
ホストゾーンを削除する aws route53 delete-hosted-zone --id ID NS と SOA 以外のレコードを先に消す

レコード種別ごとの ResourceRecordSet の書き方は次の表が目安です。

種別 Type ResourceRecords の Value の例 メモ
A A 192.0.2.10 IPv4 アドレス。複数の値を並べられる
CNAME CNAME www.example.com 値は 1 つだけ。ゾーンの頂点(Zone Apex)には置けない
TXT TXT "\"v=spf1 include:_spf.example.net ~all\"" 値全体を二重引用符で囲み、JSON 上は \" と書く
MX MX 10 mail1.example.com 「優先度 半角スペース ホスト名」
エイリアス A など ResourceRecords と TTL の代わりに AliasTarget を書く AWS リソース向け。Zone Apex にも置ける

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Route 53レコードをAWS CLIで作成|A・CNAME・エイリアス例」

ホストゾーンを作成して ID を変数に入れる

レコードを入れる箱が「ホストゾーン」です。 まずドメイン名を指定してパブリックホストゾーンを作ります。--caller-reference は同じリクエストの二重実行を見分けるための文字列で、日時など毎回違う値を渡します。

aws route53 create-hosted-zone \
  --name example.com \
  --caller-reference "zone-20261005-01"
{
    "Location": "https://route53.amazonaws.com/2013-04-01/hostedzone/4E37D9AD5AQP2YKRJ6ZEA0",
    "HostedZone": {
        "Id": "/hostedzone/4E37D9AD5AQP2YKRJ6ZEA0",
        "Name": "example.com.",
        "CallerReference": "zone-20261005-01",
        "Config": {
            "PrivateZone": false
        },
        "ResourceRecordSetCount": 2
    },
    …(省略)…
    "DelegationSet": {
        …(省略)…
        "NameServers": [
            "ns-2048.awsdns-64.com",
            "ns-2049.awsdns-65.net",
            "ns-2050.awsdns-66.org",
            "ns-2051.awsdns-67.co.uk"
        ]
    }
}

ResourceRecordSetCount が 2 なのは、NS レコードと SOA レコードが自動で作られるためです。NameServers の 4 つは、ドメインを取得したレジストラ側に登録するネームサーバーです。

以降のコマンドで何度も使うので、ホストゾーン ID をシェル変数に入れておきます。Id は /hostedzone/ つきで返るので、sed で取り除いています。

ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name example.com \
  --query 'HostedZones[0].Id' --output text | sed 's|/hostedzone/||')
echo $ZONE_ID

ネームサーバーだけを後から確認したいときは get-hosted-zone を使います。

aws route53 get-hosted-zone --id $ZONE_ID --query 'DelegationSet.NameServers'
[
    "ns-2048.awsdns-64.com",
    "ns-2049.awsdns-65.net",
    "ns-2050.awsdns-66.org",
    "ns-2051.awsdns-67.co.uk"
]

A レコードを作成する(CREATE)

change-batch は「Changes の配列に、Action と ResourceRecordSet の組を並べる」形です。 www.example.com を 192.0.2.10 に向ける A レコードの JSON を a-create.json として保存します。

{
  "Comment": "www の A レコードを作成",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "192.0.2.10" }]
      }
    }
  ]
}

file:// をつけてファイルを渡します。変更 ID だけを変数に受け取り、反映を待ってから状態を確認する流れにすると、スクリプトに組み込みやすくなります。

CHANGE_ID=$(aws route53 change-resource-record-sets \
  --hosted-zone-id $ZONE_ID \
  --change-batch file://a-create.json \
  --query 'ChangeInfo.Id' --output text)
echo "CHANGE_ID=$CHANGE_ID"

aws route53 wait resource-record-sets-changed --id $CHANGE_ID
aws route53 get-change --id $CHANGE_ID --query 'ChangeInfo.Status' --output text
CHANGE_ID=/change/C2682N5HXP0BZ4
INSYNC

AWS の API リファレンスによると、変更は受け付け直後は PENDING、すべてのネームサーバーに行き渡ると INSYNC になり、通常は 60 秒以内に反映されます。wait resource-record-sets-changed は INSYNC になるまで待つコマンドです。

登録できたかは、名前で絞り込んで確認します。Route 53 が返す Name は末尾にドットがつくので、--query の比較文字列も www.example.com. と書きます。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Name=='www.example.com.']"

CREATE・UPSERT・DELETE の違い

3 つの Action の違いは「既にあるレコードに対してどう動くか」です。

Action レコードが無いとき 同じ名前・タイプが既にあるとき 向いている場面
CREATE 作成する InvalidChangeBatch で止まる 既存を上書きしたくない新規登録
UPSERT 作成する 渡した内容で置き換える 値の変更、何度流しても同じ結果にしたい手順
DELETE エラーで止まる 内容が一致すれば削除する 不要になったレコードの整理

既にあるレコードに CREATE するとこうなる

先ほどの a-create.json をもう一度流すと、次のエラーで止まります。

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID \
  --change-batch file://a-create.json
An error occurred (InvalidChangeBatch) when calling the ChangeResourceRecordSets operation: Tried to create resource record set [name='www.example.com.', type='A'] but it already exists

「but it already exists」は、同じ名前・タイプのレコードが登録済みという意味です。 値を変えたいなら UPSERT を使います。

UPSERT で値を置き換える

Action を UPSERT に、値を 192.0.2.20 に変えた a-upsert.json を用意します。

{
  "Comment": "www の A レコードを更新",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "192.0.2.20" }]
      }
    }
  ]
}
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID \
  --change-batch file://a-upsert.json

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Name=='www.example.com.']"
[
    {
        "Name": "www.example.com.",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
            {
                "Value": "192.0.2.20"
            }
        ]
    }
]

UPSERT は「値の追加」ではなくレコードセット全体の置き換えです。値を 2 つにしたいときは、残したい値も含めて全部書きます。

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID \
  --change-batch '{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"api.example.com","Type":"A","TTL":60,"ResourceRecords":[{"Value":"192.0.2.40"},{"Value":"192.0.2.41"}]}}]}' \
  --query 'ChangeInfo.Id' --output text

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Name=='api.example.com.'].ResourceRecords[].Value" --output text
/change/C2682N5HXP0BZ4
192.0.2.40  192.0.2.41

短い変更なら、このように JSON を 1 行の文字列で直接渡すこともできます。

1 回の change-batch は「全部通るか、全部通らないか」

Changes に複数の変更を並べたとき、1 つでも検証を通らないものがあると、全体が取り消されます。 新しい new.example.com の CREATE と、既にある www.example.com の CREATE を 1 つの JSON に入れて試します。

{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "new.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "192.0.2.30" }]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "192.0.2.10" }]
      }
    }
  ]
}
An error occurred (InvalidChangeBatch) when calling the ChangeResourceRecordSets operation: Tried to create resource record set [name='www.example.com.', type='A'] but it already exists

直後に一覧を見ると、1 つ目の new.example.com も作られていません。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query 'ResourceRecordSets[].[Name,Type]' --output text
example.com.    NS
example.com.    SOA
www.example.com.    A

この性質は、CNAME からエイリアスへの切り替えのように「消して、すぐ作る」を途切れなく行いたいときに役立ちます。DELETE と CREATE を同じ change-batch に並べれば、片方だけが反映された状態になりません。

CNAME・TXT・MX をまとめて登録する

種別が違っても、Type と Value の書き方が変わるだけです。 3 種類を 1 つの multi.json にまとめます。繰り返し流せるよう、すべて UPSERT にしています。

{
  "Comment": "CNAME・TXT・MX をまとめて登録",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "blog.example.com",
        "Type": "CNAME",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "www.example.com" }]
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "example.com",
        "Type": "TXT",
        "TTL": 300,
        "ResourceRecords": [
          { "Value": "\"v=spf1 include:_spf.example.net ~all\"" },
          { "Value": "\"site-verification=abc123\"" }
        ]
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "example.com",
        "Type": "MX",
        "TTL": 3600,
        "ResourceRecords": [
          { "Value": "10 mail1.example.com" },
          { "Value": "20 mail2.example.com" }
        ]
      }
    }
  ]
}
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID \
  --change-batch file://multi.json

登録後に TXT と MX の値を取り出すと、次のように入っています。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Type=='TXT'].ResourceRecords[].Value"
[
    "\"v=spf1 include:_spf.example.net ~all\"",
    "\"site-verification=abc123\""
]
aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Type=='MX'].ResourceRecords[].Value" --output text
10 mail1.example.com    20 mail2.example.com

種別ごとの注意点を、AWS の開発者ガイド(サポートされる DNS レコードタイプ)の記載に沿って整理します。

種別 書き方の注意
CNAME 値は 1 つだけ。CNAME を作った名前には、ほかのレコードを作れない。Zone Apex(example.com そのもの)には作れない
TXT 値を二重引用符で囲む。1 つの文字列は 255 文字まで。それより長い値は "文字列1" "文字列2" のように分けて 1 つの Value に書く。値全体は 4,000 文字まで
TXT(複数) 同じ名前に用途の違う TXT を置くときは、1 つのレコードセットの ResourceRecords に並べる
MX 「優先度 ホスト名」を半角スペースで区切る。数字が小さいほうが優先

同じ名前・同じタイプのレコードセットは 1 つだけです。example.com の TXT を後から追加するときは、既にある値も含めて UPSERT します。

エイリアスレコードを作成する(ALB・CloudFront・S3)

エイリアスレコードは、Route 53 独自の「AWS リソースを指すレコード」です。 TTL と ResourceRecords を書かず、代わりに AliasTarget を書きます。CNAME と違って Zone Apex にも置けます。

ALB を指すエイリアス

{
  "Comment": "ALB へのエイリアス",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "app.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z14GRHDCWA56QT",
          "DNSName": "my-alb-1234567890.ap-northeast-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }
  ]
}

CloudFront を指すエイリアス(Zone Apex)

{
  "Comment": "CloudFront へのエイリアス(Zone Apex)",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "d111111abcdef8.cloudfront.net",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}

S3 のウェブサイトエンドポイントを指すエイリアス

{
  "Comment": "S3 ウェブサイトエンドポイントへのエイリアス",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "static.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z2M4EHUR26P7ZW",
          "DNSName": "s3-website-ap-northeast-1.amazonaws.com",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}

3 つを順に流したあと、エイリアスだけを一覧すると次のようになります。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?AliasTarget].[Name,AliasTarget.DNSName,AliasTarget.HostedZoneId]" \
  --output table
-------------------------------------------------------------------------------------------------
|                                    ListResourceRecordSets                                     |
+---------------------+------------------------------------------------------+------------------+
|  example.com.       |  d111111abcdef8.cloudfront.net                       |  Z2FDTNDATAQYW2  |
|  app.example.com.   |  my-alb-1234567890.ap-northeast-1.elb.amazonaws.com  |  Z14GRHDCWA56QT  |
|  static.example.com.|  s3-website-ap-northeast-1.amazonaws.com             |  Z2M4EHUR26P7ZW  |
+---------------------+------------------------------------------------------+------------------+

エイリアス先ごとの HostedZoneId 早見表

AliasTarget の HostedZoneId は、自分のホストゾーン ID ではなく「エイリアス先のサービス側」の ID です。 ここを取り違えるとエラーになります。AWS の API リファレンス(AliasTarget)と、General Reference のエンドポイント一覧に載っている値・調べ方をまとめます。

エイリアス先 HostedZoneId DNSName に書くもの EvaluateTargetHealth
CloudFront Z2FDTNDATAQYW2(固定) ディストリビューションのドメイン名(dxxxx.cloudfront.net) false のみ
ALB(東京) Z14GRHDCWA56QT ALB の DNSName true / false
ALB(大阪) Z5LXEXXYW11ES 同上 true / false
ALB(バージニア北部) Z35SXDOTRQ7X7K 同上 true / false
NLB(東京) Z31USIVHYNEOWT NLB の DNSName true / false
NLB(大阪) Z1GWIQ4HH19I5X 同上 true / false
S3 ウェブサイト(東京) Z2M4EHUR26P7ZW s3-website-ap-northeast-1.amazonaws.com true / false
S3 ウェブサイト(大阪) Z2YQB5RD63NC85 s3-website.ap-northeast-3.amazonaws.com true / false
S3 ウェブサイト(バージニア北部) Z3AQBSTGFYJSTF s3-website-us-east-1.amazonaws.com true / false
Global Accelerator Z2BJ6XQ5FK7U4H(固定) アクセラレーターの DNS 名 true / false
同じホストゾーン内の別レコード 自分のホストゾーン ID 指したいレコードの名前 true / false

表に無いリージョンやサービスは、コマンドで調べられます。

エイリアス先 調べるコマンド 見る項目
ALB・NLB aws elbv2 describe-load-balancers --names 名前 DNSName と CanonicalHostedZoneId
Classic Load Balancer aws elb describe-load-balancers DNSName と CanonicalHostedZoneNameId
API Gateway(リージョン) aws apigateway get-domain-names regionalDomainName と regionalHostedZoneId
API Gateway(エッジ最適化) aws apigateway get-domain-names distributionDomainName と distributionHostedZoneId
VPC インターフェイスエンドポイント aws ec2 describe-vpc-endpoints DnsName と HostedZoneId

ALB なら、次の 1 行で 2 つの値をまとめて取り出せます。

aws elbv2 describe-load-balancers --names my-alb \
  --query 'LoadBalancers[0].[DNSName,CanonicalHostedZoneId]' --output text

エイリアスと CNAME の選び方

比べる点 エイリアス CNAME
向け先 AWS リソース、または同じホストゾーン内のレコード どの DNS 名でも指定できる
Zone Apex に置けるか 置ける 置けない
TTL 指定しない(向け先に従う) 指定する
AWS リソース向けクエリの料金 かからない 通常のクエリとして数えられる
dig での見え方 A レコードなど指定したタイプで返る CNAME として返る

AWS リソースを指すならエイリアス、AWS の外のホスト名を指すなら CNAME、と覚えておくと選びやすくなります。

レコードを一覧して --query で絞り込む

list-resource-record-sets は全レコードを返すので、--query(JMESPath)で必要な部分だけを取り出します。

名前・タイプ・TTL を表で見る例です。エイリアスは TTL を持たないため None と表示されます。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query 'ResourceRecordSets[].[Name,Type,TTL]' --output table
--------------------------------------------
|          ListResourceRecordSets          |
+----------------------+--------+----------+
|  example.com.        |  A     |  None    |
|  example.com.        |  MX    |  3600    |
|  example.com.        |  NS    |  172800  |
|  example.com.        |  SOA   |  900     |
|  example.com.        |  TXT   |  300     |
|  app.example.com.    |  A     |  None    |
|  blog.example.com.   |  CNAME |  300     |
|  static.example.com. |  A     |  None    |
|  www.example.com.    |  A     |  300     |
+----------------------+--------+----------+

よく使う絞り込みをまとめます。

取り出したいもの --query の書き方
特定の名前のレコード "ResourceRecordSets[?Name=='www.example.com.']"
特定のタイプだけ "ResourceRecordSets[?Type=='TXT']"
エイリアスだけ "ResourceRecordSets[?AliasTarget]"
エイリアス以外の A レコードの名前と値 "ResourceRecordSets[?Type=='A' && !AliasTarget].[Name,ResourceRecords[0].Value]"
名前に文字列を含むもの "ResourceRecordSets[?contains(Name,'app')]"
レコード数 "length(ResourceRecordSets)"

エイリアス以外の A レコードだけを取り出した結果です。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Type=='A' && !AliasTarget].[Name,ResourceRecords[0].Value]" \
  --output text
www.example.com.    192.0.2.20

レコードが多いゾーンでは、開始位置と件数を指定して少しずつ読むこともできます。

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --start-record-name blog.example.com --start-record-type CNAME --max-items 1
{
    "ResourceRecordSets": [
        {
            "Name": "blog.example.com.",
            "Type": "CNAME",
            "TTL": 300,
            "ResourceRecords": [
                {
                    "Value": "www.example.com"
                }
            ]
        }
    ],
    "IsTruncated": true,
    "NextRecordName": "static.example.com.",
    "NextRecordType": "A",
    "MaxItems": "1"
}

IsTruncated が true のときは、NextRecordName と NextRecordType を次の開始位置に指定すると続きを読めます。

レコードを削除する(DELETE)

DELETE は、名前とタイプだけでなく、TTL と値(エイリアスなら AliasTarget)まで登録内容と同じに書きます。 AWS の API リファレンスにも「作成時に指定した値と同じ値をすべて指定する」とあります。

いま www.example.com は 192.0.2.20・TTL 300 なので、a-delete.json は次のようになります。

{
  "Comment": "www の A レコードを削除",
  "Changes": [
    {
      "Action": "DELETE",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "192.0.2.20" }]
      }
    }
  ]
}
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID \
  --change-batch file://a-delete.json

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Name=='www.example.com.']"
[]

登録内容をそのまま使って削除するスクリプト

値や TTL を手で写すと、1 文字の違いで DELETE が通らなくなります。いまの登録内容を list-resource-record-sets で取り出し、そのまま DELETE に渡すと写し間違いがなくなります。エイリアスレコードにも同じスクリプトが使えます。

#!/bin/bash
# 使い方: bash delete-record.sh ホストゾーンID レコード名(末尾ドットつき) タイプ
# 例:     bash delete-record.sh Z0123456789ABC www.example.com. A
set -euo pipefail
ZONE_ID=$1; NAME=$2; TYPE=$3

# いまの登録内容をそのまま取り出す(値・TTL・エイリアス先を手で写さない)
RRSET=$(aws route53 list-resource-record-sets --hosted-zone-id "$ZONE_ID" \
  --query "ResourceRecordSets[?Name=='$NAME' && Type=='$TYPE'] | [0]")

if [ "$RRSET" = "null" ]; then
  echo "record not found: $NAME $TYPE"
  exit 1
fi

aws route53 change-resource-record-sets --hosted-zone-id "$ZONE_ID" \
  --change-batch "{\"Changes\":[{\"Action\":\"DELETE\",\"ResourceRecordSet\":$RRSET}]}" \
  --query 'ChangeInfo.Id' --output text
echo "deleted: $NAME $TYPE"

A レコードとエイリアスレコードを順に削除し、最後にもう一度同じレコードを指定した結果です。

bash delete-record.sh $ZONE_ID www.example.com. A
bash delete-record.sh $ZONE_ID app.example.com. A
bash delete-record.sh $ZONE_ID www.example.com. A
/change/C2682N5HXP0BZ4
deleted: www.example.com. A
/change/C2682N5HXP0BZ4
deleted: app.example.com. A
record not found: www.example.com. A

ホストゾーンを削除する

ホストゾーンは、NS と SOA 以外のレコードが残っていると削除できません。

aws route53 delete-hosted-zone --id $ZONE_ID
An error occurred (HostedZoneNotEmpty) when calling the DeleteHostedZone operation: The hosted zone contains resource records that are not SOA or NS records.

残りのレコードを DELETE でまとめて消してから、もう一度実行します。複数の DELETE も 1 つの change-batch に並べられます。

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID \
  --change-batch file://delete-rest.json

aws route53 list-resource-record-sets --hosted-zone-id $ZONE_ID \
  --query 'ResourceRecordSets[].[Name,Type]' --output text
example.com.    NS
example.com.    SOA
aws route53 delete-hosted-zone --id $ZONE_ID
aws route53 list-hosted-zones --query 'HostedZones[].Name'
[]

InvalidChangeBatch などのエラー文と直し方

エラー文の「Tried to …」以降を読むと、どのレコードの何が合っていないかがわかります。 まず、今回の確認環境で実際に出たものです。

操作 エラー文 直し方
既にあるレコードに CREATE (InvalidChangeBatch) … Tried to create resource record set [name='www.example.com.', type='A'] but it already exists UPSERT に変える。または既存を確認して名前を見直す
Action を小文字で書いた (InvalidInput) … Invalid XML ; cvc-enumeration-valid: Value 'create' is not facet-valid with respect to enumeration '[CREATE, DELETE, UPSERT]'. CREATE UPSERT DELETE を大文字で書く
ホストゾーン ID が違う (NoSuchHostedZone) … No hosted zone found with ID: Z0000000000 list-hosted-zones-by-name で ID を取り直す
レコードが残ったままゾーン削除 (HostedZoneNotEmpty) … The hosted zone contains resource records that are not SOA or NS records. NS・SOA 以外を先に削除する

次に、AWS 公式ナレッジセンター(Route 53 のレコード作成時のエラー対処、CNAME 作成時のエラー対処)に掲載されているエラー文です。ドメイン名の部分は掲載どおりの例です。

場面 掲載されているエラー文 原因と直し方
DELETE の名前・タイプ・TTL が登録内容と違う Tried to delete resource record set [name='a.domain.com.', type='A'] but the values provided do not match... JSON の Name・Type・TTL を現在の登録内容に合わせる
DELETE の値が登録内容と違う Tried to delete resource record set [name='a.domain.com.', type='A'] but the rdata provided is invalid ResourceRecords の値を現在の登録内容に合わせる
無いレコードを DELETE Tried to delete resource record set [name='a.domain.com.', type='A'] but it was not found. ホストゾーン ID とレコード名を確認する
Zone Apex に CNAME RRSet of type CNAME with DNS name example.com. is not permitted at apex in zone example.com Zone Apex はエイリアスレコードにする
ほかのレコードがある名前に CNAME RRSet of type CNAME with DNS name test.example.com. is not permitted as it conflicts with other records with the same DNS name 既存レコードを整理する。AWS リソース向けならエイリアスにする
CNAME がある名前に A・MX・TXT RRSet of type MX with DNS name b.domain.com. is not permitted because a conflicting RRSet of type CNAME... CNAME と同じ名前には置けないので、別の名前にする
エイリアスの HostedZoneId が違う Tried to create an alias that targets applicationenv.us-west-2.elasticbeanstalk.com., type A... HostedZoneId をエイリアス先のサービス・リージョンの ID にする

DELETE に関する上の 3 つは、前の節の delete-record.sh のように現在の登録内容を取り出してそのまま渡すことでまとめて避けられます。

よくある質問

Q. CREATE と UPSERT はどちらを使えばよいですか? A. 手順書やスクリプトで繰り返し流すなら UPSERT が扱いやすいです。既にあっても無くても同じ結果になります。既存のレコードをうっかり置き換えたくない新規登録では CREATE にしておくと、同じ名前・タイプがあるときにエラーで知らせてくれます。

Q. レコード名の末尾のドットは必要ですか? A. change-batch の Name はドットなしで書いても登録できました。一方、list-resource-record-sets が返す Name は末尾ドットつきなので、--query で名前を比較するときは www.example.com. と書きます。

Q. TXT レコードの値はなぜ \" と書くのですか? A. TXT の値は二重引用符で囲む決まりがあり、その引用符を JSON の文字列の中に入れるために \" とエスケープします。結果として "\"v=spf1 ...\"" という形になります。

Q. 変更が反映されたかはどう確かめますか? A. change-resource-record-sets が返す変更 ID を aws route53 get-change --id に渡すと状態が見られます。INSYNC なら Route 53 のネームサーバーへの反映が済んでいます。スクリプトでは aws route53 wait resource-record-sets-changed --id で待てます。

まとめ

  • レコードの作成・更新・削除はすべて change-resource-record-sets で行い、JSON の Action と ResourceRecordSet を書き分ける
  • CREATE は既存があると止まり、UPSERT は置き換え、DELETE は登録内容との一致が必要
  • 1 つの change-batch は全部通るか全部通らないか。DELETE と CREATE を並べると切り替えが途切れない
  • TXT は \" で囲み、MX は「優先度 ホスト名」、CNAME は値 1 つで Zone Apex には置けない
  • エイリアスは AliasTarget を書き、HostedZoneId はエイリアス先サービスの ID を使う
  • DELETE は現在の登録内容を取り出してそのまま渡すと写し間違いがなくなる
  • 一覧は --query で絞り込み、比較する名前には末尾のドットをつける

参考・出典

  • AWS Route 53 API リファレンス:ChangeResourceRecordSets、AliasTarget
  • Amazon Route 53 開発者ガイド:サポートされる DNS レコードタイプ、エイリアスレコードと非エイリアスレコードの選択
  • AWS General Reference:Elastic Load Balancing のエンドポイント、Amazon S3 ウェブサイトエンドポイント
  • AWS ナレッジセンター:Troubleshoot errors when creating Route 53 resource record sets with the AWS CLI、Troubleshoot CNAME record creation errors in Route 53

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Route 53レコードをAWS CLIで作成|A・CNAME・エイリアス例」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?