Configure AWS Route 53 DNS Records

The aws:route53/record:Record resource, part of the Pulumi AWS provider, defines DNS records within Route 53 hosted zones: their names, types, values, and routing behavior. This guide focuses on four capabilities: simple A and CNAME records, weighted routing for traffic splitting, alias records for AWS services, and NS record management with allowOverwrite.

DNS records belong to hosted zones and often point to AWS resources like load balancers or accelerators. The examples are intentionally small. Combine them with your own hosted zones and target infrastructure.

Point a domain to an IP address

Most DNS configurations start with simple A records that map domain names to IP addresses, making services accessible via human-readable names.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const www = new aws.route53.Record("www", {
    zoneId: primary.zoneId,
    name: "www.example.com",
    type: aws.route53.RecordType.A,
    ttl: 300,
    records: [lb.publicIp],
});
import pulumi
import pulumi_aws as aws

www = aws.route53.Record("www",
    zone_id=primary["zoneId"],
    name="www.example.com",
    type=aws.route53.RecordType.A,
    ttl=300,
    records=[lb["publicIp"]])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := route53.NewRecord(ctx, "www", &route53.RecordArgs{
			ZoneId: pulumi.Any(primary.ZoneId),
			Name:   pulumi.String("www.example.com"),
			Type:   pulumi.String(route53.RecordTypeA),
			Ttl:    pulumi.Int(300),
			Records: pulumi.StringArray{
				lb.PublicIp,
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var www = new Aws.Route53.Record("www", new()
    {
        ZoneId = primary.ZoneId,
        Name = "www.example.com",
        Type = Aws.Route53.RecordType.A,
        Ttl = 300,
        Records = new[]
        {
            lb.PublicIp,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.route53.Record;
import com.pulumi.aws.route53.RecordArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var www = new Record("www", RecordArgs.builder()
            .zoneId(primary.zoneId())
            .name("www.example.com")
            .type("A")
            .ttl(300)
            .records(lb.publicIp())
            .build());

    }
}
resources:
  www:
    type: aws:route53:Record
    properties:
      zoneId: ${primary.zoneId}
      name: www.example.com
      type: A
      ttl: 300
      records:
        - ${lb.publicIp}

The name property sets the domain name, type specifies the record type (A for IPv4 addresses), and records contains the IP addresses to return. The ttl property controls how long DNS resolvers cache this answer (300 seconds here). The zoneId ties the record to a specific hosted zone.

Split traffic between environments with weighted routing

Teams running blue-green deployments or gradual rollouts use weighted routing to control what percentage of traffic reaches each environment.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const www_dev = new aws.route53.Record("www-dev", {
    zoneId: primary.zoneId,
    name: "www",
    type: aws.route53.RecordType.CNAME,
    ttl: 5,
    weightedRoutingPolicies: [{
        weight: 10,
    }],
    setIdentifier: "dev",
    records: ["dev.example.com"],
});
const www_live = new aws.route53.Record("www-live", {
    zoneId: primary.zoneId,
    name: "www",
    type: aws.route53.RecordType.CNAME,
    ttl: 5,
    weightedRoutingPolicies: [{
        weight: 90,
    }],
    setIdentifier: "live",
    records: ["live.example.com"],
});
import pulumi
import pulumi_aws as aws

www_dev = aws.route53.Record("www-dev",
    zone_id=primary["zoneId"],
    name="www",
    type=aws.route53.RecordType.CNAME,
    ttl=5,
    weighted_routing_policies=[{
        "weight": 10,
    }],
    set_identifier="dev",
    records=["dev.example.com"])
www_live = aws.route53.Record("www-live",
    zone_id=primary["zoneId"],
    name="www",
    type=aws.route53.RecordType.CNAME,
    ttl=5,
    weighted_routing_policies=[{
        "weight": 90,
    }],
    set_identifier="live",
    records=["live.example.com"])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := route53.NewRecord(ctx, "www-dev", &route53.RecordArgs{
			ZoneId: pulumi.Any(primary.ZoneId),
			Name:   pulumi.String("www"),
			Type:   pulumi.String(route53.RecordTypeCNAME),
			Ttl:    pulumi.Int(5),
			WeightedRoutingPolicies: route53.RecordWeightedRoutingPolicyArray{
				&route53.RecordWeightedRoutingPolicyArgs{
					Weight: pulumi.Int(10),
				},
			},
			SetIdentifier: pulumi.String("dev"),
			Records: pulumi.StringArray{
				pulumi.String("dev.example.com"),
			},
		})
		if err != nil {
			return err
		}
		_, err = route53.NewRecord(ctx, "www-live", &route53.RecordArgs{
			ZoneId: pulumi.Any(primary.ZoneId),
			Name:   pulumi.String("www"),
			Type:   pulumi.String(route53.RecordTypeCNAME),
			Ttl:    pulumi.Int(5),
			WeightedRoutingPolicies: route53.RecordWeightedRoutingPolicyArray{
				&route53.RecordWeightedRoutingPolicyArgs{
					Weight: pulumi.Int(90),
				},
			},
			SetIdentifier: pulumi.String("live"),
			Records: pulumi.StringArray{
				pulumi.String("live.example.com"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var www_dev = new Aws.Route53.Record("www-dev", new()
    {
        ZoneId = primary.ZoneId,
        Name = "www",
        Type = Aws.Route53.RecordType.CNAME,
        Ttl = 5,
        WeightedRoutingPolicies = new[]
        {
            new Aws.Route53.Inputs.RecordWeightedRoutingPolicyArgs
            {
                Weight = 10,
            },
        },
        SetIdentifier = "dev",
        Records = new[]
        {
            "dev.example.com",
        },
    });

    var www_live = new Aws.Route53.Record("www-live", new()
    {
        ZoneId = primary.ZoneId,
        Name = "www",
        Type = Aws.Route53.RecordType.CNAME,
        Ttl = 5,
        WeightedRoutingPolicies = new[]
        {
            new Aws.Route53.Inputs.RecordWeightedRoutingPolicyArgs
            {
                Weight = 90,
            },
        },
        SetIdentifier = "live",
        Records = new[]
        {
            "live.example.com",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.route53.Record;
import com.pulumi.aws.route53.RecordArgs;
import com.pulumi.aws.route53.inputs.RecordWeightedRoutingPolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var www_dev = new Record("www-dev", RecordArgs.builder()
            .zoneId(primary.zoneId())
            .name("www")
            .type("CNAME")
            .ttl(5)
            .weightedRoutingPolicies(RecordWeightedRoutingPolicyArgs.builder()
                .weight(10)
                .build())
            .setIdentifier("dev")
            .records("dev.example.com")
            .build());

        var www_live = new Record("www-live", RecordArgs.builder()
            .zoneId(primary.zoneId())
            .name("www")
            .type("CNAME")
            .ttl(5)
            .weightedRoutingPolicies(RecordWeightedRoutingPolicyArgs.builder()
                .weight(90)
                .build())
            .setIdentifier("live")
            .records("live.example.com")
            .build());

    }
}
resources:
  www-dev:
    type: aws:route53:Record
    properties:
      zoneId: ${primary.zoneId}
      name: www
      type: CNAME
      ttl: 5
      weightedRoutingPolicies:
        - weight: 10
      setIdentifier: dev
      records:
        - dev.example.com
  www-live:
    type: aws:route53:Record
    properties:
      zoneId: ${primary.zoneId}
      name: www
      type: CNAME
      ttl: 5
      weightedRoutingPolicies:
        - weight: 90
      setIdentifier: live
      records:
        - live.example.com

Weighted routing requires multiple records with the same name and type but different setIdentifier values. The weight property determines traffic distribution: here, 10% goes to dev (weight 10) and 90% to live (weight 90). Route 53 randomly selects a target based on these proportions.

Create alias records for AWS load balancers

AWS services like ELB, CloudFront, and API Gateway expose DNS names that change over time. Alias records point your domain to these services without hardcoding IP addresses.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const main = new aws.elb.LoadBalancer("main", {
    name: "foobar-elb",
    availabilityZones: ["us-east-1c"],
    listeners: [{
        instancePort: 80,
        instanceProtocol: "http",
        lbPort: 80,
        lbProtocol: "http",
    }],
});
const www = new aws.route53.Record("www", {
    zoneId: primary.zoneId,
    name: "example.com",
    type: aws.route53.RecordType.A,
    aliases: [{
        name: main.dnsName,
        zoneId: main.zoneId,
        evaluateTargetHealth: true,
    }],
});
import pulumi
import pulumi_aws as aws

main = aws.elb.LoadBalancer("main",
    name="foobar-elb",
    availability_zones=["us-east-1c"],
    listeners=[{
        "instance_port": 80,
        "instance_protocol": "http",
        "lb_port": 80,
        "lb_protocol": "http",
    }])
www = aws.route53.Record("www",
    zone_id=primary["zoneId"],
    name="example.com",
    type=aws.route53.RecordType.A,
    aliases=[{
        "name": main.dns_name,
        "zone_id": main.zone_id,
        "evaluate_target_health": True,
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/elb"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		main, err := elb.NewLoadBalancer(ctx, "main", &elb.LoadBalancerArgs{
			Name: pulumi.String("foobar-elb"),
			AvailabilityZones: pulumi.StringArray{
				pulumi.String("us-east-1c"),
			},
			Listeners: elb.LoadBalancerListenerArray{
				&elb.LoadBalancerListenerArgs{
					InstancePort:     pulumi.Int(80),
					InstanceProtocol: pulumi.String("http"),
					LbPort:           pulumi.Int(80),
					LbProtocol:       pulumi.String("http"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = route53.NewRecord(ctx, "www", &route53.RecordArgs{
			ZoneId: pulumi.Any(primary.ZoneId),
			Name:   pulumi.String("example.com"),
			Type:   pulumi.String(route53.RecordTypeA),
			Aliases: route53.RecordAliasArray{
				&route53.RecordAliasArgs{
					Name:                 main.DnsName,
					ZoneId:               main.ZoneId,
					EvaluateTargetHealth: pulumi.Bool(true),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var main = new Aws.Elb.LoadBalancer("main", new()
    {
        Name = "foobar-elb",
        AvailabilityZones = new[]
        {
            "us-east-1c",
        },
        Listeners = new[]
        {
            new Aws.Elb.Inputs.LoadBalancerListenerArgs
            {
                InstancePort = 80,
                InstanceProtocol = "http",
                LbPort = 80,
                LbProtocol = "http",
            },
        },
    });

    var www = new Aws.Route53.Record("www", new()
    {
        ZoneId = primary.ZoneId,
        Name = "example.com",
        Type = Aws.Route53.RecordType.A,
        Aliases = new[]
        {
            new Aws.Route53.Inputs.RecordAliasArgs
            {
                Name = main.DnsName,
                ZoneId = main.ZoneId,
                EvaluateTargetHealth = true,
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.elb.LoadBalancer;
import com.pulumi.aws.elb.LoadBalancerArgs;
import com.pulumi.aws.elb.inputs.LoadBalancerListenerArgs;
import com.pulumi.aws.route53.Record;
import com.pulumi.aws.route53.RecordArgs;
import com.pulumi.aws.route53.inputs.RecordAliasArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var main = new LoadBalancer("main", LoadBalancerArgs.builder()
            .name("foobar-elb")
            .availabilityZones("us-east-1c")
            .listeners(LoadBalancerListenerArgs.builder()
                .instancePort(80)
                .instanceProtocol("http")
                .lbPort(80)
                .lbProtocol("http")
                .build())
            .build());

        var www = new Record("www", RecordArgs.builder()
            .zoneId(primary.zoneId())
            .name("example.com")
            .type("A")
            .aliases(RecordAliasArgs.builder()
                .name(main.dnsName())
                .zoneId(main.zoneId())
                .evaluateTargetHealth(true)
                .build())
            .build());

    }
}
resources:
  main:
    type: aws:elb:LoadBalancer
    properties:
      name: foobar-elb
      availabilityZones:
        - us-east-1c
      listeners:
        - instancePort: 80
          instanceProtocol: http
          lbPort: 80
          lbProtocol: http
  www:
    type: aws:route53:Record
    properties:
      zoneId: ${primary.zoneId}
      name: example.com
      type: A
      aliases:
        - name: ${main.dnsName}
          zoneId: ${main.zoneId}
          evaluateTargetHealth: true

Alias records use the aliases property instead of records and ttl. The name and zoneId come from the target AWS resource (here, an ELB). The evaluateTargetHealth property tells Route 53 whether to check the load balancer’s health before routing traffic. Alias records always use a 60-second TTL managed by AWS.

Manage zone NS records with allowOverwrite

When Route 53 creates a hosted zone, it automatically generates NS and SOA records. The allowOverwrite property lets you manage these records without importing existing state.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.route53.Zone("example", {name: "test.example.com"});
const exampleRecord = new aws.route53.Record("example", {
    allowOverwrite: true,
    name: "test.example.com",
    ttl: 172800,
    type: aws.route53.RecordType.NS,
    zoneId: example.zoneId,
    records: [
        example.nameServers[0],
        example.nameServers[1],
        example.nameServers[2],
        example.nameServers[3],
    ],
});
import pulumi
import pulumi_aws as aws

example = aws.route53.Zone("example", name="test.example.com")
example_record = aws.route53.Record("example",
    allow_overwrite=True,
    name="test.example.com",
    ttl=172800,
    type=aws.route53.RecordType.NS,
    zone_id=example.zone_id,
    records=[
        example.name_servers[0],
        example.name_servers[1],
        example.name_servers[2],
        example.name_servers[3],
    ])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := route53.NewZone(ctx, "example", &route53.ZoneArgs{
			Name: pulumi.String("test.example.com"),
		})
		if err != nil {
			return err
		}
		_, err = route53.NewRecord(ctx, "example", &route53.RecordArgs{
			AllowOverwrite: pulumi.Bool(true),
			Name:           pulumi.String("test.example.com"),
			Ttl:            pulumi.Int(172800),
			Type:           pulumi.String(route53.RecordTypeNS),
			ZoneId:         example.ZoneId,
			Records: pulumi.StringArray{
				example.NameServers.ApplyT(func(nameServers []string) (string, error) {
					return nameServers[0], nil
				}).(pulumi.StringOutput),
				example.NameServers.ApplyT(func(nameServers []string) (string, error) {
					return nameServers[1], nil
				}).(pulumi.StringOutput),
				example.NameServers.ApplyT(func(nameServers []string) (string, error) {
					return nameServers[2], nil
				}).(pulumi.StringOutput),
				example.NameServers.ApplyT(func(nameServers []string) (string, error) {
					return nameServers[3], nil
				}).(pulumi.StringOutput),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Route53.Zone("example", new()
    {
        Name = "test.example.com",
    });

    var exampleRecord = new Aws.Route53.Record("example", new()
    {
        AllowOverwrite = true,
        Name = "test.example.com",
        Ttl = 172800,
        Type = Aws.Route53.RecordType.NS,
        ZoneId = example.ZoneId,
        Records = new[]
        {
            example.NameServers.Apply(nameServers => nameServers[0]),
            example.NameServers.Apply(nameServers => nameServers[1]),
            example.NameServers.Apply(nameServers => nameServers[2]),
            example.NameServers.Apply(nameServers => nameServers[3]),
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.route53.Zone;
import com.pulumi.aws.route53.ZoneArgs;
import com.pulumi.aws.route53.Record;
import com.pulumi.aws.route53.RecordArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Zone("example", ZoneArgs.builder()
            .name("test.example.com")
            .build());

        var exampleRecord = new Record("exampleRecord", RecordArgs.builder()
            .allowOverwrite(true)
            .name("test.example.com")
            .ttl(172800)
            .type("NS")
            .zoneId(example.zoneId())
            .records(            
                example.nameServers().applyValue(_nameServers -> _nameServers[0]),
                example.nameServers().applyValue(_nameServers -> _nameServers[1]),
                example.nameServers().applyValue(_nameServers -> _nameServers[2]),
                example.nameServers().applyValue(_nameServers -> _nameServers[3]))
            .build());

    }
}
resources:
  example:
    type: aws:route53:Zone
    properties:
      name: test.example.com
  exampleRecord:
    type: aws:route53:Record
    name: example
    properties:
      allowOverwrite: true
      name: test.example.com
      ttl: 172800
      type: NS
      zoneId: ${example.zoneId}
      records:
        - ${example.nameServers[0]}
        - ${example.nameServers[1]}
        - ${example.nameServers[2]}
        - ${example.nameServers[3]}

The allowOverwrite property permits this record to replace the auto-generated NS record. The nameServers output from the zone resource provides the values. This pattern works for both NS and SOA records, letting you customize zone-level DNS configuration in a single deployment.

Beyond these examples

These snippets focus on specific record-level features: simple A and CNAME records with TTL, weighted routing for traffic distribution, and alias records for AWS services. They’re intentionally minimal rather than full DNS configurations.

The examples may reference pre-existing infrastructure such as Route 53 hosted zones, and load balancers, accelerators, or other AWS services with DNS names. They focus on configuring the record rather than provisioning the surrounding infrastructure.

To keep things focused, common routing patterns are omitted, including:

  • Health checks and failover routing (healthCheckId, failoverRoutingPolicies)
  • Geographic routing (geolocationRoutingPolicies, geoproximityRoutingPolicy)
  • Latency-based routing (latencyRoutingPolicies)
  • CIDR-based routing (cidrRoutingPolicy)

These omissions are intentional: the goal is to illustrate how each record feature is wired, not provide drop-in DNS modules. See the Route 53 Record resource reference for all available configuration options.

Let's configure AWS Route 53 DNS Records

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Alias Records
What's the difference between alias and regular records?
Alias records point to AWS resources (like ELBs or CloudFront distributions) using the aliases property, while regular records use the records property for explicit values. You must use exactly one of these properties, never both.
Why can't I set TTL on my alias record?
Alias records have a fixed 60-second TTL that cannot be changed. You must omit the ttl property when creating alias records, as it conflicts with the aliases property.
How do I create an alias record for an AWS load balancer?
Use the aliases property with the load balancer’s dnsName and zoneId, and set evaluateTargetHealth as needed. Omit both ttl and records properties.
Routing Policies
Can I use multiple routing policies on the same record?
No, each record can only use one routing policy. All routing policy properties conflict with each other (weighted, geolocation, latency, failover, etc.).
When do I need to set a setIdentifier?
The setIdentifier property is required whenever you use any routing policy: cidrRoutingPolicy, failoverRoutingPolicy, geolocationRoutingPolicy, geoproximityRoutingPolicy, latencyRoutingPolicy, multivalueAnswerRoutingPolicy, or weightedRoutingPolicy.
How do I implement weighted routing between environments?
Create multiple records with the same name and type, each with different weightedRoutingPolicies weights and unique setIdentifier values (e.g., “dev” with weight 10, “live” with weight 90).
Record Configuration
What properties can't I change after creating a record?
The name, type, and zoneId properties are immutable. Changing any of these requires recreating the record.
How do I create a TXT record longer than 255 characters?
For TXT records exceeding 255 characters (like DKIM records), add \"\" inside the string to concatenate segments: "first255characters\"\"morecharacters".
Zone Management
How do I manage NS or SOA records without importing them first?
Set allowOverwrite: true on the record to manage zone-default NS and SOA records in a single deployment. This allows overwriting the automatically created records.
Should I use allowOverwrite for regular records?
No, allowOverwrite is not recommended for most environments. It’s primarily intended for managing NS and SOA records. The default value is false.

Using a different cloud?

Explore networking guides for other cloud providers: