Introduction

ExternalDNS automatically creates DNS records in Route53 for Kubernetes Ingress and Service resources. When it stops updating, new services don't get DNS entries, and removed services leave stale DNS records pointing to non-existent endpoints.

Symptoms

DNS records not created:

```bash $ kubectl get ingress my-ingress

NAME CLASS HOSTS ADDRESS PORTS AGE my-ingress alb * 80 10m

$ dig my-app.example.com

;; ANSWER SECTION: # No record or old IP ```

ExternalDNS logs showing errors:

```bash $ kubectl logs -n external-dns deployment/external-dns

E0115 10:00:00.000000 controller.go:123] failed to create records: AccessDenied: User is not authorized ```

Service annotations ignored:

```bash $ kubectl get svc my-service -o yaml | grep external-dns

# Annotations present but no DNS record created ```

Common Causes

  1. 1.IAM permissions missing - Can't access Route53
  2. 2.Service account not annotated - IRSA not configured
  3. 3.Hosted zone not found - Zone ID incorrect or not accessible
  4. 4.Annotation format wrong - Incorrect annotation syntax
  5. 5.Domain filter mismatch - Domain doesn't match hosted zone
  6. 6.RBAC issues - Service account can't watch resources
  7. 7.ExternalDNS not running - Deployment issues

Step-by-Step Fix

  1. 1.Check logs for specific error messages
  2. 2.Verify configuration settings
  3. 3.Test network connectivity
  4. 4.Review recent changes
  5. 5.Apply corrective action
  6. 6.Verify the fix

Step 1: Check ExternalDNS Pod Status

```bash # Check if ExternalDNS is running kubectl get pods -n external-dns

# Check logs kubectl logs -n external-dns deployment/external-dns --tail=100

# Look for errors: # - AccessDenied: IAM permissions issue # - NoSuchHostedZone: Zone not found # - InvalidChangeBatch: DNS record format issue ```

Step 2: Verify IAM Permissions

```bash # Get service account annotation kubectl get sa external-dns -n external-dns -o yaml | grep eks.amazonaws.com/role-arn

# Check role policy aws iam get-role-policy --role-name ExternalDNSRole --policy-name Route53Access

# Required permissions for Route53: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets" ], "Resource": "arn:aws:route53:::hostedzone/HOSTED_ZONE_ID" }, { "Effect": "Allow", "Action": [ "route53:ListHostedZones" ], "Resource": "*" } ] } ```

Add missing permissions:

bash
aws iam put-role-policy \
  --role-name ExternalDNSRole \
  --policy-name Route53Access \
  --policy-document file://route53-policy.json

Step 3: Check OIDC Provider

```bash # Verify OIDC provider exists aws iam list-open-id-connect-providers \ --query 'OpenIDConnectProviderList[*].Arn'

# Get cluster OIDC issuer aws eks describe-cluster --name my-cluster \ --query 'cluster.identity.oidc.issuer'

# Verify trust policy matches aws iam get-role --role-name ExternalDNSRole \ --query 'Role.AssumeRolePolicyDocument' ```

Step 4: Verify Hosted Zone Access

```bash # List hosted zones aws route53 list-hosted-zones \ --query 'HostedZones[*].[Id,Name]'

# Get specific zone aws route53 get-hosted-zone --id Z1234567890ABC

# Check if zone matches domain filter # In ExternalDNS args: --domain-filter=example.com ```

Step 5: Check Service/Ingress Annotations

```bash # For Service type LoadBalancer kubectl get svc my-service -o yaml

# Required annotations: annotations: external-dns.alpha.kubernetes.io/hostname: my-app.example.com

# For Ingress kubectl get ingress my-ingress -o yaml

# Host in spec.rules automatically creates DNS spec: rules: - host: my-app.example.com http: paths: ... ```

Step 6: Verify ExternalDNS Configuration

```bash # Check ExternalDNS deployment args kubectl get deployment external-dns -n external-dns -o yaml | grep args -A 20

# Key arguments: # --source=service,ingress # --domain-filter=example.com # --provider=aws # --policy=upsert-only # or sync (deletes records) # --registry=txt # --txt-owner-id=my-cluster ```

Step 7: Test DNS Record Creation

```bash # Create test service with annotation kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: test-service annotations: external-dns.alpha.kubernetes.io/hostname: test.example.com spec: type: LoadBalancer ports: - port: 80 targetPort: 80 selector: app: test EOF

# Watch ExternalDNS logs kubectl logs -f -n external-dns deployment/external-dns

# Check Route53 aws route53 list-resource-record-sets \ --hosted-zone-id Z1234567890ABC \ --query 'ResourceRecordSets[?Name==test.example.com.]' ```

Step 8: Debug RBAC Issues

```bash # Check ClusterRole kubectl get clusterrole external-dns -o yaml

# Required permissions: rules: - apiGroups: [""] resources: ["services", "pods", "nodes", "endpoints"] verbs: ["get", "list", "watch"] - apiGroups: ["extensions", "networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "list", "watch"]

# Check ClusterRoleBinding kubectl get clusterrolebinding external-dns -o yaml

# Should bind to correct service account subjects: - kind: ServiceAccount name: external-dns namespace: external-dns ```

Step 9: Check for Record Conflicts

bash # ExternalDNS uses TXT records for ownership # Check for existing TXT records aws route53 list-resource-record-sets \ --hosted-zone-id Z1234567890ABC \ --query 'ResourceRecordSets[?Type==TXT`]'

# If ownership conflict, update txt-owner-id kubectl set env deployment/external-dns -n external-dns \ TXT_OWNER_ID=my-cluster-name ```

Step 10: Enable Debug Logging

```bash # Increase log level kubectl set env deployment/external-dns -n external-dns \ LOG_LEVEL=debug

# Watch logs for detailed information kubectl logs -f -n external-dns deployment/external-dns

# Look for: # - "Creating records" messages # - API calls to Route53 # - Error responses from AWS ```

ExternalDNS Common Arguments

ArgumentPurposeExample
--sourceResource types to watchservice,ingress
--providerDNS provideraws,google,cloudflare
--domain-filterLimit to domainsexample.com
--policySync behaviorupsert-only, sync
--registryOwnership trackingtxt
--txt-owner-idOwner identifiermy-cluster
--aws-zone-typeZone visibilitypublic,private

Verification

bash # Check DNS record created aws route53 list-resource-record-sets \ --hosted-zone-id Z1234567890ABC \ --query 'ResourceRecordSets[?contains(Name, my-app`)]'

# Test DNS resolution dig my-app.example.com

# Should return the LoadBalancer IP

# Check ExternalDNS logs for success kubectl logs -n external-dns deployment/external-dns | grep -i "created" ```

  • [Fix AWS EKS Load Balancer Controller Failed](/articles/fix-aws-eks-load-balancer-controller-failed)
  • [Fix AWS EKS IAM Role for Service Account](/articles/fix-aws-eks-iam-role-for-service-account)
  • [Fix DNS Resolution Failure](/articles/fix-dns-resolution-failure)
  • [AWS troubleshooting: Fix IAM Permission Denied - Complete Tro](fix-iam-permission-denied)
  • [AWS cloud troubleshooting: AWS ACM Certificate Pending Validation Because the](aws-acm-certificate-pending-validation-wrong-route53-zone)
  • [AWS cloud troubleshooting: AWS ALB Returns 502 Because the Target Closed the ](aws-alb-502-target-closed-connection-keepalive-timeout-mismatch)
  • [AWS cloud troubleshooting: Fix AWS ALB CreateListener TargetGroupNotFound Err](aws-alb-createlistener-targetgroupnotfound)
  • [AWS cloud troubleshooting: Fix Aws Alb Lambda 502 Bad Gateway Issue in AWS](aws-alb-lambda-502-bad-gateway)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Fix AWS EKS ExternalDNS Not Updating Route53", "description": "Troubleshoot ExternalDNS Route53 issues. Fix IAM permissions, service accounts, and DNS record configurations.", "url": "https://www.fixwikihub.com/fix-aws-eks-external-dns-not-updating", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-04-02T01:45:44.258Z", "dateModified": "2026-04-02T01:45:44.258Z" } </script>