Back to blog

Security

Auto-update AWS security groups with Cloudflare IPs

How to auto update security groups with Cloudflare IPs using AWS Lambda

March 16, 2026Platform Engineering3 min read

When you use Cloudflare to protect a website, the origin still needs to allow Cloudflare IP addresses through to the application. In AWS that usually means allowing those IP ranges in the relevant security groups.

The challenge is that Cloudflare’s IP list can change over time. If the list is baked into an infrastructure deployment and never refreshed, the security groups can become stale and block legitimate traffic. This article walks through one way to automate that update process with AWS Lambda and OpenTofu or Terraform.

Solution Overview

The pattern is straightforward. A Lambda function runs on a schedule, fetches the latest Cloudflare IP ranges, and updates tagged security groups automatically.

The Lambda function:

  • finds security groups tagged with CF-AutoUpdate = true
  • looks for a CF-Ports tag containing a comma-separated list of ports
  • updates ingress rules to allow the latest Cloudflare IPv4 ranges on those ports
  • defaults to port 443 if no CF-Ports tag is present

This keeps the control per security group while centralising the automation.

OpenTofu/Terraform Module Usage

You can package the setup in a reusable module:

module "cloudflare-sg-updater" {
  source = "coresolutions-ltd/cloudflare-sg-updater/aws"
}

That module can create the Lambda function, IAM role, and scheduled trigger needed to keep the rules current.

How the Lambda Function Works

The Lambda function can be written in Python with boto3.

Importing the EC2 Resource

import boto3

ec2 = boto3.resource('ec2')

Using the higher-level resource interface keeps the implementation relatively clean.

Filtering Security Groups by Tags

groups = ec2.security_groups.filter(
    Filters=[
        {'Name': 'tag:CF-AutoUpdate', 'Values': ['true']},
    ]
)

For each matching security group, read CF-Ports and convert the value into a list of ports. If the tag is missing, fall back to 443.

for security_group in groups:
    ports_tag = next((tag for tag in security_group.tags if tag["Key"] == "CF-Ports"), None)

    if ports_tag:
        ports = [int(port.strip()) for port in ports_tag['Value'].split(",")]
    else:
        ports = [443]

Fetching Cloudflare IP Addresses

import requests

response = requests.get('https://www.cloudflare.com/ips-v4')
cloudflare_ips = response.text.splitlines()

If you also need IPv6 support, the same pattern can be extended to ips-v6.

Adding Missing Rules

for port in ports:
    for ip in cloudflare_ips:
        if not rule_exists(current_rules, ip, port):
            security_group.authorize_ingress(
                IpProtocol='tcp',
                CidrIp=ip,
                FromPort=port,
                ToPort=port
            )

Removing Obsolete Rules

for port in ports:
    for ip in current_cloudflare_rules:
        if ip not in cloudflare_ips:
            security_group.revoke_ingress(
                IpProtocol='tcp',
                CidrIp=ip,
                FromPort=port,
                ToPort=port
            )

That keeps the rules aligned to the latest published Cloudflare ranges instead of only ever appending to them.

Watch the first run

The first run is the one to pay attention to, because this function removes rules as well as adding them. Any rule that was added by hand and is not in Cloudflare’s published ranges will disappear. Confirm the tag filter matches only the security groups you meant it to before you leave it running unattended.