Lambda@Edge is one of two ways to run custom code as part of the request flow in CloudFront. In contrast to typical Lambda functions, they must be deployed to us-east-1 (North Virginia), and CloudFront will schedule them in regions across the world close to your users to optimize the response latency. Lambda@Edge functions are handy if you want to run custom logic inside the request or response path that doesn't fit within the constraints of what's possible with CloudFront functions.
Unfortunately, at the time of writing this, the AWS documentation doesn't mention in which regions functions can be executed. It just explains that replicas of the function are created in regions around the world. The only two sources I could find on the topic are this Reddit post by a random person and an AWS blog post that briefly mentions they're running in Regional Edge Caches.
Why would you even care? Isn't this an implementation detail? In some ways, it shouldn't matter much, and the only reason that I care about this is logging. When Lambda@Edge functions are executed, they write their logs in a log stream in the region where the code is running. That means you'll find log groups called /aws/lambda/us-east-1.{function-name}
scattered around the world with no central visibility. The aforementioned blog post details how to use a firehose stream to aggregate the logs into an S3 bucket for further analysis.
To do that, we need to deploy log groups in all regions that have these Regional Edge Caches, and the 2019 post mentions 11 of them. On the CloudFront features page, we get a nice overview of CloudFront locations around the world. We care about the ones with a red dot.
The website also offers a list view that's a little more useful than the pretty map since it has actual names. This contains nice human-readable names. Unfortunately, they don't mention the API code (e.g., us-east-1), which you'd actually need to pre-create log groups.
Since I'm a big fan of spending too much time automating things that should really be done precisely once manually, I figured out how to create a list of region codes in which Lambda@Edge functions can be scheduled. First, I used the developer tools to figure out which endpoints the website uses to get all these CloudFront locations. AWS commonly uses a central API to retrieve content listings for its website, that's available at https://aws.amazon.com/api/dirs/items/search
.
This API is undocumented, so you probably shouldn't rely on it, but it has been around for a long time. The website can retrieve all kinds of information from here, and if we specify the correct directory ID (which we get by finding the correct request in the network log), we get a list of the CloudFront points of presence that are visible on the maps. The data structures it returns look something like this (just a single list item here):
{
"item": {
"id": "cf-map-pins#namer-us-zelienople",
"locale": "en_US",
"directoryId": "cf-map-pins",
"name": "North America United States Zelienople",
"dateCreated": "2024-02-15T18:28:10+0000",
"dateUpdated": "2024-03-22T18:51:47+0000",
"additionalFields": {
"x": "64.4",
"pinName": "Zelienople",
"y": "41",
"pinDescription": "United States"
}
},
"tags": [
{
"id": "GLOBAL#location#namer",
"locale": "en_US",
"tagNamespaceId": "GLOBAL#location",
"name": "North America",
"description": "North America",
"dateCreated": "2020-02-03T05:29:03+0000",
"dateUpdated": "2022-02-03T03:31:34+0000"
},
{
"id": "cf-map-pins#map-format#embedded-pops",
"locale": "en_US",
"tagNamespaceId": "cf-map-pins#map-format",
"name": "Embedded POPs",
"description": "<p>Embedded POPs</p>\n",
"dateCreated": "2024-02-15T19:34:19+0000",
"dateUpdated": "2024-02-15T19:34:19+0000"
},
{
"id": "GLOBAL#infrastructure-type#region",
"locale": "en_US",
"tagNamespaceId": "GLOBAL#infrastructure-type",
"name": "Region",
"description": "Region",
"dateCreated": "2021-05-19T07:48:42+0000",
"dateUpdated": "2022-02-03T03:31:20+0000"
}
]
}
It would be nice if the data structure included any API names, but sadly that's not available from this web service. Time to start scraping. I wrote a small python script that queries the data from this endpoint and filters it to the regional edge caches. Regional edge caches can be identified by having a specific tag id attached.
import logging
import sys
import requests
LOGGER = logging.getLogger(__name__)
LOGGER.addHandler(logging.StreamHandler(sys.stdout))
LOGGER.setLevel(logging.DEBUG)
def fetch_cloudfront_pops() -> list[dict]:
more_content = True
page = 0
all_items = []
while more_content:
more_content = False
response = requests.get(
"https://aws.amazon.com/api/dirs/items/search",
params={
"item.directoryId": "cf-map-pins",
"sort_by": "item.additionalFields.y",
"sort_order": "desc",
"size": "500",
"item.locale": "en_US",
"page": page,
},
timeout=10,
)
for item_container in response.json()["items"]:
more_content = True
item = item_container["item"]
item["tags"] = item_container["tags"]
all_items.append(item)
page += 1
return all_items
def is_regional_edge_pop(item: dict) -> bool:
for tag in item["tags"]:
if tag["id"].endswith("#regional-edge-caches"):
return True
return False
Running this gives us a list of regional edge caches and the country they're located in:
if __name__ == "__main__":
all_pops = fetch_cloudfront_pops()
regional_pops = [pop for pop in all_pops if is_regional_edge_pop(pop)]
regional_pop_names_and_descriptions = [
(x["additionalFields"]["pinName"], x["additionalFields"]["pinDescription"])
for x in regional_pops
]
LOGGER.info("Regional Edge Caches: %s", regional_pop_names_and_descriptions)
Regional Edge Caches: [('Seoul', 'South Korea'), ('Tokyo', 'Japan'), ('Sao Paulo', 'Brazil'), ('Dublin', 'Ireland'), ('Oregon', 'USA'), ('Mumbai', 'India'), ('Ohio', 'USA'), ('Sydney', 'Australia'), ('London', 'UK'), ('Frankfurt', 'Germany'), ('Northern Virginia', 'USA'), ('California', 'USA'), ('Singapore', 'Singapore')]
Now, we just need to map these to the correct API codes, which should be easy, right? We just need to find an API endpoint that maps the human-readable names of AWS regions to their API code. The official APIs allow you to get a list of API codes through the account.ListRegions API, which is insufficient because it doesn't contain the human-readable names.
After some digging, I found the AWS website that allows you to check the regional availability of services. The developer tools showed that it requested a locations.json
which happens to contain exactly the data I was interested in.
// Excerpt
{
"Africa (Cape Town)": {
"name": "Africa (Cape Town)",
"code": "af-south-1",
"type": "AWS Region",
"label": "Africa (Cape Town)",
"continent": "Africa"
},
// ...
}
The last problem to solve was mapping the display names from the console to the names in the CloudFront diagram. CloudFront sometimes uses names that are very close to the console display name, e.g., "Cape Town" and "Africa (Cape Town)", but also variations such as "Northern Virginia" instead of "US East (N. Virginia)", which make matching a bit more annoying. The biggest confusion is the Ireland/Dublin region. CloudFront refers to it as "Dublin," and the console display name is "EU West (Ireland)".
I ended up solving this with difflib.get_close_matches
from the Python standard library. It accepts a word and a list of keywords and returns the n closest matches from the keyword list using a similarity score. This allowed me to find the best match in the console display names based on the city's name. Since we have to deal with Dublin/Ireland, I had to check based on the description (country name) if the city yielded no result.
def get_region_info() -> dict[str, dict]:
global_infrastructure = requests.get(
"https://b0.p.awsstatic.com/locations/1.0/aws/current/locations.json",
timeout=10,
).json()
return {
key: value
for key, value in global_infrastructure.items()
if value["type"] == "AWS Region"
}
if __name__ == "__main__":
# ...
region_info = get_region_info()
for regional_pop_name, description in regional_pop_names_and_descriptions:
matches = difflib.get_close_matches(
regional_pop_name, region_info.keys(), n=1, cutoff=0.4
)
if not matches:
LOGGER.debug(
"Failed to match %s by name, trying description (%s)",
regional_pop_name,
description,
)
matches = difflib.get_close_matches(
description, region_info.keys(), n=1, cutoff=0.3
)
if not matches:
raise RuntimeError(f"Unable to map {regional_pop_name}")
region_name = matches[0]
api_name = region_info[region_name]["code"]
LOGGER.info(
"The CF-Name %s most closely matches %s (%s)",
regional_pop_name,
region_name,
api_name,
)
Running my code showed that the number of regional edge caches has increased from 11 in 2019 to 13 in early 2025 and are as follows:
Region Name | Region Code |
---|---|
Asia Pacific (Mumbai) | ap-south-1 |
Asia Pacific (Seoul) | ap-northeast-2 |
Asia Pacific (Singapore) | ap-southeast-1 |
Asia Pacific (Sydney) | ap-southeast-2 |
Asia Pacific (Tokyo) | ap-northeast-1 |
EU (Frankfurt) | eu-central-1 |
EU (Ireland) | eu-west-1 |
EU (London) | eu-west-2 |
South America (Sao Paulo) | sa-east-1 |
US East (N. Virginia) | us-east-1 |
US East (Ohio) | us-east-2 |
US West (N. California) | us-west-1 |
US West (Oregon) | us-west-2 |
Conclusion
Reddit was correct all along. This information shouldn't be hard to find - I'd expect this list or maybe a table to exist somewhere in the CloudFront docs. Ideally, there should be an official API endpoint so you can automate the process more quickly. Granted, the list doesn't seem to change much, but AWS clearly has that information available and could make our lives easier.
If you liked this post, you might also like the one telling the story of how I spent a few hours using advanced technology to save $2.
The script is available on Github.
— Maurice
Top comments (0)