Redirect www to non-www using Lambda@Edge
Tags: aws, serverless, python, lambda
Published: 2021-01-01 19:58:17

---
title: Redirect www to non-www using Lambda@Edge
published: true
date: 2020-12-29 00:00:00 UTC
tags: aws,serverless,python,lambda
canonical_url: https://jurriaan.cloud/redirect-www-to-non-www-using-lamda-at-edge/
---

> A recurring question among website owners is whether to choose non-www or www URLs. ([source](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Choosing_between_www_and_non-www_URLs))

As I’m considering myself somewhat of a minimalist, I’ve decided I want to serve my website on the non-www domain.

In this post I’ll show you how to redirect viewers from the www domain to the non-www domain using Lambda@Edge.

## Lambda@Edge

Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. [https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html)

There are four CloudFront events that can trigger a Lambda function to execute: _viewer request_, _origin request_, _origin response_ and _viewer response_.

![Lambda@Edge events](https://jurriaan.cloud/assets/images/lambda-at-edge-redirect-1.png)

_Image from [AWS documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html)_

The right place to implement our redirect is the **viewer request** event as we want to redirect the user before CloudFront even checks if the requested object is in the CloudFront cache.

## Add the redirect

I’m using the [Serverless Framework](https://www.serverless.com/) to provision the CloudFront distribution that is serving this blog. The framework [supports](https://www.serverless.com/framework/docs/providers/aws/events/cloudfront/) the CloudFront events as a trigger so adding a function that does the redirect is relatively easy.

I have configured two A records in Route53 that route traffic to the CloudFront distribution, one for the www domain and one for the non-www domain.

We can check the `Host` header to determine whether a viewer came in through the www domain or the non-www domain.

If they did come in through the www domain, we return a response with status code `301` and the `Location` header set to the same url the user came in with except for the `www.` part stripped. If they didn’t we do not touch the request and pass it on.

In the end, the function looks like this:

```python
def handle_event(event, context):
    request = event["Records"][0]["cf"]["request"]
    headers = request["headers"]
    host_header = headers["host"][0]["value"]

    if host_header.startswith("www."):
        uri = request["uri"]
        qs = request["querystring"]

        domain_without_www = host_header.strip("www.")

        response = {
            "status": "301",
            "statusDescription": "Moved Permanently",
            "headers": {
                "location": [{
                    "key": "Location",
                    "value": f"https://{domain_without_www}{uri}" if not qs else f"https://{domain_without_www}{uri}?{qs}"
                }]
            }
        }

        return response

    return request
```

I’ve then configured the function in my `serverless.yaml` like this (irrelevant parts left out):

```yaml
service: cloudfront

provider:
  name: aws
  region: us-east-1
  runtime: python3.8

custom:
  domainName: <myDomainName>

functions:
  viewerRequest:
    handler: src/functions/viewer_request/handler.handle_event
    events:
      - cloudFront:
          eventType: viewer-request
          origin: http://${self:custom.domainName}.s3-website-eu-west-1.amazonaws.com

resources:
  Resources:
    CloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Aliases:
            - ${self:custom.domainName}
            - www.${self:custom.domainName}
          Origins:
            - Id: ${self:custom.domainName}
              DomainName: ${self:custom.domainName}.s3-website-eu-west-1.amazonaws.com
              CustomOriginConfig:
                OriginProtocolPolicy: http-only
          DefaultCacheBehavior:
            TargetOriginId: ${self:custom.domainName}
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods:
              - GET
              - HEAD
              - OPTIONS
            ForwardedValues:
              QueryString: true
```

After deploying this I can also see that the Lambda function is associated with the cache behavior in the AWS console.

![CloudFront cache behavior associated Lambda](https://jurriaan.cloud/assets/images/cloudfront-associated-lambda-1.png)

Let’s test it out:

```
$ curl -iL https://www.jurriaan.cloud
HTTP/2 301
content-length: 0
location: https://jurriaan.cloud/
server: CloudFront
date: Tue, 29 Dec 2020 11:04:14 GMT
x-cache: LambdaGeneratedResponse from cloudfront
via: 1.1 3c01812e357a7900959ea67a1c5782ad.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: QJ-TK3kaLkJ1BMgzxP-LduIHy7efkpWu2Um9LJL0VYOSStLpf90CeQ==

HTTP/2 200
content-type: text/html
content-length: 4301
x-amz-id-2: OeaCkgGlHbaDO6VHVHSghVFysdRLzsPlJ7LcVPCEc658vQIPQ6CyU6PsSOFy/VUW8XSTiXQvVtE=
x-amz-request-id: 6162F12678845FAB
last-modified: Mon, 28 Dec 2020 14:35:41 GMT
server: AmazonS3
date: Tue, 29 Dec 2020 11:04:15 GMT
cache-control: no-cache
etag: "84107066793acaa28d5ffbadc3fec382"
x-cache: RefreshHit from cloudfront
via: 1.1 9fce949f3749407c8e6a75087e168b47.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: ATiO8jCE27cBONXbw4VPcHZ8R_-tEgCdtNMBPN4XZmyl8TkFPJYh9Q==
```

It works for URLs that contain a path as well:

```
$ curl -iL https://www.jurriaan.cloud/manage-python-dependencies-in-serverless-projects-with-serverless-layers-plugin/
HTTP/2 301
content-length: 0
location: https://jurriaan.cloud/manage-python-dependencies-in-serverless-projects-with-serverless-layers-plugin/
server: CloudFront
date: Tue, 29 Dec 2020 11:06:31 GMT
x-cache: LambdaGeneratedResponse from cloudfront
via: 1.1 dd133741afef09b02f3e6afd7cb39f40.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: 21uTcM40vvO2yy9clgfcdUThH_26nr4em2zE874wXDW23sn1cP74cQ==

HTTP/2 200
content-type: text/html
content-length: 19357
x-amz-id-2: 9F/cdUB9VxX4RhacXD5+nEk4QQtO3ZAtBR+B/v6RHXtbjgdC3DvjPwFZo5DbzX2DUXfynqyf6XM=
x-amz-request-id: 67F3725866AE73EB
last-modified: Mon, 28 Dec 2020 14:35:41 GMT
server: AmazonS3
date: Tue, 29 Dec 2020 11:06:33 GMT
cache-control: no-cache
etag: "b8c913de37afe87f7662626e09def0f8"
x-cache: RefreshHit from cloudfront
via: 1.1 52102486f97ad6ff39f81538f01349ab.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: qVtHd8fPzsierejaZF-CfjvTONvckVRy0HwbO29Pevc4AXXau4Qhjg==
```

And we can also confirm that requests to the non-www domain aren’t redirected:

```
$ curl -iL https://jurriaan.cloud/manage-python-dependencies-in-serverless-projects-with-serverless-layers-plugin/
HTTP/2 200
content-type: text/html
content-length: 19357
x-amz-id-2: 9F/cdUB9VxX4RhacXD5+nEk4QQtO3ZAtBR+B/v6RHXtbjgdC3DvjPwFZo5DbzX2DUXfynqyf6XM=
x-amz-request-id: 67F3725866AE73EB
last-modified: Mon, 28 Dec 2020 14:35:41 GMT
server: AmazonS3
date: Tue, 29 Dec 2020 11:08:07 GMT
cache-control: no-cache
etag: "b8c913de37afe87f7662626e09def0f8"
x-cache: RefreshHit from cloudfront
via: 1.1 552d1a24616d6b8d6e3fbbdf18a54b6a.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: nmPyUhi99-Pt8McBCtgqa1tFxRTbchbOB0XAlS5eF-hD5OklS9_-6g==
```

🎉

## Conclusion

Lambda@Edge is a perfect place to apply this redirect of www to non-www. It can be used for lots of other things as well. Make sure to take into account [the requirements and restrictions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html) when using Lambda@Edge for more complex things.