How to use Lambda@Edge with Terraform

The differences between Lambda@Edge and regular functions

5 mins


Lambda@Edge is advertised mainly as a tool that brings processing closer to the users thus increasing speed. But as a developer, I see a different use-case: to influence how CloudFront works. Without functions, CloudFront offers only a handful of configuration options. You can add origins and cache behaviors to set up routing, but you’ll run out of options as soon as you need anything beyond the basics.

This is when Lambda comes handy. You can modify the requests and the responses any way you’d like, which opens up ways to fix most of the shortcomings of CloudFront config.

There are some differences from a regular Lambda function though, and some new limitations you should know about before you start using edge functions.

Differences from a regular Lambda function

The usual Lambda resources are needed: an archive_file to hold the code, an aws_iam_role for the execution role, an aws_iam_policy_document for the function’s permissions and an aws_iam_role_policy to wire the last two together. This is what you need for any Lambda function, so let’s concentrate on the differences!

archive_fileaws_lambda_function2)publish = true3)region = us-east-1aws_iam_policy_documentaws_iam_role1)assume_role_policyaws_iam_role_policyCloudFront

Shameless plug
Join our mailing list and get a free guide of 5 tips to make an AWS account more secure


The aws_iam_role’s assume role policy must include both and

resource "aws_iam_role" "lambda_edge_exec" {
  assume_role_policy = <<EOF
  "Version": "2012-10-17",
  "Statement": [
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": ["", ""]
      "Effect": "Allow"


Since Lambda@Edge requires a specific version to be referenced, you need to instruct Terraform to publish a new version for every change. To do this, use publish = true:

resource "aws_lambda_function" "lambda_edge" {
	# ...

	publish  = true

The function must be deployed in the us-east-1 region. If you deploy the whole stack there then it’s not a problem, but it’s better to make sure that is not a requirement. Fortunately, Terraform can deploy resources to multiple regions, which is exactly what we need here.

First, define a provider with the us-east-1 region specified:

provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"

Then make the function use this provider:

resource "aws_lambda_function" "lambda_edge" {
	# ...

  provider = aws.us_east_1

This makes sure this function goes to us-east-1 no matter where the rest of the resources are deployed.


To associate this function with a distribution, add a lambda_function_association to the cache_behavior:

resource "aws_cloudfront_distribution" "distribution" {
	# ...

	# or default_cache_behavior
	ordered_cache_behavior {
		# ...

		lambda_function_association {
      event_type = "origin-request"
      lambda_arn = "${aws_lambda_function.lambda_edge.qualified_arn}"

The lambda_arn must include the version, that’s why the qualified_arn has to be used here.

The event_type must be one of the 4 defined trigger point: viewer-request, origin-request, viewer-response, and origin-response.


In this case, I want to change how CloudFront calls the origin, so I specify the origin-request trigger.

Example code

Now that we have all the resources in place, let’s see an example!

CloudFront appends the full path to the origin request which can be a problem, for example, when your API expects requests starting from the root (/) instead of some other path.

With a fairly common configuration of an API Gateway with the /api/* pattern, a request to /api/users goes to, well, /api/users. But then you need to make sure your API is able to handle this path and does not expect /users instead.

<distribution><restApiId>.execute-api.<region> = ...origin_path = "/stage

It would be better to just strip the /api path from the request sent to the origin. And that’s when Lambda@Edge comes useful.

The code is straightforward. The event.Records[0].cf.request.uri contains the path /api/users and we need to strip the /api part from the start:

module.exports.handler = (event, context, callback) => {
	const request = event.Records[0].cf.request;
	request.uri = request.uri.replace(/^\/api/, "");
	callback(null, request);

Lambda@Edge<restApiId>.execute-api.<region><distribution><restApiId>.execute-api.<region> = ...origin_path = "/stage"request.uri.replace(/^\/api/, "")

Shameless plug
Learn how to implement a secure, robust, and scalable file downloading and uploading solution using S3 signed URLs
Get the Ebook

Not sure if it's for you? Sign up for the free 8-part AWS signed URLs email course and find out.


Seems like the pricing is deliberately made to make it hard to compare traditional and Lambda@Edge pricing. The request charges are straightforward: $0.2 vs $0.6.

But for duration prices, one is in GB-seconds and 100ms increments the other one is 128MB-seconds and 50ms increments. In GB-seconds it’s $0.0000166667 vs $0.00005001, which is again three times the price.

In total, Lambda@Edge is three times as expensive as a normal Lambda.

But for simple cases, like transforming a request, the math is a bit different. Lambda@Edge is metered at 50ms increments and if you don’t use any external services then it’s likely you’ll never exceed that. That means for every 1 million requests you’ll pay ~$0.9 extra.


When you try to destroy a stack with Lambda@Edge functions, you’ll see this error message:

Error: Error deleting Lambda Function: InvalidParameterValueException: Lambda was unable to delete <arn> because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functions and Replicas.

When CloudFront starts using the Lambda function it replicates it to the global network. This happens during that ~20 minutes the distribution is deploying.

But when you delete the distribution, while it still takes ~20 minutes, the replicated functions are not deleted but only scheduled for deletion. In effect, Lambda complains about the replicas and refuses to delete the function.

You need to wait a few hours (!) after you delete the distribution to delete the function. Keep trying to terraform destroy until you succeed.

It’s funny to see that even this was an improvement over how it originally worked. AWS devs did not think people would want to delete Lambda@Edge functions, like, ever.

Shameless plug
Learn how serverless and Infrastructure-as-Code play together in AWS

Find out more


Lambda@Edge gives you the missing piece of CloudFront configurations. Apart from request rewriting, you can add cache or security headers, define more advanced routing policies, optimize content, and a lot more. It is a valuable tool when working with CloudFront.

01 October 2019