AWS : How to set a static IP to a Classic Load Balancer

Having a static IP assigned to a Load Balancer can be useful for firewall or connectivity components reasons. For example, if you have a critical system which only allows authorized IPs, you will not be able to use your Load balancer if it’s not properly configured.
AWS allows you to have static IPs on your Load Balancer on the new offerings :
– ALB (Application Load Balancer)
– NLB (Network Load Balancer)
It can be done by the creation of an “Accelerator” for the ALB, and assigned Elastic IPs for the NLB. However, you may have a Classic Load Balancer due to infrastructure reason. And here it gets complicated to have a static IP, there are no “simple” ways proposed by AWS.
But we have a solution to do it efficiently. The guide is based on this AWS article https://aws.amazon.com/blogs/networking-and-content-delivery/using-static-ip-addresses-for-application-load-balancers/ explaining how to set a static IP for an Application Load Balancer. We will reuse the different parts here for clarity and adapt it for a Classic Load Balancer. It will be done using the AWS Management Console. The difference with the AWS guide, is that NLB target only accept private IP address inside a VPC and can’t use public IP. So our modified Lambda function will retrieve the private IP address of the Classic LB and assign it as a target.
Concretely, you are going to create a few AWS components, which are :
– an IAM policy and role
– a S3 bucket,
– a Lambda function,
– a Cloudwatch event,
– a Network Load Balancer,
– one or multiple Elastic IPs.
Note that those different components require little computation and are not expensive. We used this solution for months with multi-availability zones and the price was really low.
The idea will be to have a Network Load Balancer configured with static Elastic IPs you previously created. This NLB will redirect the requests directly to the Classic Load Balancer. The NLB will have a CNAME domain which can be used in Route 53 to have a proper DNS. This NLB will face the external internet and access your application as it possess a fixed IP.
Note: You can use this guide if you have a single zone or multi availability zone VPC. Both will be managed by the Lambda function.

The NLB will redirect all of the requests to our Classic Load Balancer by redefining constantly his target group. A target group contains the different instances to which we want a Load balancer to distribute the requests. We can assign a target by providing his IP address. However, the Classic Load Balancer keeps changing his IP address (that’s what we want to fix). So how should we define the target if the IP is changing ? We will use a Lambda function which will get the current IP of our Classic LB and assign a new target in the NLB if the IP address has changed.

The steps the Lambda function takes
- Query DNS for IP addresses in use by the Classic LB. Upload the results (NEW IP LIST) to the S3 bucket.
- From those public IP adresses get the associated private IP adresses in the VPC by requesting the Network Interfaces of your account possessing such public IP adresses.
- Call the describe-target-health API action to get a list of the IP addresses that are currently registered to the NLB (REGISTERED LIST).
- Download previous IP address list (OLD LIST). If it is the first invocation of the Lambda function, this IP address list is empty.
- Publish the NEW LIST to the Lambda function’s CloudWatch Logs log stream. This can be used later to search for IP addresses that were used by the ALB.
- Update the CloudWatch metric that tracks the number of the internal ALB IP addresses (created on first invocation). This metric shows how many IP addresses changed since the last run. This is useful if you want to track how many IP addresses your load balancer had over time. You can disable it by setting CW_METRIC_FLAG_IP_COUNT to “false”. Here is an example of the CloudWatch metric, showing that the number of IP addresses of the ALB changed from 20 IP addresses to 24 then to 28.
- Register IP addresses to the NLB that are in NEW LIST but missing from the OLD LIST or REGISTERED LIST.
- Deregister IP addresses in the OLD LIST that are missing from the NEW LIST.
STEP 1: Create an IAM policy
In the IAM console, create an IAM policy with the permissions required by the Lambda function. You can find the sample IAM policy in Appendix A. To learn more, see the documentation for Creating IAM Policies. To learn how to create an IAM role for AWS Lambda see the documentation for Creating a Role for an AWS Service (Console).


STEP 2: Create an IAM role
After the IAM policy is ready, create an IAM role and attach the IAM policy that we created in Step 1.


STEP 3: Create a Lambda function
Now we have an IAM role for our Lambda function to assume. In the AWS Lambda console, create the Lambda function. While creating the function, we need to make sure the IAM role that was created in Step 2 is selected and the Runtime environment is set to Python2.7.

STEP 4: Configure the Lambda function
Change the handler name to “populate_NLB_TG_with_ALB.lambda_handler” so that AWS Lambda can pick up the Python file that contains the function code. After that, click the “Upload” button and upload the Lambda function zip file.
Get it also from the git repo : https://github.com/pelletierkevin/aws-lambda-static-load-balancer

STEP 5: Set up the Lambda environment variables
After we see the function code on the Lambda console, add the following environment variables to the Lambda function to let it populate Network Load Balancer’s target group with Application Load Balancer IP addresses.

- ALB_DNS_NAME – the full DNS name (FQDN) of the ALB
- ALB_LISTENER – The traffic listener port of the ALB
- S3_BUCKET – Bucket to track changes between Lambda invocations
- NLB_TG_ARN – The ARN of the NLBs target group
- MAX_LOOKUP_PER_INVOCATION – The max times of DNS look per invocation. The default value is 50 in the CloudFormation template.
- INVOCATIONS_BEFORE_DEREGISTRATION – Then number of required Invocations before an IP address is deregistered. The default value is 3 in the CloudFormation template.
- CW_METRIC_FLAG_IP_COUNT – The controller flag that enables the CloudWatch metric of the IP address count. The default value is “true” in the CloudFormation template.
A single DNS lookup for a load balancer will only return up to eight IP addresses. So, if you have an ALB that has more than eight IP addresses, you need to perform multiple DNS queries to be sure you have all of the addresses. To achieve this we provide two environment variables MAX_LOOKUP_PER_INVOCATION and INVOCATIONS_BEFORE_DEREGISTRATION.
MAX_LOOKUP_PER_INVOCATION gives us the option to define how many DNS lookups the Lambda function performs if there are more than 8 IP addresses in the first DNS response. The default value is set to 50. The higher this is, the more likely you will have all of the addresses. In our testing we found that the vast majority of the time all IP addresses were returned within 20-40 queries. We suggest starting here and tuning if you observe IP addresses missing from results.
INVOCATIONS_BEFORE_DEREGISTRATION lets you configure the number of times an IP address can not be in the DNS results before we will deregister it. In normal operation, the IP address of an ALB continues to be available after it is removed from DNS for a short period. The NLB health check will detect failed ALB IP addresses if we miss one, so immediately de-registering is not a risk to our traffic. The default value is set to 3, which causes an ALB IP address to be deregistered only after it is missing from the DNS result for 3 minutes.
In our testing, the Lambda function rarely takes more than 1 minute to run. We set the timeout to 5 minutes to give it enough time to run. More information about how to configure your Lambda function is available in the documentation at Configure Your Lambda Function.

STEP 6: Create a CloudWatch Event
After we create the Lambda function, the next step is to open the CloudWatch console, create a CloudWatch Event, and configure it to trigger the Lambda function that we just created.

STEP 7: Configure the CloudWatch Event
On the CloudWatch Event console, set the job to run at a fixed rate of 1 time per minute. On the left side, we select the Lambda function as the target of the event.

After the configuration is ready, go ahead and save the CloudWatch Event rule.

After that you should be able to to test the Network Load Balancer which will redirect to your Classic Load Balancer.