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 three capabilities: basic A and CNAME records, weighted routing for traffic distribution, and alias records for AWS services.

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

Point a domain to an IP address

Most DNS configurations start with simple A records that map domain names to IP addresses.

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 resolvers cache the response (300 seconds here). This is standard DNS resolution: clients query the name and receive the IP.

Split traffic between environments with weighted routing

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

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

Each record with the same name and type needs a unique setIdentifier. The weightedRoutingPolicies block assigns a weight; Route 53 distributes traffic proportionally (10% to dev, 90% to live in this case). Both records must exist for weighted routing to work.

Create alias records for AWS load balancers

AWS services like ELB expose DNS names that change over time. Alias records point your domain to these services without hardcoding IPs.

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. The name and zoneId come from the target service (here, an ELB). Route 53 automatically tracks the underlying IPs. The evaluateTargetHealth property determines whether Route 53 checks the target’s health before routing traffic. Alias records have a fixed 60-second TTL; you cannot set ttl explicitly.

Manage NS records for a hosted zone

Route 53 automatically creates NS and SOA records when you create a hosted zone. The allowOverwrite property lets you customize them without importing first.

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 replacing the auto-generated NS record. The records array references the zone’s nameServers output. This pattern is useful when you need to manage NS records alongside zone creation, but it’s not recommended for most environments since Route 53 handles these records automatically.

Beyond these examples

These snippets focus on specific record-level features: basic A/CNAME records with TTL, weighted routing for traffic splitting, 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 record 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 & TTL
What's the difference between alias and non-alias records?
Alias records point to AWS resources (like ELBs or CloudFront distributions) using the aliases property, while non-alias records use the records property with IP addresses or domain names. You must specify exactly one of aliases or records, not both.
What's the TTL for alias records?
Alias records have a fixed TTL of 60 seconds that cannot be changed. You must omit the ttl property when creating alias records.
How do I create an alias record pointing to an ELB or Global Accelerator?
Use the aliases property with the resource’s dnsName and zoneId (or hostedZoneId for Global Accelerator), set evaluateTargetHealth as needed, and omit the ttl property.
Routing Policies
What routing policies are available and can I combine them?
Available routing policies include weighted, geolocation, geoproximity, latency, failover, CIDR, and multivalue answer. Each routing policy conflicts with all others, so you can only use one per record.
When is setIdentifier required?
setIdentifier is required when using any routing policy: cidrRoutingPolicy, failoverRoutingPolicy, geolocationRoutingPolicy, geoproximityRoutingPolicy, latencyRoutingPolicy, multivalueAnswerRoutingPolicy, or weightedRoutingPolicy.
How do I set up weighted routing between multiple environments?
Create multiple records with the same name and type, each with a unique setIdentifier and different weights in weightedRoutingPolicies. For example, assign weight 10 to dev and weight 90 to live.
Record Configuration
What properties can't I change after creating a record?
The name, type, and zoneId properties are immutable and cannot be changed after creation.
How do I create a TXT record longer than 255 characters?
Add \"\" inside the configuration string at the 255-character boundary. For example: "first255characters\"\"morecharacters".
NS & SOA Management
How do I manage NS or SOA records without importing them?
Set allowOverwrite to true to manage the auto-created NS and SOA records in a single deployment. However, this configuration is not recommended for most environments.
Should I use allowOverwrite in production?
No, allowOverwrite is not recommended for most environments as it can lead to unintended record overwrites. Use it only for specific cases like managing auto-created NS/SOA records.

Using a different cloud?

Explore networking guides for other cloud providers: