Redirecting HTTP to HTTPS in AWS

I wanted an easy way in AWS (e.g. using an Elastic Load Balancer) to rewrite/redirect a URL in Elastic Compute Cloud (EC2) but had to find an alternate solution. Having the ability to do this is useful when you're setting up a website where you want to accept a port 80 (e.g. http://www.domain.com or http://domain.com) web request, but redirect it to port 443 (https://www.domain.com). This way you can force all traffic to use SSL/TLS.

This is something I just needed to work to get the website setup, and wanted the simplest solution possible. It's fairly straight forward to do this using a reverse proxy. After examining the obvious choices (e.g. NGINX and Apache HTTP Server w/ mod_proxy), I chose lighttpd (https://www.lighttpd.net/) because it looked to be the most lightweight and easiest to configure for what I wanted to do.

Standing up lighttpd

You can install lighttpd on the command line using yum or cloud-init. Or you could use a lighttpd Docker container in AWS. I'll step through all three possibilities.

Using yum install

Installing lighttpd on Amazon Linux is simple. Simply spin up an instance . Be certain to create/assign a user key pair. SSH into the instance and install lighttpd:

sudo yum update -y
sudo yum install -y lighttpd

This will install lighttpd to the /etc/lighttpd/ folder. You can verify by running the 'find' command:

sudo find / -name lighttpd.conf

To start lighttpd from the command line for testing purposes, you can enter:

sudo lighttpd -D -f /etc/lighttpd/lghttpd.conf

Or to start it as a service:

sudo service lighttpd start
sudo service lighttpd status

Now skip to the Configure lighttpd section below.

Using cloud-init

For a slightly more advanced approach/shortcut you could also look at cloud-init to perform this install using the User Data field during instance creation:

#!/bin/bash
yum update -y
yum install -y lighttpd
sudo service lighttpd start

Just as before, lighttpd will be installed in the /etc/lighttpd/ folder.

Now skip to the Configure lighttpd section below.

Using a Docker Container

Another alternative is to use lighttpd in a Docker container. To use Docker on EC2 Amazon Linux, launch an instance. If you intend to use EC2 Container Registry (ECR) to host this container, I recommend using a role assignment for the instance. You must assign the role during the creation of the instance. This makes it cleaner/easier to access ECR without having to install your access keys. This way, you can use the AWS CLI leveraging the role to perform the call according to the assigned permissions.

Below is the lighttpd dockerfile I use to create the container:

## Use Alpine Linux
FROM alpine

## Install lighttpd and remove install files
RUN apk add --update lighttpd \
    && rm -rf /var/cache/apk/*

## Copy lighttpd config over to overwrite the config file
ADD lighttpd.conf /etc/lighttpd/lighttpd.conf
ADD modules.conf /etc/lightppd/modules.conf

## Command on startup
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]

I provide some edits to lighttpd.conf and modules.conf in the Configure lighttpd section below. You can download these files separately from . To build the container and tag it, simply run:

docker build -t lighttpd:latest .

If you're using ECR, you get the instructions for tagging and pushing a container on startup. For the above, after granting Docker permissions to access ECR, it would simply be:

docker tag lighttpd:latest <container id>.dkr.ecr.<region>.amazonaws.com/<container>:<tag>

docker push <container id>.dkr.ecr.<region>.amazonaws.com/<container>:<tag>

Where the <> are replaced with the correct details. When you log into the AWS instance, after installing Docker and logging into ECR, you simply perform a run command to have Docker retrieve/pull the container and run it:

docker run -p xxxx:xxxx --restart always -d <container id>.dkr.ecr.<region>.amazonaws.com/<container>:<tag>

Where xxxx:xxxx is the port you're receiving requests on in that ec2 instance mapped to the server.port in the lighttpd.

Configure lighttpd

Here, I present the fewest edits to lighttpd and modules.conf possible. You'll want to read up on these so that you undestand what I'm doing. You may want to further restrict access, etc. To configure lighttpd to perform redirects, you want to open the modules.conf file:

sudo vi /etc/lighttpd/modules.conf

Modify the list of loaded modules to include rewrite and redirect by uncommenting mod_redirect and mod_rewrite:

server.modules = (
    "mod_access",
    #  "mod_alias",
    #  "mod_auth",
    #  "mod_evasive",
    "mod_redirect",
    "mod_rewrite",
    #  "mod_setenv",
    #  "mod_usertrack",
)

Next, scroll down and uncomment the following line:

include "conf.d/status.conf"

This will allow you to setup a health check on the service at "/server-status?auto" from the ELB or ALB.

Save and exit the file (hitting the ESC key, and typing :wq!). Next, open the lighttpd.conf for writing:

sudo vi /etc/lighttpd/lighttpd.conf

Scroll down to the Filename/File handling section. Simply add:

$HTTP["host"] =~ "www\.YOUR_SITE\.com" {
 url.redirect = ("^/(.*)$" => "https://www.YOUR_SITE.com/$1")
} else $HTTP["host"] =~ "blog\.YOUR_SITE\.com" {
 url.redirect = ("^/(.*)$" => "https://blog.YOUR_SITE.com/$1")
} else $HTTP["host"] =~ "YOUR_SITE\.com" {
 url.redirect = ("^/(.*)$" => "https://www.YOUR_SITE.com/$1")
}

This config will redirect incoming requests based on the host. The example above demonstrates how to redirect the 'www' requests, a separate sub-domain such as 'blog', and a catchall without any sub-domain. Plus, the regex captures the path after the first forward slash, should you want to append that to the redirect URL. The '$1' puts the path from the request to the end of the redirect URL.

Monitoring Health Status

In AWS, using either ELBs or an ALB Web Target, you may need to (and should) monitor the health status of each component in your architecture. Therfore, I recently learned how to add a new module to lighttpd to accomplish this very thing: mod_status.

To add mod_status, you have two choices. You can either uncomment include conf.d/status.conf in modules.conf or simply author your modules.conf again by adding mod_status to your list of server.modules. Doing the former (uncommenting include conf.d/status.conf in modules.conf) means making the conf.d/status.conf file accessible to your service (-e.g. adding it to your Dockerfile and repackaging your container if you are using Docker).

If you choose to just edit modules.conf, you can add mod_status above mod_redirect as shown below.

server.modules = (
    "mod_access",
    #  "mod_alias",
    #  "mod_auth",
    #  "mod_evasive",
    "mod_status",
    "mod_redirect",
    "mod_rewrite",
    #  "mod_setenv",
    #  "mod_usertrack",
)

Next, you'll need to enable the server-status page by activtating the server.status endpoint. This is done for you automatically if you went the conf.d/status.conf route ... but with one caveat which I'll explain below. But if you chose to add this to modules.conf directly, you'll need to add the following line (-e.g. under your server.modules invocation):

status.status-url = "/server-status"

Placing this line by itself means that any IP can access the /server-status page. You can place a request filter around this to restrict access (and/or use Security Groups, etc). An example of this is shown below: :

$HTTP["remoteip"] == "127.0.0.0/8" {
    status.status-url = "/server-status"
}

The above example comes from the conf.d/status.conf file. If you decided to uncomment the reference to conf.d/status.conf in modules.conf, you'll want to be aware that this filter is already active, restricting access to IPs beginning with the first octet: "127". You can alter the IP and CIDR block range to fit your needs.

You should be able to access the /server-status page now. And you can use it as a target for your load balancer.