{"id":42753861,"url":"https://github.com/danielkneipp/aws-route53-global-dns","last_synced_at":"2026-01-29T20:03:55.491Z","repository":{"id":218101535,"uuid":"743251528","full_name":"DanielKneipp/aws-route53-global-dns","owner":"DanielKneipp","description":"Have your service with a global endpoint highly available and segmented by region based on response times","archived":false,"fork":false,"pushed_at":"2024-01-24T19:33:23.000Z","size":3334,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-01-24T19:42:07.737Z","etag":null,"topics":["aws","aws-ec2","aws-nlb","aws-route53"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DanielKneipp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2024-01-14T19:23:19.000Z","updated_at":"2024-01-20T12:55:28.000Z","dependencies_parsed_at":"2024-01-24T19:52:28.218Z","dependency_job_id":null,"html_url":"https://github.com/DanielKneipp/aws-route53-global-dns","commit_stats":null,"previous_names":["danielkneipp/aws-route53-global-dns"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/DanielKneipp/aws-route53-global-dns","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DanielKneipp%2Faws-route53-global-dns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DanielKneipp%2Faws-route53-global-dns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DanielKneipp%2Faws-route53-global-dns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DanielKneipp%2Faws-route53-global-dns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DanielKneipp","download_url":"https://codeload.github.com/DanielKneipp/aws-route53-global-dns/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DanielKneipp%2Faws-route53-global-dns/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28884016,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-29T19:55:09.949Z","status":"ssl_error","status_checked_at":"2026-01-29T19:55:08.490Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-ec2","aws-nlb","aws-route53"],"created_at":"2026-01-29T20:03:54.128Z","updated_at":"2026-01-29T20:03:55.482Z","avatar_url":"https://github.com/DanielKneipp.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Global Endpoint For a Multi-region Service\n\nTo have a highly available web application requires several components working together. Automated health checks, failover mechanisms, backup infrastructure, you name it. And when it comes to keeping response times low globally, the complexity increases even further.\n\nThis project presents progressively how to go from a simple application available on the internet to a full-fledged setup with automated failover, together with user traffic segmented per region to keep response times low.\n\n## Progressive Architecture\n\nHere we have three different infrastructure designs of the same service with different levels of complexity.\n\nThe first one is a simple web server running on an EC2 machine.\n\nThe second is a more reliable design, with another web server running in passive mode. This second web server would only respond to traffic in case the first one becomes inoperable.\n\nFinally, the last one replicates the previous design in a different region, to provide the same service to a customer base located in a geographically distance place than the previous setup.\n\n### As simple as it gets\n\nThis design, as the section implies, is as simple as it gets. Essentially, we have just a two-tier application running. We have three main components:\n\n- A DNS record pointing to an [AWS Network Load Balancer (NLB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html)\n- This NLB is deployed on the public subnet to receive traffic from the internet. This first layer/tier allows the web server to not be publicly exposed to the internet, as well as load balancing between multiple servers in case more were available\n- Finally, we have the web server itself receiving traffic only from the NLB on a private subnet\n\n![Simple Architecture](./docs/general-diagram-simple.png)\n\nEverything is deployed on the same [availability zone (AZ)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones), which represents one or more AWS data centers geographically close to each other. This is to ensure fast communication between the load balancer and the web server. This also avoids [costs related to data transfer between AZs on AWS](https://aws.amazon.com/blogs/architecture/overview-of-data-transfer-costs-for-common-architectures/).\n\nThe obvious problem with this design is that if the web server becomes out of service, or if the AZ becomes inoperable, the application stops responding to user requests. To overcome this, we will move to the next design.\n\n### Going active-passive\n\nHere we replicate the same infrastructure in a different AZ. The key difference is that this secondary deployment should not receive user traffic unless the primary one stops working. This is also known as an [active-passive deployment](ttps://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-failover-types.html#dns-failover-types-active-passive-one-resource).\n\n![Highly Available Architecture](docs/general-diagram-failover.png)\n\nIn this scenario we use AWS Route53 records of type [`failover`](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-failover-types.html#dns-failover-types-active-passive-one-resource). Those records work in pairs of primary and secondary records. As the name implies, the primary record should point to the server that responds to traffic under normal circumstances.\n\nThe secondary DNS record will only take precedence if the primary server is not working properly, or, in other words, it is unhealthy.\n\n*And how can a server be detected as unhealthy?* Using [health checks](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-health-checks.html). Those can be configured in a way that the load balancer will perform regular checks to confirm if the web server is working properly.\n\nIf those checks fail above a certain threshold, the target (the ec2 instance is labeled as unhealthy). If a certain amount of targets are considered unhealthy, the endpoint of the load balancer itself is considered unhealthy.\n\nThis status is reported to Route53 (when using records of type `alias`), which uses this information to decide if the DNS requests should be resolved to the secondary load balancer instead, consequently, performing the failover.\n\n\u003e ℹ️ We are also making use of making use of the [Availability Zone Affinity](https://aws.amazon.com/blogs/architecture/improving-performance-and-reducing-cost-using-availability-zone-affinity/) architectural pattern to, as mentioned previously, improve response times and reduce [costs](ttps://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer_within_the_same_AWS_Region). This way, traffic that reaches an AZ never leaves it.\n\nSo, now we have a more reliable design with an automated failover mechanism between distinct data centers. However, as a single-region deployment, users in a geographically distant region will suffer from slow response times. Although a [CDN](https://aws.amazon.com/what-is/cdn/) exists for this use case, it is used for static assets, not for dynamic APIs.\n\n### Multi-region setup\n\nTo achieve a multi-region architecture, while retaining the previous features, the strategy is still the same: replicate the previous design, this time in a different region.\n\nHowever, in this case, the way to connect the two is via `CNAME` DNS record of type [latency](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy-latency.html).\n\n![Complete Architecture](docs/general-diagram-complete.png)\n\nThe records are assigned to different AWS regions and latency is measured between users and AWS regions. The record of the region with the lowest latency is used to resolve the user's DNS requests.\n\nThis design is highly available and provides fast response times to users in different regions of the world. Now, it's time to deploy it!\n\n\u003e ⚠️ Note: this design provides resilience against AZ failure, but not regional outages. If the entire region fails, users of that region won't have the service operational.\n\n## Implementation\n\nNow that we have discussed the architectural design, it is time to implement it.\n\n### Infrastructure as code\n\nHere we use terraform to allow us to define all the infrastructure via code. This allows us to perform the design replications mentioned previously with relative ease. We can define pieces of the infrastructure as modules and just reuse them as many times as we need.\n\nHere is a brief description of the main directories and files:\n\n```txt\n.\n├── 📁 regional_domain/ -\u003e Per-region DNS config module\n├── 📁 service/         -\u003e NLB + EC2 + web server module\n├── 📄 dns.tf           -\u003e Hosted zone and DNS module usage\n├── 📄 locals.tf        -\u003e Variables used for replicating server resources\n├── 📄 services.tf      -\u003e Loop over the variables to deploy the servers\n└── 📄 vpc.tf           -\u003e Network config for public/private subnets\n```\n\nThe specifications of the service deployments are defined in `./terraform/locals.tf` and, for each region and AZ, a service is defined. As shown below, a public subnet is passed for the NLB, and a private one is passed for the server. The subnets define in which AZ the service will be deployed.\n\n```tf\nsa1 = {\n  name           = \"sa1-service\"\n  private_subnet = module.vpc_sa.private_subnets[0]\n  public_subnet  = module.vpc_sa.public_subnets[0]\n}\n```\n\n\u003e The name defines what you will see as a response if that server specifically responds to the request.\n\nThose variables are looped over in `services.tf` by region via `for_each = local.services.\u003cREGION\u003e`. This is a nice example of how, when using some terraform features, we can easily replicate infrastructure with next to no code duplication.\n\n`dns.tf` defines which service deployment is the primary and secondary. DNS records are deployed on pairs of primary/secondary via the `regional_domain/` module, together with a record of type latency associated with the region the module is being deployed to.\n\n```tf\nelb_target_primary = {\n  domain_name = module.services_sa[\"sa1\"].nlb_domain_name\n  zone_id     = module.services_sa[\"sa1\"].nlb_zone_id\n}\n```\n\nThose are the main components of the code. Feel free to dive into the modules to see how they are implemented. Now let's jump into how to deploy this.\n\n### Deploy\n\nThe very first thing needed for this demo is a domain name. If you already have one, remember to configure the Registrar of your domain name to use Route53 as the DNS server once the infrastructure has been deployed\n\n\u003e In my case, I added the name servers assigned to the Hosted Zone as `NS` records for the `locals.domain_name` on Cloudflare.\n\nAnd if you don't have one, remember you can also buy one from AWS itself as seen from the image below.\n\n![Buy domain name from AWS](docs/buy-domain-name.png)\n\n\u003e *Just remember that this will create a Hosted Zone automatically for the domain bought. You will need to change it to make use of the new one that will be created by this project*\n\nIn order to run this project you essentially only need terraform. However, I highly suggest installing it via [`asdf`](https://asdf-vm.com/guide/getting-started.html), as it allows you to automate the installation of several other tools and keep multiple versions of them installed at the same time.\n\nOnce `asdf` is installed, terraform can be installed in its correct version via\n\n```bash\nasdf plugin-add terraform https://github.com/asdf-community/asdf-hashicorp.git\nasdf install\n```\n\nThis project obviously also requires that you have properly configured access to your AWS account. If you have configured the credentials using a [file with a profile name](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format-profile), the only thing needed is to change the profile name in the `providers.tf` file.\n\nAnd lastly, change the domain name defined in `locals.tf` to your own.\n\nWith that, you can run the following command\n\n`cd terraform \u0026\u0026 terraform init \u0026\u0026 terraform apply`\n\nYou should see the output:\n\n`Plan: 123 to add, 0 to change, 0 to destroy.`\n\nThe deployment of all resources can take from 5 to around 10 minutes.\n\nAfter all has been deployed, you can already try to reach the service via `www.\u003cDOMAIN-NAME\u003e`, in my case, `www.service.dkneipp.com`.\n\nYou should see `sa1-service`, or `eu1-service` depending on the region you are currently in 😉. From a `dig` command we can also identify which region is responding to the request\n\n| Web UI                                 | DNS Resolution                  |\n|----------------------------------------|---------------------------------|\n| ![Web service](docs/eu-central-ui.png) | ![Dig](docs/eu-central-dig.png) |\n\nAlso, the IP returned by the DNS resolution should match of the NLB of the primary AZ of the region you are closest to.\n\n\u003e 💡 There is only one IP returned because the NLB has only one AZ associated. This ensures the traffic always goes to the designated AZ unless a failure happens in that AZ.\n\nAnd by using another computer closest to the other region, we can see the response from that other region\n\n| Web UI                                 | DNS Resolution                  |\n|----------------------------------------|---------------------------------|\n| ![Web service SA](docs/sa-east-ui.png) | ![Dig SA](docs/sa-east-dig.png) |\n\n### Test failover\n\nIn order to test if the failover from one AZ to the other is happening as expected, remove the instance from the target group associated with the primary NLB of the region you want to test. From the image below it can be seen how the instance can be removed\n\n![AWS Console](docs/aws-ui-simulate-failover.png)\n\nYou can also identify the correct target group by checking the listener of the primary NLB. The listener will be forwarding the traffic to the target group that should be changed.\n\nAfter this, the NLB has no more healthy targets and it is considered unhealthy. This status will be reported to Route53, which will automatically start resolving the DNS requests to the secondary NLB.\n\nWait for around 2 minutes, and you should be able to see the following while trying to access the same `www.service.dkneipp.com`\n\n| Web UI                                 | DNS Resolution                  |\n|----------------------------------------|---------------------------------|\n| ![Web service SA](docs/eu-central-ui-failover.png) | ![Dig SA](docs/eu-central-dig-failover.png) |\n\nNow, the secondary server is responding to the traffic, as we can identify from the Web UI response and the fact that the IP of the server changed.\n\n\u003e 💡 Note: since different regions have access to, essentially, different applications, this could be used as a way to promote service upgrades segmented by region\n\nAnd now, if you add the instance back to the target group of the primary NLB, in a couple of minutes you should see the previous response back again.\n\n### Cleanup\n\nA simple `terraform destroy` should delete all 123 resources.\n\n## Conclusion\n\nThis project shows a step-by-step design evolution from a basic web server available on the web, to a resilient design capable of handling failures in data centers and keeping response times low for users in different parts of the globe.\n\nA lot more can still be done, of course, like the use of multiple servers on the same AZ to handle more load or start following a microservice approach with Kubernetes or AWS ECS to handle the web server code deployment.\n\nHowever, the goal of this project is to show some interesting features we can use from AWS Route53 and NLBs to have a fast, reliable, and cost-effective web server spread in different parts of the world.\n\n### Notes\n\n\u003cdetails\u003e\n\n\u003csummary\u003eWhy \u003ccode\u003ewww\u003c/code\u003e subdomain?\u003c/summary\u003e\n\nIf you try to access the web server simply via the domain name, you will notice that the DNS request doesn't resolve.\n\nThis is because the `CNAME` records are associated with the `www` subdomain.\n\nThis has been done because, by default, Hosted Zones already come with `SOA` records domain apex, and `CNAME` records cannot overlap with `SOA` records.\n\nIf you try to deploy a `CNAME` record of `service.\u003cDOMAIN-NAME\u003e`, AWS API will return the following error:\n\n```txt\nBad request.\n(InvalidChangeBatch 400: RRSet of type CNAME with DNS name service.dkneipp.com. is not permitted at apex in zone service.dkneipp.com.)\n```\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielkneipp%2Faws-route53-global-dns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielkneipp%2Faws-route53-global-dns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielkneipp%2Faws-route53-global-dns/lists"}