All writing

Route 53 private hosted zones: DNS inside a VPC

Route 53 private hosted zones give selected resolver views a private DNS namespace. Their architecture depends on zone ownership, direct VPC associations or Route 53 Profiles, resolver-rule precedence, split-view behaviour, record design, caching, health, routing and operational evidence.

about 23 minutes min read

The bfstore production application needs to send telemetry to its self-hosted observability platform.

The observability service currently has this private address:

10.96.12.45

I could place that address directly in the application configuration:

telemetry:
  endpoint: 10.96.12.45:4317

That works until the service moves, a load balancer replaces it, recovery activates another destination or somebody must remember what 10.96.12.45 represents.

An address identifies a current network location.

The application needs a stable service identity:

otel.prod.aws.internal.bfstore.example

The name can point to:

  • an internal load balancer;
  • a PrivateLink interface endpoint;
  • one or more intentional private addresses;
  • another Route 53 record;
  • a failover destination.

The application keeps the service name.

DNS absorbs the change in location.

BFSTORE APPLICATION
        │
        │ query:
        │ otel.prod.aws.internal.bfstore.example
        ▼
ROUTE 53 VPC RESOLVER
        │
        ▼
PRIVATE DNS VIEW
        │
        ▼
PRIVATE SERVICE DESTINATION

A Route 53 private hosted zone contains records that Route 53 VPC Resolver can use to answer queries for a domain and its subdomains. The private answer is available through the resolver contexts to which the zone is distributed, traditionally by direct VPC association and now also through Route 53 Profiles. AWS describes the basic private hosted-zone model here.

A private hosted zone gives selected resolver views a private interpretation of a DNS namespace.

This article covers VPC DNS through direct associations, Profiles and Resolver rules and endpoints. Route 53 Global Resolver is outside scope. AWS documents it separately.

Private DNS is one part of private service access

Suppose bfstore creates:

prod.aws.internal.bfstore.example

with these records:

orders.prod.aws.internal.bfstore.example
payments.prod.aws.internal.bfstore.example
otel.prod.aws.internal.bfstore.example

The zone tells Route 53 how to answer those names.

It does not establish a route to the returned address.

If:

otel.prod.aws.internal.bfstore.example

resolves to:

10.96.12.45

the client still needs a network path to 10.96.12.45.

That path may use:

  • local VPC routing;
  • Transit Gateway;
  • VPC peering;
  • AWS PrivateLink;
  • another private network connection.

Security groups, network ACLs, firewalls and application authentication must also permit the connection.

Control Question
Private DNS Where should this name lead?
Route table Can the destination address be reached?
Network policy May this source establish the connection?
Application identity May this caller perform the operation?

DNS is not private access control. A strong design provides both a stable name and an intentional access path.

How the private namespace reaches a VPC

A private hosted zone must be associated with a VPC when it is created. More VPCs can be associated afterwards. The associated VPCs require enableDnsSupport and enableDnsHostnames to be enabled for private hosted-zone resolution. AWS documents those VPC requirements and the matching behaviour.

There are now two principal ways to distribute the namespace.

Direct hosted-zone association

The zone is associated directly with each consuming VPC.

PRIVATE HOSTED ZONE
prod.aws.internal.bfstore.example
            │
            ├── bfstore-prod VPC
            ├── bfstore-observability VPC
            └── bfstore-backup VPC

This model is explicit and works well when the VPC inventory is modest or when each association needs individual approval.

Route 53 Profile

A Route 53 Profile can collect DNS configuration and apply it to many VPCs, including VPCs in other accounts through AWS Resource Access Manager.

Profiles can distribute:

  • private hosted-zone associations;
  • Resolver rules;
  • DNS Firewall rule groups;
  • Resolver query-logging configurations.

A VPC can have one Profile association. When local VPC DNS configuration conflicts with Profile-delivered configuration, the local configuration takes precedence; the most-specific matching domain still determines the winner where names differ in specificity. AWS documents Profile resources and precedence here.

ROUTE 53 PROFILE
production-dns
        │
        ├── private hosted zones
        ├── Resolver rules
        ├── DNS Firewall policies
        └── query logging
                │
                ├── prod VPC
                ├── observability VPC
                └── backup VPC

A useful choice is:

Model Best fit
Direct association Smaller estate, explicit per-VPC control, exceptional namespace
Route 53 Profile Repeated DNS configuration across many VPCs and accounts

The association mechanism defines resolution scope: which resolver views receive the namespace. It does not authorise workloads to use the services behind the records.

Transit Gateway does not distribute DNS

Suppose the production and observability VPCs are attached to the same Transit Gateway.

The routes work:

BFSTORE PROD VPC
       │
       ▼
TRANSIT GATEWAY
       │
       ▼
OBSERVABILITY VPC

That does not automatically make a private hosted zone available to production.

Transit Gateway provides Layer 3 connectivity.

A direct zone association, Route 53 Profile or deliberate forwarding design determines whether the source resolver view receives the private answer.

TRANSIT GATEWAY

Can the returned address
be reached?


DNS CONFIGURATION

Can this resolver view
obtain the private answer?

Routing joins address spaces. DNS configuration joins naming views.

They must often be designed together, but they are not the same control plane.

Workloads query Route 53 VPC Resolver

Workloads do not normally query the private zone’s displayed name servers directly.

They send queries to Route 53 VPC Resolver, the recursive DNS service available in each VPC. VPC Resolver answers local VPC names, associated private hosted zones and public recursive queries, and applies matching Resolver rules. AWS describes the resolver path here.

WORKLOAD
   │
   ▼
ROUTE 53 VPC RESOLVER
   │
   ├── local VPC names
   ├── private hosted zones
   ├── Resolver rules
   └── public recursive resolution

The ordinary DHCP option set normally directs resources towards AmazonProvidedDNS. Custom first-hop DNS servers need conditional forwarding, commonly through Resolver inbound endpoints, for names AWS should answer.

How VPC Resolver chooses the winning answer

Most private DNS mistakes become easier to understand when resolution is treated as a selection process.

The resolver considers:

  1. local and Profile-delivered DNS configuration;
  2. matching Resolver rules;
  3. matching private hosted zones;
  4. namespace specificity;
  5. record presence;
  6. public recursion only when no private namespace or rule claims the query.

Split-view DNS

Route 53 can use public and private hosted zones with the same name.

PUBLIC ZONE
bfstore.example

PRIVATE ZONE
bfstore.example

An internet client receives the public view.

A client using VPC Resolver in a resolver view containing the private zone receives the private view.

INTERNET CLIENT
      │
      ▼
PUBLIC ZONE
      │
      ▼
PUBLIC ENDPOINT


VPC CLIENT
      │
      ▼
PRIVATE ZONE
      │
      ▼
PRIVATE ENDPOINT

The zones do not share or inherit records.

A matching private zone prevents public fallback

Suppose the public zone contains:

docs.bfstore.example

The production resolver view also contains a private zone:

bfstore.example

but that private zone does not contain the docs record.

VPC Resolver finds a matching private namespace, searches it and returns NXDOMAIN.

It does not retry against the public zone. AWS documents this behaviour explicitly.

QUERY
docs.bfstore.example
        │
        ▼
MATCHING PRIVATE ZONE
bfstore.example
        │
        ▼
RECORD ABSENT
        │
        ▼
NXDOMAIN

A broad same-name private zone claims authority over the complete branch for that resolver view.

If internal clients still need public records, bfstore must:

  • reproduce the required records privately;
  • use a narrower private namespace;
  • redesign forwarding;
  • avoid creating the overlapping private zone.

The most-specific namespace wins

A resolver view may contain:

internal.bfstore.example

prod.internal.bfstore.example

payments.prod.internal.bfstore.example

For:

authorise.payments.prod.internal.bfstore.example

the most-specific matching zone is:

payments.prod.internal.bfstore.example

VPC Resolver searches that zone.

If the record is absent, it does not continue upwards and search the less-specific zones for the same record. AWS documents most-specific matching for overlapping private zones.

This allows independent authority for narrower namespaces, but can also create accidental shadowing. Review a new, more-specific zone like a routing change.

The word “delegation” should be reserved for designs that actually use DNS delegation semantics. A separately associated child private hosted zone can become the winning authority without the parent zone containing an NS delegation.

A Resolver rule can override the hosted zone

Suppose a VPC has:

Private hosted zone:
corp.example

Forwarding rule:
corp.example
    → enterprise DNS

The forwarding rule takes precedence over the private hosted zone.

Resolver also chooses the most-specific matching rule when several rules apply. AWS documents rule selection and private-zone interactions here.

QUERY
service.corp.example
        │
        ▼
MATCHING FORWARD RULE
corp.example
        │
        ▼
ENTERPRISE DNS

A correct private record can remain unused because a rule intercepted the namespace. Troubleshooting must inspect both zones and rules.

Choose zone boundaries around ownership and audience

One possible design is a broad zone:

internal.bfstore.example

It could contain production, non-production and platform records.

That minimises the number of zones.

It also centralises every change beneath one authority.

A more separated design might use:

prod.aws.internal.bfstore.example

nonprod.aws.internal.bfstore.example

platform.aws.internal.bfstore.example

Separate a zone when the namespace needs a different owner, audience, IAM policy, change lifecycle or incident boundary. Production records should not normally share the everyday development DNS role.

The displayed hierarchy does not imply that the zones are automatically delegated to one another. Each zone becomes authoritative in resolver views where it is directly or indirectly associated, subject to the resolver’s selection rules.

Names should express durable service identity rather than incidental infrastructure.

Prefer:

orders.prod.aws.internal.bfstore.example

over:

orders-eks-cluster-3-subnet-a.prod...

The service may move between clusters without requiring every client to learn a new name.

Records should point to durable service boundaries

Private hosted zones support ordinary Route 53 record types and alias records.

Common bfstore patterns include:

Internal load balancer

orders.prod.aws.internal.bfstore.example
    → internal ALB or NLB alias
otel.prod.aws.internal.bfstore.example
    → interface endpoint alias

Database endpoint

mysql.orders.prod.aws.internal.bfstore.example
    → RDS endpoint through CNAME where appropriate

Intentional private address

legacy.prod.aws.internal.bfstore.example
    → 10.80.40.25

Alias records can target supported AWS resources and can be used at a zone apex. A CNAME cannot be used at the apex and cannot coexist with other data at the same owner name. AWS compares aliases and CNAME records here.

Use direct IP records only for intentionally stable, owned addresses, not as an inventory of ephemeral Kubernetes Pods.

KUBERNETES-INTERNAL DISCOVERY

use Kubernetes Service and cluster DNS


VPC OR CROSS-VPC BOUNDARY

consider Route 53 private DNS

Private hosted zones are strongest at durable service boundaries. Publishing a record does not grant permission to use the service.

DNS

discover the destination


NETWORK POLICY

permit the connection


SERVICE IDENTITY

authorise the operation

A useful internal name is a stable label, not an invitation.

TTL and alias records need different treatment

DNS resolvers cache ordinary record answers according to the record’s time to live.

RECORD CHANGED
      │
      ▼
ROUTE 53 HAS NEW ANSWER
      │
      ▼
CACHE MAY HOLD OLD ANSWER
      │
      ▼
TTL EXPIRES
      │
      ▼
CLIENT QUERIES AGAIN

Shorter TTLs make changes visible sooner; longer TTLs reduce query frequency. Alias records do not always expose a configurable TTL.

  • When an alias points to an AWS resource, Route 53 uses the target resource’s default TTL.
  • When an alias points to another record in the same hosted zone, Route 53 uses the target record’s TTL.
  • Ordinary non-alias records such as A, AAAA and CNAME records have an explicitly configured TTL.

AWS documents these alias TTL rules here.

A team cannot assume it can lower an ALB alias TTL before migration; the target controls it.

The change process should distinguish:

ROUTE 53 CHANGE STATUS

Has Route 53 applied the new record?


CACHE LIFETIME

How long may resolvers and clients
retain the previous answer?

INSYNC confirms the Route 53 change has propagated through Route 53.

It does not flush answers already cached by clients or recursive resolvers.

Health and failover depend on the target

Private hosted zones support failover and other routing policies.

The health source should match the target and the capability being protected.

Destination Possible health source Important limitation
ALB or NLB alias EvaluateTargetHealth Inherits supported load-balancer and target-group health
Private application endpoint CloudWatch alarm-backed Route 53 health check Alarm must represent the application capability
Publicly reachable endpoint Direct Route 53 endpoint health check Health checker must be able to reach it
Interface endpoint Application or synthetic metric Alias target health may not prove the provider service works

When EvaluateTargetHealth is enabled for a supported alias target, the alias inherits the health of the referenced AWS resource or record. For load balancers, Route 53 can use load-balancer and target-group health. AWS documents alias target health here.

This differs from a standalone Route 53 health check. Health checkers cannot directly monitor private, non-routable addresses, so private services can use a CloudWatch alarm-backed health check. AWS documents both points here. health-check-addresses

PRIVATE SERVICE
      │
      ▼
APPLICATION OR SYNTHETIC METRIC
      │
      ▼
CLOUDWATCH ALARM
      │
      ▼
ROUTE 53 HEALTH CHECK
      │
      ▼
FAILOVER DECISION

The signal should represent the capability the DNS decision is meant to protect.

An instance status check may prove that an instance is running.

It may not prove that:

  • the service can process an order;
  • its database is available;
  • the recovery endpoint is ready;
  • required credentials remain valid.

DNS failover affects future lookups.

It does not move established connections, and cached answers may remain in use until their TTL expires.

Private routing policies use AWS resolver context

Private hosted zones support several routing policies, including failover, weighted, latency and geolocation.

For private geolocation records, Route 53 uses the AWS Region of the originating VPC, not necessarily the human user’s location. Resolver endpoints can therefore shape the location Route 53 observes. AWS documents the private-zone behaviour here.

Before using private latency or geolocation routing, state explicitly:

Which resolver location does Route 53 see?

Does that location represent
the client population we intend to steer?

For many internal services, explicit regional names or application-level routing may be easier to reason about.

Multi-account ownership

bfstore may place the private hosted zone in bfstore-network while workload VPCs live in separate accounts.

For a direct cross-account association:

  1. the zone-owning account authorises one VPC association;
  2. the VPC-owning account completes the association;
  3. the unused authorisation should be removed.

The cross-account workflow requires the API, CLI, SDK or infrastructure code rather than the Route 53 console alone. AWS documents the handshake here.

ZONE ACCOUNT
     │
     ▼
AUTHORISE VPC
     │
     ▼
VPC ACCOUNT
     │
     ▼
ASSOCIATE ZONE

For repeated organisation-wide configuration, a shared Route 53 Profile may remove much of that per-zone, per-VPC association work.

Profiles are shared through AWS RAM, and updates to the Profile propagate to its associated VPCs. AWS documents Profile sharing here.

The choice should be intentional:

DIRECT ASSOCIATION

explicit exceptional relationship


ROUTE 53 PROFILE

repeatable resolver view
for a class of VPCs

DNS write access is data-path authority

Changing:

payments.prod.aws.internal.bfstore.example

can redirect payment traffic towards:

  • the wrong environment;
  • an obsolete endpoint;
  • a compromised destination;
  • a recovery service that is not ready.

DNS write permission is production data-path authority.

The change model should distinguish:

Role Capabilities
Zone administrator Create zones, associate resolver views, change ownership boundaries
Record administrator Change approved records, TTLs, routing and health associations
Read-only investigator Inspect zones, rules, Profiles, associations and change status

Route 53 IAM conditions can restrict changes by normalised record name, record type and change action, and can restrict zone associations to particular VPCs. AWS documents these fine-grained conditions here.

That supports a model such as:

APPLICATION TEAM

declares a service-name requirement


PLATFORM PIPELINE

validates the namespace and destination


ROUTE 53 ROLE

updates only approved names and types

The key question is who may redirect the name and where that decision is reviewed.

Logging and inventory must cover the resolver view

VPC Resolver query logging can record queries from associated VPCs and deliver them to CloudWatch Logs, S3 or Firehose.

Useful fields include:

  • VPC and source information;
  • queried name and record type;
  • response code;
  • returned answer;
  • DNS Firewall action.

The logs can identify clients using retired names, explain NXDOMAIN, show failover lookups and reveal unexpected domains.

VPC Resolver caches answers.

Query logging records unique queries that reach the resolver, not repeated queries answered from the resolver cache. AWS documents that limitation here.

ONE QUERY-LOG ENTRY

does not equal

ONE APPLICATION REQUEST

The inventory must also cover every way DNS configuration reaches the VPC:

resolver_view:
  vpc_id: vpc-example
  region: eu-west-2

private_zones:
  direct:
    - zone_id: Z123
      name: prod.aws.internal.bfstore.example

  profiles:
    - profile_id: rp-example
      private_zones:
        - platform.aws.internal.bfstore.example

resolver_rules:
  - domain: corp.example
    type: FORWARD

query_logging:
  destination: central-dns-logs

dns_firewall:
  policy: production-egress

endpoints:
  inbound: []
  outbound:
    - rslvr-out-example

This distinction matters because ListHostedZonesByVPC and GetHostedZone do not report private hosted-zone associations delivered through Route 53 Profiles. Profile resource-association APIs must also be queried. AWS documents the inventory limitation here.

The architecture must know which rule, association or Profile actually supplies the resolver view.

Quotas should inform, not dictate, the design

Route 53 applies quotas to hosted zones, records, associations, Profiles and other resources.

AWS currently documents:

  • 10,000 records per hosted zone by default;
  • 300 direct VPC associations per private hosted zone;
  • Route 53 Profiles as the recommended model when more associations are required.

Because quotas can change or be increased, runbooks should link to the current page. Current quotas are documented here.

The design tension remains:

TOO BROAD

one zone
many unrelated owners
large blast radius


TOO FRAGMENTED

one zone per record
large inventory
complex authority tree


USEFUL

one zone per meaningful
owner and audience boundary

The objective is not the fewest zones.

It is the clearest authority model.

A bfstore private DNS design

I would place private DNS governance in the bfstore-network account and use a combination of Route 53 Profiles and exceptional direct associations.

aws.internal.bfstore.example
│
├── prod.aws.internal.bfstore.example
├── nonprod.aws.internal.bfstore.example
└── platform.aws.internal.bfstore.example

These are separate authority zones, not automatically delegated children.

Production Profile

A bfstore-production-dns Profile could contain:

  • prod.aws.internal.bfstore.example;
  • approved platform-zone access;
  • central query logging;
  • standard DNS Firewall policy;
  • common enterprise forwarding rules.

It would be shared with production-class VPCs through AWS RAM.

Non-production Profile

A separate Profile could provide:

  • nonprod.aws.internal.bfstore.example;
  • development forwarding rules;
  • non-production logging and firewall policy.

Development and staging can split when their ownership diverges.

Exceptional direct associations

A narrow zone or rule can be associated directly when one VPC needs an exceptional resolver view.

Because local configuration overrides conflicting Profile configuration, the exception must be visible in inventory.

A conceptual record could look like:

private_hosted_zone:
  name: prod.aws.internal.bfstore.example
  owner_account: bfstore-network
  owner_team: network-platform

distribution:
  profile:
    name: bfstore-production-dns
    shared_with:
      - bfstore-prod
      - bfstore-observability
      - bfstore-backup

records:
  - name: otel.prod.aws.internal.bfstore.example
    type: A_ALIAS
    target: observability-ingest-nlb
    owner: observability-platform
    health:
      evaluate_target_health: true
    ttl:
      managed_by: alias-target

  - name: orders.prod.aws.internal.bfstore.example
    type: A_ALIAS
    target: internal-orders-alb
    owner: order-service
    health:
      evaluate_target_health: true
    ttl:
      managed_by: alias-target

controls:
  query_logging: central-dns-logs
  public_dnssec_validation: evaluated-separately
  record_changes: infrastructure-pipeline
  resolver_view_inventory: required

public_dnssec_validation is deliberately scoped.

VPC Resolver DNSSEC validation applies to recursively resolved signed public domains. It is not a mechanism that signs or authenticates records inside this private hosted zone. AWS documents VPC DNSSEC validation here.

Test from the consuming VPC

A hosted zone showing the correct record in the console does not prove that a client receives it.

Test from the resolver view that consumes the namespace.

dig otel.prod.aws.internal.bfstore.example A

dig missing.prod.aws.internal.bfstore.example A

getent hosts otel.prod.aws.internal.bfstore.example

Record the source VPC and Region, resolver configuration, VPC DNS attributes, direct and Profile associations, matching rules, winning namespace, effective TTL, route and application result.

NAME RESOLVES
      │
      ▼
ADDRESS RETURNED
      │
      ▼
ROUTE EXISTS
      │
      ▼
NETWORK POLICY PERMITS
      │
      ▼
SERVICE AUTHORISES

A successful answer proves only the first two steps; a failed connection does not automatically mean DNS is broken.

Common failure patterns

Public name works, VPC receives NXDOMAIN

Likely cause:

  • a matching private zone claims the namespace;
  • the record is absent privately;
  • Resolver does not fall back to public DNS.

Name resolves in production but not development

Likely cause:

  • the production resolver view includes the zone;
  • the development resolver view does not.

This may be intentional.

Correct record exists but is ignored

Likely causes:

  • a forwarding rule takes precedence;
  • local VPC configuration overrides a Profile;
  • a more-specific zone owns the branch;
  • the client uses a custom resolver without the required forwarding path.

Name resolves to the wrong environment

Likely causes:

  • overly broad association or Profile;
  • copied record points to staging;
  • local override conflicts with the shared Profile;
  • stale client or resolver cache.

Name resolves, connection times out

Likely causes:

  • missing route;
  • incorrect Transit Gateway propagation;
  • security-group or firewall denial;
  • return-path failure;
  • service not listening.

DNS may have succeeded.

Failover does not switch as expected

Likely causes:

  • the health source does not represent the application;
  • EvaluateTargetHealth is unsupported or ineffective for the target;
  • a CloudWatch-alarm health check is misconfigured;
  • the old answer remains cached;
  • existing connections do not repeat DNS resolution.

Inventory says the VPC has no zone, but it resolves

Likely cause:

  • the zone arrived through a Route 53 Profile;
  • the inventory queried only direct VPC associations.

Diagnose namespace selection, cache state, routing and service acceptance separately.

The decision model

The recurring questions are:

Which namespace are we claiming?

Which resolver views receive it?

Does a local setting, Profile or rule win?

Which zone is the most-specific authority?

What durable service boundary
does the record represent?

Who may redirect the name?

How is health evaluated?

What does the client cache?

Can the returned address be reached
and used by the caller?

The model is:

                       PRIVATE NAMESPACE
                               │
                               ▼
                      PRIVATE HOSTED ZONE
                               │
                 ┌─────────────┴─────────────┐
                 │                           │
                 ▼                           ▼
              RECORDS               RESOLUTION SCOPE
                                         │
                           ┌─────────────┴─────────────┐
                           │                           │
                           ▼                           ▼
                   DIRECT ASSOCIATION             PROFILE
                           │                           │
                           └─────────────┬─────────────┘
                                         ▼
                               VPC RESOLVER VIEW
                                         │
                         ┌───────────────┼───────────────┐
                         │               │               │
                         ▼               ▼               ▼
                    LOCAL OVERRIDE   RESOLVER RULE   PRIVATE ZONE
                         │               │               │
                         └───────────────┼───────────────┘
                                         ▼
                              MOST-SPECIFIC WINNER
                                         │
                                         ▼
                                   DNS RESPONSE
                                         │
                                         ▼
                                  CACHE AND TTL
                                         │
                                         ▼
                                  NETWORK ROUTE
                                         │
                                         ▼
                               NETWORK AUTHORISATION
                                         │
                                         ▼
                              APPLICATION AUTHORISATION

A private hosted zone creates private meaning, not private infrastructure.

The zone defines authority. Direct associations and Profiles define the resolver views. Rules can redirect the question, and the most-specific namespace decides which authority wins. Alias targets may control caching, while health determines which future answers remain eligible.

The network carries the resulting packet. Network and application controls decide whether the request should succeed.

DNS lets the architecture move without requiring every application to memorise its current address.

References and further reading

Reviewed against official AWS documentation on 31 July 2026.