Route 53

Amazon Route 53 is a simple and robust DNS service. This PHP class is a REST-based interface to that service.

Some example code will show how easy it is to use this class. I will assume you are already familiar with how DNS and nameservers in general work, as well as what a hosted zone is and how a hosted zone differs from a domain name. If you need more information on that topic, please consult the official Route 53 documentation.

Once you’ve read these examples, feel free to leave a comment if you have any questions or comments.

Let’s get started! First, you’ll need to create a Route53 class object:

require_once('r53.php');
$r53 = new Route53('Access Key Here', 'Secret Key Here');

Next, create the DNS zone you want hosted:

$result = $r53->createHostedZone('example.com', 'unique identifier', 'An optional comment');

There are three parameters to createHostedZone:

  1. The hosted zone to create. This cannot be a top-level domain name (e.g. “com.”). Amazon Route53 requires that the zone name end with a period, but this class will add it for you if you forget.
  2. A unique identifier that you choose to identify this request. This will prevent accidentally creating two hosted zones for the same domain. (It is both possible and valid to do so, but if you do that you’ll need to provide different unique identifiers for each createHostedZone request.)
  3. An optional comment that you want attached to the hosted zone. This can be any text you choose, and is included with the zone information if you query for it later. You may omit this third parameter if you do not want to add a comment.

Be careful! Amazon Route 53 charges your account $0.50 per hosted zone per month, and this amount is not prorated. This means it costs you $0.50 to make a call to createHostedZone, even if you don’t use it for more than a few seconds, so only create a zone if you’re going to use it!

After this call, $result will contain a bunch of information about the new zone. Let’s take a look:

print_r($result);
-------
Array
(
    [HostedZone] => Array
        (
            [Id] => /hostedzone/Z1PA6795UKMFR9
            [Name] => example.com.
            [CallerReference] => unique identifier
            [Config] => Array
                (
                    [Comment] => An optional comment
                )
        )
    [ChangeInfo] => Array
        (
            [Id] => /change/C1PA6795UKMFR9
            [Status] => PENDING
            [SubmittedAt] => 2011-01-20T23:45:25.320Z
        )
    [NameServers] => Array
        (
            [0] => ns-415.awsdns-51.com
            [1] => ns-726.awsdns-26.net
            [2] => ns-1358.awsdns-41.org
            [3] => ns-1555.awsdns-02.co.uk
        )
)

When you create a zone, it is prepopulated with an SOA record and four NS records. If you did not provide a comment, you would not see the Config array under HostedZone. Under ChangeInfo, you can see that the status is ‘PENDING’. Let’s wait a few seconds,
then check if it’s done yet, using the getChange API:

print_r($r53->getChange('/change/C1PA6795UKMFR9'));
-------
Array
(
    [Id] => /change/C1PA6795UKMFR9
    [Status] => INSYNC
    [SubmittedAt] => 2011-01-20T23:45:25.320Z
)

As you can see, when the change has been propogated to the servers hosting your zone, the status will change to ‘INSYNC’.

Now let’s add an A record:

$change = $r53->prepareChange('CREATE', 'example.com.', 'A', 86400, '192.0.2.1');
print_r($r53->changeResourceRecordSets('/hostedzone/Z1PA6795UKMFR9', $change);
-------
Array
(
    [Id] => /change/C1PA6795UKMXZ9
    [Status] => PENDING
    [SubmittedAt] => 2011-01-20T23:59:59.572Z
)

The object returned by Route53::prepareChange() is not meant to be user-visible. Its contents may change at any time in future versions of this library, so if you poke around in it, your code may not work when you upgrade to a newer version of this library!

Note the period following the domain name — it is not optional! If you leave it off, then AWS will treat the value as relative to the zone root — that means you would be setting an A record for example.com.example.com instead of just for example.com, so be specific!

If you have more than one change to make, you should do them together in one call:

$changes = array();
$changes[] = $r53->prepareChange('CREATE', 'www.example.com.', 'A', 86400, '192.0.2.1');
$changes[] = $r53->prepareChange('CREATE', 'fake.example.com.', 'A', 86400, '192.0.2.1');
$result = $r53->changeResourceRecordSets('/hostedzone/Z1PA6795UKMFR9', $changes);

All of these changes are done in a single transaction — either all of them will fail, or all of them will succeed. Because of that, you will receive only a single change ID, no matter how many changes you make in a single call.

You can also delete records. Suppose you didn’t mean to create fake.example.com:

$change = $r53->prepareChange('DELETE', 'fake.example.com.', 'A', 86400, '192.0.2.1');
$result = $r53->changeResourceRecordSets('/hostedzone/Z1PA6795UKMFR9', $change);

Note that if you’re going to delete a record, all of the parameters to the delete change request must be identical to the record’s current values. The call will fail if any of them are wrong.

You can, of course, create other types of records. The trickiest one is MX records. For MX records, you need to set all of the MX records and their priorities in a single call, like this:

$mailservers = array('10 mx.example.com.', '20 mx2.example.com.', '30 mx3.example.com.');
$change = $r53->prepareChange('CREATE', 'example.com.', 'MX', 86400, $mailservers);
$result = $r53->changeResourceRecordSets('/hostedzone/Z1PA6795UKMFR9', $change);

You can find more information on creating records in the Amazon Route 53 API reference.

You can also list all the hosted zones on your account:

print_r($r53->listHostedZones());
-------
Array
(
    [HostedZone] => Array
        (
            [0] => Array
                (
                    [Id] => /hostedzone/Z1PA6795UKMFR9
                    [Name] => example.com.
                    [CallerReference] => unique identifier
                    [Config] => Array
                        (
                            [Comment] => An optional comment
                        )
                )
            [1] => Array
                (
                    [Id] => /hostedzone/Y1PA6795UKMFR1
                    [Name] => example.net.
                    [CallerReference] => some unique id
                )
        )
    [MaxItems] => 100
    [IsTruncated] => false
)

You can delete hosted zones:

print_r($r53->deleteHostedZone('/hostedzone/Y1PA6795UKMFR1'));
-------
Array
(
    [Id] => /change/C1PA6795UKMXY9
    [Status] => PENDING
    [SubmittedAt] => 2011-01-21T02:17:22.640Z
)

You can call getHostedZone to get more information on the zone, including its nameservers:

print_r($r53->getHostedZone('/hostedzone/Z1PA6795UKMFR9'));
-------
Array
(
    [HostedZone] => Array
        (
            [Id] => /hostedzone/Z1PA6795UKMFR9
            [Name] => example.com.
            [CallerReference] => unique identifier
            [Config] => Array
                (
                    [Comment] => An optional comment
                )
        )
    [NameServers] => Array
        (
            [0] => ns-415.awsdns-51.com
            [1] => ns-726.awsdns-26.net
            [2] => ns-1358.awsdns-41.org
            [3] => ns-1555.awsdns-02.co.uk
        )
)

If that looks familiar, it should: createHostedZone returned that information as well.

Finally, you can list all the records in a zone:

print_r($r53->listResourceRecordSets('/hostedzone/Z1PA6795UKMFR9'));
-------
Array
(
    [ResourceRecordSets] => Array
        (
            [0] => Array
                (
                    [Name] => example.com.
                    [Type] => A
                    [TTL] => 86400
                    [ResourceRecords] => Array
                        (
                            [0] => 192.168.0.2
                        )
                )
            [1] => Array
                (
                    [Name] => example.com.
                    [Type] => MX
                    [TTL] => 86400
                    [ResourceRecords] => Array
                        (
                            [0] => 10 mx1.example.com.
                            [1] => 20 mx2.example.com.
                            [2] => 30 mx3.example.com.
                        )
                )
            [2] => Array
                (
                    [Name] => example.com.
                    [Type] => NS
                    [TTL] => 172800
                    [ResourceRecords] => Array
                        (
                            [0] => ns-1358.awsdns-41.org.
                            [1] => ns-1555.awsdns-02.co.uk.
                            [2] => ns-726.awsdns-26.net.
                            [3] => ns-415.awsdns-51.com.
                        )
                )
            [3] => Array
                (
                    [Name] => example.com.
                    [Type] => SOA
                    [TTL] => 900
                    [ResourceRecords] => Array
                        (
                            [0] => ns-1358.awsdns-41.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
                        )
                )
            [4] => Array
                (
                    [Name] => www.example.com.
                    [Type] => A
                    [TTL] => 86400
                    [ResourceRecords] => Array
                        (
                            [0] => 192.168.0.2
                        )
                )
        )
    [MaxItems] => 100
    [IsTruncated] => false
)

And that’s all there is to it!


Disclaimer: Although I worked for Amazon in the past, this class was neither produced, endorsed nor supported by Amazon.  I wrote this class as a personal project, just for fun.

41 thoughts on “Route 53

  1. Trav

    Hey Dan,

    This is great, thank you so much. Just curious if you’re looking for help on this; I’ve been trying to extend the classes to create a rudimentary web-form-based interface for r53DNS. I’m reasonably good at PHP, but not so much of an OOP-er; this seems like an interesting project to get more familiar with it.

    Reply
  2. Alex Smith

    Hi Dan,

    Will you be updating this at some point in the near future to support AWS’ announcement today with WRR and ELB for the zone apex?

    Thanks! Great class!
    Alex

    Reply
  3. ckromero

    Per Alex Smith, this handles creation of a WRR, hope you find it useful!

    /**
    * Utility function to prepare a Change object for ChangeResourceRecordSets requests with weighted resources!
    * All fields are required.
    *
    * @param string action The action to perform. One of: CREATE, DELETE
    * @param string name The name to perform the action on.
    * If it does not end with ‘.’, then AWS treats the name as relative to the zone root.
    * @param string type The type of record being modified.
    * Must be one of: A, AAAA, CNAME, TXT !!!!!!
    * @param string setIdentifier A unique identifier for a weighted record.
    * @param string weight The weight determines what portion of traffic goes to this record set.
    * @param int ttl The time-to-live value for this record, in seconds.
    * @param array records An array of resource records to attach to this change.
    * Each member of this array can either be a string, or an array of strings.
    * Passing an array of strings will attach multiple values to a single resource record.
    * If a single string is passed as $records instead of an array,
    * it will be treated as a single-member array.
    * @return object An opaque object containing the change request.
    * Do not write code that depends on the contents of this object, as it may change at any time.
    */
    public function prepareWeightedChange($action, $name, $type, $setIdentifier, $weight, $ttl, $records) {
    switch($type){
    case ‘A’:
    case ‘AAAA’:
    case ‘CNAME’:
    case ‘TXT’:
    $change = “\n”;
    $change .= ”.$action.”\n”;
    $change .= “\n”;
    $change .= ”.$name.”\n”;
    $change .= ”.$type.”\n”;
    $change .= ”.$setIdentifier.”\n”;
    $change .= ”.$weight.”\n”;
    $change .= ”.$ttl.”\n”;
    $change .= “\n”;
    break;
    default:
    die(“type needs to be A, AAAA, CNAME, TXT\n”);
    }

    if(!is_array($records)) {
    $records = array($records);
    }

    foreach($records as $record) {
    $change .= “\n”;
    if(is_array($record)) {
    foreach($record as $value) {
    $change .= ”.$value.”\n”;
    }
    }
    else {
    $change .= ”.$record.”\n”;
    }
    $change .= “\n”;
    }

    $change .= “\n”;
    $change .= “\n”;
    $change .= “\n”;

    return $change;
    }

    Reply
  4. Zladivliba

    Great lib ! I love it !
    We really miss the possibility to update AMZ with root domains though, this would be a very big plus since amazon updated their API.

    Well, thanks a lot for this work anyway !!

    Reply
  5. Gator

    Thanks for sharing your class that provides a very nice abstraction to the Rt 53 API. Something not mentioned here, that I ran across, is that multiple TXT records work much like multiple MX records, eg:


    $txts = array('"text record1"', '"text record2"','"text record3"');
    $change = $r53->prepareChange('CREATE', 'example.com.', 'TXT', 86400, $txts);
    $result = $r53->changeResourceRecordSets('/hostedzone/Z1PA6795UKMFR9', $change);

    Reply
  6. Rick

    Great interface to Route53 thanks! Any chance you could add examples for TXT and SPF records?

    Reply
  7. Anna

    Thanks so much- great writeup and love the library. I’d love support for “elb-associate-route53-hosted-zone” as well, if you’re inclined.

    Reply
  8. angro

    $r53->listResourceRecordSets(ZONE_ID, ‘A’, ‘subdomain.example.com’)

    bug with different subdomains

    Reply
  9. pk

    Hi, i am getting problem while deleting the name servers with delete command..Error is : Warning: Route53::changeResourceRecordSets(): Sender – InvalidChangeBatch: Tried to delete resource record set XXXXX.xxx., type NS but the values provided do not match the current values Request Id
    >>these name servers are automatically created when domain zone is created,
    >>i can delete the other resource records which i added manually using changeResourceRecordSets() function. but i am not able to delete the 4 name servers and 1 SOA records.(these are the automatically created resource records.
    pls respond

    Reply
    1. Dan

      Deleting those records would defeat the purpose of using Route 53… In other words, if you want to use Route 53, you need those records. It makes sense that you can’t delete them, because you *shouldn’t* delete them.

      Reply
      1. pk

        i understand that but the interface provided at link https://app.dns30.com/ can delete those records as well.
        So i am trying to make that work with your class. Please check the interface and pls respond.

        Reply
        1. Dan

          Can you show me the NS records (output from nslookup or something, if you can) and the code you’re using to try to delete them?

          Reply
  10. Umar

    Hi,

    Is there any possibility to UPDATE A, CNAME or MX records ?

    and what about the TXT records like spf and domains keys

    Best Regards,

    Uamr

    Reply
    1. Dan

      If you want to update a record, send a DELETE change and then a CREATE change in the same request (in that order). For example, suppose I want to change the IP address of http://www.example.com from 192.0.2.1 to 192.0.2.2:


      $changes = array();
      $changes[] = $r53->prepareChange('DELETE', 'www.example.com.', 'A', 86400, '192.0.2.1');
      $changes[] = $r53->prepareChange('CREATE', 'www.example.com.', 'A', 86400, '192.0.2.2');
      $result = $r53->changeResourceRecordSets('/hostedzone/Z1PA6795UKMFR9', $changes);

      Reply
      1. Umar

        Hi Dan,

        Thanks for you reply,

        One more question about SPF and domainkeys?

        Best Regards,

        Umar

        Reply
        1. Dan

          SPF works through TXT records, so for that you’d just add a TXT record to your zone with the appropriate content.

          I don’t know anything about domainkeys, but it probably works more or less the same way.

          Reply
          1. Umar

            HI Another Question

            The “listResourceRecordSets” return everything e.g A,CNAME,NS,MX records.

            Is is possible to get specific TYPE record like I want to list only A Records.

            Best Regards,

            Umar

            Reply
              1. Umar

                I did it

                But its not working according method which is mentioned in r53.php

                Here is the code

                $records = $r53->listResourceRecordSets(‘/hostedzone/Z1I85BBEI3d6N’, ‘A’, ‘mydomain.com’);

                but it still shows all record including NS, CNAME and MX

                –Umar

                Reply
  11. Umar

    No problem

    My This function done it

    function SearchRecords($records, $type) {
    $result = array();
    foreach($records as $record) {
    if(strtolower($record[‘Type’]) == strtolower($type)) {
    $result [] = $record;
    }
    }
    return ($result) ? $result : false;
    }

    –usage:

    $records = $r53->listResourceRecordSets(‘/hostedzone/ZEF90B0K59IVWO’);

    if(false !== ($a_result = SearchRecords($records[‘ResourceRecordSets’], “A”)))
    {
    print_r($a_result)
    }

    Reply
  12. Jon Dering

    Just wanted to say thank you for writing this. You have saved me hours of headache. Job well done, really.

    Reply
    1. Dan

      It looks like that requires using API version 2011-05-05; my class only supports the original 2010-10-01 API version. If this is just a one-time thing, you could use the AWS Console to set it up, otherwise you’ll need to modify the code in r53.php to use the newer API version and add the new operation.

      Reply
      1. Ian B

        Ignore my other question – the code is easy enough to understand that I was able to modify it easily enough to support the new API functions relating to weighted sets.

        -ian

        Reply
    2. KITAITI Makoto

      Hi, this is very helpful class.
      Thank you, Mr. Myers.
      And I needed to create alias records recently, so add a method to do so.

      https://gist.github.com/KitaitiMakoto/4972686

      By using above file, you can create/delete alias record by:


      $changes[] = $r53->prepareChange(...);
      $changes[] = $r53->prepareAliasChange('CREATE', $domain, $aliasHostedZone, $dnsName);
      :
      :
      $result = $r53->changeResourceRecordSets('/hostedzone/' . $hostedZone, $changes);

      Could you try and consider it?
      And I’m very appreciated if you add the method to next version.
      Thanks.

      Reply
  13. Ian B

    What about weighted record sets? I get this when listing a record with weights:

    [0] => This resource record set includes an attribute that is unsupported on this Route 53 endpoint. Please consider using a newer endpoint or a tool that does so.

    Reply
  14. Brian H

    We’ve encountered a bug where the maxItems parameter, though being passed into the API, doesn’t seem to affect the number of reponses returned (always returns 100). Is this a hard api limitation or is there an easy fix?

    Reply
  15. Brian H

    Ah, apparently 100 is the hard max for records returned. Easy to get around due how this api is implemented. Thanks for writing such a well self-documented API!

    $myrecs=$myr53->listResourceRecordSets($myid);
    $nextrecords=$myrecs[NextRecordName];
    while ($nextrecords)
    {
    $newrecs=$myr53->listResourceRecordSets($myid,”,$myrecs[NextRecordName]);
    foreach ($newrecs[ResourceRecordSets] as $setid=>$set) $myrecs[ResourceRecordSets][]=$set;
    $nextrecords=$newrecs[NextRecordName];
    }

    Reply
  16. Sahil

    I was constantly getting following error.

    Warning: Route53::listHostedZones(): 60 SSL certificate problem, verify that the CA cert is OK. Details:error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed in r53.php on line 539

    After debugging, found the error is coming from Curl with options (CURLOPT_SSL_VERIFYHOST, CURLOPT_SSL_VERIFYPEER) set to true

    To work around this, set following class variables to 0:
    protected $__verifyHost = 0;
    protected $__verifyPeer = 0;

    It worked for now but if you can help me figure out why it failed with options set to true.

    Reply
    1. Dan

      You probably need to configure PHP so it knows how to verify SSL certificates (it probably needs to know how to find the trusted certificate store). How you do this will depend on your operating system and (for Linux) the specific distribution you’re using.

      Reply
  17. Pingback: Amazon Web Services (AWS) — Elastic Compute Cloud (EC2) | ProgClub

  18. Paul

    Thanks for this – helped me quite a bit in dealing with the route53 API.
    Couple of small points:
    1. typo:
    print_r($r53->changeResourceRecordSets(‘/hostedzone/Z1PA6795UKMFR9’, $change);
    Needs a final closing ‘)’.

    2. Updating a record set is possible using the action “UPSERT”. I am sure you knew this but it was missing in your documentation and in the class comments.

    P.

    Reply
  19. Brian

    When I create an a-record, I get

    PHP Notice: curl_setopt(): CURLOPT_SSL_VERIFYHOST no longer accepts the value 1, value 2 will be used instead in r53.php on line 654
    and
    PHP Notice: Undefined property: stdClass::$body in r53.php on line 727

    The record is still created, but I do get these notices. Is there some way to ‘fix’ this? I’m really new to programming, so just wondering…

    By the way, this example of creating an a-record drove me bananas for a couple of hours:

    $change = $r53->prepareChange(‘CREATE’, ‘example.com.’, ‘A’, 86400, ‘192.0.2.1’);
    print_r($r53->changeResourceRecordSets(‘/hostedzone/Z1PA6795UKMFR9’, $change);
    ——-
    Array
    (
    [Id] => /change/C1PA6795UKMXZ9
    [Status] => PENDING
    [SubmittedAt] => 2011-01-20T23:59:59.572Z
    )

    It appears to be missing $result = $r53->changeResourceRecordSets(‘/hostedzone/Z1PA6795UKMFR9’, $change);

    Reply
  20. Michelle

    I’m trying to create and delete a record, and changeResourceRecordSets is returning false. I looked at your code and don’t understand why I’m not seeing more details of the failure. (It is not having any effect on records)

    Could an incorrect policy for an IAM user cause this? Any other ideas?

    Reply
  21. Michelle

    I just realized this is 10 year old code 😉 Does this r53 library still work in 2020?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *