Skip to content

Route53

Route53 emulation supporting hosted zones, reusable delegation sets, resource record sets, health checks, change tracking, and tagging. Private hosted zones provide embedded DNS resolution for cluster containers such as EKS and workloads wired to Floci's embedded DNS server.

Supported Operations

Action Description
CreateReusableDelegationSet POST /2013-04-01/delegationset. Optionally promotes an existing public zone with HostedZoneId.
GetReusableDelegationSet GET /2013-04-01/delegationset/{Id}
DeleteReusableDelegationSet DELETE /2013-04-01/delegationset/{Id}. Refuses to delete a set in use by a zone.
ListReusableDelegationSets GET /2013-04-01/delegationset. Paginates with marker / maxitems.
CreateHostedZone POST /2013-04-01/hostedzone. A <VPC> element in the request creates a private zone and records the first VPC association.
GetHostedZone GET /2013-04-01/hostedzone/{Id}. Private zones include their <VPCs> list.
UpdateHostedZoneComment POST /2013-04-01/hostedzone/{Id}
DeleteHostedZone DELETE /2013-04-01/hostedzone/{Id}
ListHostedZones GET /2013-04-01/hostedzone
ListHostedZonesByName GET /2013-04-01/hostedzonesbyname
AssociateVPCWithHostedZone POST /2013-04-01/hostedzone/{Id}/associatevpc. Private zones only; cross-account associations require prior authorization.
DisassociateVPCFromHostedZone POST /2013-04-01/hostedzone/{Id}/disassociatevpc. Refuses to remove the last association (LastVPCAssociation).
CreateVPCAssociationAuthorization POST /2013-04-01/hostedzone/{Id}/authorizevpcassociation. Must be called by the hosted-zone owner.
DeleteVPCAssociationAuthorization POST /2013-04-01/hostedzone/{Id}/deauthorizevpcassociation. Must be called by the hosted-zone owner; success has an empty response body.
ListVPCAssociationAuthorizations GET /2013-04-01/hostedzone/{Id}/authorizevpcassociation. Paginates with maxresults / nexttoken.
ListHostedZonesByVPC GET /2013-04-01/hostedzonesbyvpc?vpcid=…&vpcregion=…. Paginates with maxitems / nexttoken.
GetHostedZoneCount GET /2013-04-01/hostedzonecount
ChangeResourceRecordSets POST /2013-04-01/hostedzone/{Id}/rrset
ListResourceRecordSets GET /2013-04-01/hostedzone/{Id}/rrset
GetChange GET /2013-04-01/change/{Id}
CreateHealthCheck POST /2013-04-01/healthcheck
GetHealthCheck GET /2013-04-01/healthcheck/{HealthCheckId}
DeleteHealthCheck DELETE /2013-04-01/healthcheck/{HealthCheckId}
ListHealthChecks GET /2013-04-01/healthcheck
UpdateHealthCheck POST /2013-04-01/healthcheck/{HealthCheckId}
ListTagsForResource GET /2013-04-01/tags/{ResourceType}/{ResourceId}
ChangeTagsForResource POST /2013-04-01/tags/{ResourceType}/{ResourceId}
GetAccountLimit GET /2013-04-01/accountlimit/{Type}
GetHealthCheckStatus GET /2013-04-01/healthcheck/{HealthCheckId}/status
GetDNSSEC GET /2013-04-01/hostedzone/{Id}/dnssec. Always reports signing as NOT_SIGNING.
GetHostedZoneLimit GET /2013-04-01/hostedzonelimit/{HostedZoneId}/{Type}

Behavior

  • All changes return status INSYNC immediately (no async propagation simulation).
  • Every new hosted zone automatically gets SOA and NS records at the zone apex. These records cannot be deleted.
  • DeleteHostedZone fails with HostedZoneNotEmpty if the zone contains records other than the apex SOA and NS.
  • ChangeResourceRecordSets validates all changes atomically before applying any.
  • Supported change actions: CREATE, UPSERT, DELETE.
  • Alias deletion treats target DNS names without regard to ASCII letter case and accepts an optional trailing dot. The target hosted-zone ID, health-evaluation setting, and other record values must still match; a rejected change batch does not apply its other changes.
  • Hosted zone IDs are returned with the /hostedzone/ prefix in XML responses (e.g. /hostedzone/Z1PA6795UKMFR9). The AWS SDK strips this prefix client-side.
  • Health check IDs are plain UUIDs without a prefix.
  • CreateHealthCheck with a CallerReference already in use returns that health check when the settings match, so an SDK retry succeeds, and fails with HealthCheckAlreadyExists when they differ. A deleted health check's reference can be reused at once; AWS keeps refusing it for some days.
  • Tags are supported for both hostedzone and healthcheck resource types.
  • A hosted zone is private when CreateHostedZone carries a <VPC> element; HostedZoneConfig.PrivateZone is response-only.
  • AssociateVPCWithHostedZone rejects public zones with PublicZoneVPCAssociation, and DisassociateVPCFromHostedZone rejects removing the last VPC with LastVPCAssociation.
  • Associating a VPC that is already attached to another zone with the same name fails with ConflictingDomainExists.
  • Cross-account association follows the Route 53 authorization lifecycle: the hosted-zone owner authorizes the VPC, the VPC account associates it, and the hosted-zone owner can then delete the authorization without removing the association.
  • A cross-account association without a matching authorization fails with NotAuthorizedException.
  • Private hosted zones support up to 300 VPC associations and up to 1000 outstanding cross-account VPC association authorizations, matching the documented Route 53 default quotas.
  • Set FLOCI_SERVICES_ROUTE53_VPC_ASSOCIATION_CONTROL_PLANE_DELAY_MS above 0 to emulate a short in-flight control-plane window for retry testing. During that window, a subsequent AssociateVPCWithHostedZone for the same hosted zone returns PriorRequestNotComplete; overlapping create/delete authorization requests return ConcurrentModification. These are the retryable overlap errors documented by Route 53 for those operations.

Private Hosted Zone DNS Resolution

Floci resolves private hosted zone DNS records directly through its embedded DNS server on port 53. Cluster containers (such as EKS k3s containers) and workloads wired to embedded DNS automatically query Floci as their primary resolver.

  • Record Types: Resolves IPv4 A records (including CNAME and ALIAS target chasing across private zones, EC2 internal names, and external hostnames). When a private CNAME targets a public hostname, Floci resolves the target addresses off the event loop so container stub resolvers succeed. Multiple values are returned as separate answers. Simple private SRV record sets also resolve, preserving each value's priority, weight (including zero), port, target, and the record set TTL. The target . encodes an unavailable service. Malformed SRV values and SRV sets requiring routing policies or health evaluation return REFUSED; SRV CNAME chasing is not implemented.
  • TTL and Negative Answers: Answers carry the record set's TTL. A chased CNAME chain is flattened to A records, so it answers with the shortest TTL in the chain; an alias answers with its target's TTL, and an alias or CNAME to a name outside the private zones answers with at most 60 seconds, since Floci cannot see that target's TTL. A name absent from the zone receives NXDOMAIN; a name that exists without the requested record type (only TXT, for example, or only records beneath it) receives NOERROR with no answers. A private CNAME chain preserves that distinction from its target, including after the target is created or deleted. A wildcard record such as *.example.internal answers only when neither the name nor an ancestor closer than the wildcard exists, so a record at api.team.example.internal keeps the wildcard from answering for team.example.internal or anything beneath it.
  • Zone Visibility and Fallthrough: Private hosted zones (created with a VPC association) provide IPv4 and SRV resolution. Public zones support the authoritative record types described below; unowned external domains fall through to upstream forwarders. Because the standard DNS wire protocol carries no caller VPC identifier, Floci resolves across private zones without filtering by account or VPC ID.
  • Resolver Rule Precedence: A Route 53 Resolver rule associated with the querying container's VPC takes precedence over a private hosted zone that matches the same name, as it does in AWS: the query is forwarded to the rule's targets instead of answered from the zone. See Route 53 Resolver.
  • Zero-Restart Propagation: Record changes via ChangeResourceRecordSets take effect immediately without restarts.
  • Kubernetes and external-dns Compatibility: In-cluster controllers like external-dns provisioning Route 53 private records for Services or Ingresses are immediately resolvable.

Public Authoritative DNS

The same embedded UDP server serves stored public TXT, NS, and SOA records with authoritative answers and their configured TTLs. DNS clients must be able to reach Floci on UDP port 53. The HTTP endpoint published by Testcontainers wrappers does not publish this UDP listener. Host clients need an explicit UDP port mapping; containers on the same network can query Floci's container address directly.

For a Docker host client, publish UDP separately from HTTP, for example -p 5353:53/udp, then query dig @127.0.0.1 -p 5353 example.com TXT. Configure the equivalent UDP binding explicitly when using a Testcontainers wrapper; its standard HTTP port mapping is insufficient.

The longest matching public zone answers. Missing names return NXDOMAIN; existing names without the requested supported type return NOERROR with no answers. Both include the zone SOA with the smaller of its TTL and minimum field for negative caching. Changes and deletes affect DNS through the same stored record sets as the Route 53 API.

Public address routing, aliases, CNAME chasing, health-based or weighted record selection, and delegation referrals are not implemented. Queries for supported types that require these features, ambiguous duplicate public zones, or malformed stored records return REFUSED without claiming authority. Other query types retain the existing emulator-name and upstream resolution paths. Private-zone and VPC forwarding-rule precedence remains unchanged. UDP responses honor legacy/EDNS size limits and set truncation when necessary; TCP fallback is not implemented.

Default Nameservers

By default, new public zones without a reusable delegation set and new standalone reusable sets receive four nameservers shaped like ns-2048.awsdns-64.com. Floci derives their numbers from each resource ID and uses the org, net, com, and co.uk TLDs. An occasional server name can overlap between resources, as with Route 53. With no explicit nameserver overrides, private zones keep these names:

ns-1.awsdns-01.org
ns-2.awsdns-02.net
ns-3.awsdns-03.com
ns-4.awsdns-04.co.uk

Set any floci.services.route53.default-nameserver* value to use that exact name for its slot in new zones without a reusable set and in new standalone sets, including when the configured value equals the former default shown above. Zones using a reusable set keep that set's names. A configured name can have or omit its final dot. The delegation set lists names without the final dot, as Route 53 does. A new zone's apex NS records, and the primary server named in its SOA record (the first nameserver), use the fully qualified form with the dot.

For zones without a reusable delegation set, both the assigned servers returned by CreateHostedZone and GetHostedZone and the apex NS and SOA records remain stable after the configuration changes. Update the records with an UPSERT change if needed.

Reusable Delegation Sets

CreateReusableDelegationSet, GetReusableDelegationSet, ListReusableDelegationSets, and DeleteReusableDelegationSet implement the APIs used by the aws_route53_delegation_set Terraform resource. Sets are owned by the calling account and shared across regions. A duplicate live CallerReference returns DelegationSetAlreadyCreated. Listing uses marker / maxitems, returns at most 100 sets, and identifies the first set of the next page with NextMarker.

The four delegation-set operations honor IAM policies when enforcement is enabled. Get and delete authorize the selected set's global ARN; create and list require wildcard resources.

Pass delegationsetid to ListHostedZones to filter by a reusable set before pagination. An unknown set, including one owned by another account, returns NoSuchDelegationSet.

Pass DelegationSetId to CreateHostedZone to reuse a set. The create/get responses include its ID, caller reference, and name servers; the new zone's apex NS records and SOA primary server use those same servers. Passing HostedZoneId to CreateReusableDelegationSet marks an existing public zone's delegation set as reusable. Private zones cannot use reusable sets. Deleting a set associated with any hosted zone returns DelegationSetInUse; delete its zones first. The default quotas are 100 sets per account and 100 hosted zones per set.

A new standalone set keeps its assigned four nameservers. Promotion reuses the zone's original assignment, even after configuration or apex NS changes. Older stored zones without a separate assignment recover it from their four apex NS entries, falling back to current settings when those entries are unavailable. Its servers and zone associations persist through the configured Route53 storage backend and remain stable after configuration changes. Requests selecting a reusable set reject equal, parent, or child public-zone names only when they share a nameserver with an existing public zone (ConflictingDomainExists). Sibling domains can share a set. This feature manages delegation metadata and apex records; public DNS delegation referrals remain unsupported.

set_id=$(aws route53 create-reusable-delegation-set \
  --caller-reference local-delegation --query DelegationSet.Id --output text)
aws route53 create-hosted-zone \
  --name first.example.com --caller-reference first-zone \
  --delegation-set-id "$set_id"
aws route53 create-hosted-zone \
  --name second.example.com --caller-reference second-zone \
  --delegation-set-id "$set_id"

Configuration

Variable Default Description
FLOCI_SERVICES_ROUTE53_ENABLED true Enable or disable the service
FLOCI_SERVICES_ROUTE53_DEFAULT_NAMESERVER1 unset Exact override for the first assigned nameserver
FLOCI_SERVICES_ROUTE53_DEFAULT_NAMESERVER2 unset Exact override for the second assigned nameserver
FLOCI_SERVICES_ROUTE53_DEFAULT_NAMESERVER3 unset Exact override for the third assigned nameserver
FLOCI_SERVICES_ROUTE53_DEFAULT_NAMESERVER4 unset Exact override for the fourth assigned nameserver
FLOCI_SERVICES_ROUTE53_VPC_ASSOCIATION_CONTROL_PLANE_DELAY_MS 0 Optional VPC association/auth processing window used to reproduce documented retryable overlap errors

CLI Examples

export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test

# Create a hosted zone
aws route53 create-hosted-zone \
  --name example.com \
  --caller-reference "$(date +%s)"

# List hosted zones
aws route53 list-hosted-zones

# Add an A record
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1PA6795UKMFR9 \
  --change-batch '{
    "Changes": [{
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "www.example.com.",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{"Value": "1.2.3.4"}]
      }
    }]
  }'

# List records
aws route53 list-resource-record-sets --hosted-zone-id Z1PA6795UKMFR9

# Create a health check
aws route53 create-health-check \
  --caller-reference "hc-$(date +%s)" \
  --health-check-config '{
    "Type": "HTTPS",
    "FullyQualifiedDomainName": "example.com",
    "Port": 443,
    "ResourcePath": "/health"
  }'

# Create a private hosted zone attached to a VPC
aws route53 create-hosted-zone \
  --name internal.example.com \
  --caller-reference "$(date +%s)" \
  --vpc VPCRegion=us-east-1,VPCId=vpc-0123456789abcdef0

# Attach a second VPC to the private zone
aws route53 associate-vpc-with-hosted-zone \
  --hosted-zone-id Z1PA6795UKMFR9 \
  --vpc VPCRegion=us-east-1,VPCId=vpc-0fedcba9876543210

# List the private zones attached to a VPC
aws route53 list-hosted-zones-by-vpc \
  --vpc-id vpc-0fedcba9876543210 --vpc-region us-east-1

# Detach a VPC (the last association cannot be removed)
aws route53 disassociate-vpc-from-hosted-zone \
  --hosted-zone-id Z1PA6795UKMFR9 \
  --vpc VPCRegion=us-east-1,VPCId=vpc-0fedcba9876543210

# Delete a hosted zone
aws route53 delete-hosted-zone --id Z1PA6795UKMFR9

Record names in change batches are stored with lowercase ASCII letters and a trailing dot. CREATE, UPSERT, and DELETE use that name identity while keeping the record type and routing set identifier distinct. A case-only CloudFormation record-name update retains the record. DELETE still requires matching record values and routing properties.

Not Supported (Phase 2)

  • Traffic policies and traffic policy instances
  • Query logging configs
  • DNSSEC (key signing keys, enabling/disabling)
  • TestDNSAnswer
  • Public authoritative record types other than simple TXT, NS, and SOA; public routing policies, delegation referrals, and DNS-over-TCP