AWS Managed Prefix Lists for Reusable IP Allowlists
Create reusable IP allowlists with AWS customer-managed prefix lists and apply them consistently across security groups and access controls.
Published · Republished on Medium

Customer-managed prefix lists solve a specific problem: the same trusted CIDR ranges need to be reused across several AWS resources. Instead of copying those CIDRs into every security group or route table, one prefix list becomes the controlled source of truth.
They do not turn a domain name into a durable network identity. DNS answers can change, return different addresses by location, or point to shared infrastructure. For SaaS or partner access, consume an IP range feed that the provider explicitly publishes and supports. Do not build a security allowlist by periodically resolving an arbitrary hostname.
When a prefix list is a good fit
- corporate office or VPN egress CIDRs used by several applications
- partner ranges supplied through an authenticated, documented feed
- shared private-network ranges referenced by route tables
- centrally governed ranges shared with other AWS accounts
AWS supports customer-managed prefix-list references in resources including VPC security groups, subnet route tables, transit gateway route tables, and AWS Network Firewall rule groups. A security group can use a prefix list as the source of an inbound rule or the destination of an outbound rule.
Create the prefix list
Choose MaxEntries deliberately. The maximum size affects quotas in resources that reference the list, even when the list currently contains fewer entries.
bash
aws ec2 create-managed-prefix-list \
--prefix-list-name "trusted-partner-egress" \
--address-family "IPv4" \
--max-entries 20 \
--entries \
Cidr=192.0.2.10/32,Description=partner-primary \
Cidr=198.51.100.0/28,Description=partner-secondaryThe example ranges are documentation-only addresses. Replace them with ranges whose ownership and purpose you have verified.
Reference it from a security group
bash
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--ip-permissions \
'IpProtocol=tcp,FromPort=443,ToPort=443,PrefixListIds=[{PrefixListId=pl-0123456789abcdef0,Description="Trusted partner HTTPS"}]'The rule follows subsequent prefix-list versions. Adding a CIDR therefore expands access everywhere the list is referenced; removing one can immediately interrupt legitimate traffic. Treat updates as controlled network-policy changes.
Reconcile entries safely
An updater should compare desired and current entries, then submit only the additions and removals. It must use the current prefix-list version because AWS applies optimistic concurrency control to modifications.
This Lambda example expects the desired IPv4 CIDRs in a JSON environment variable such as:
json
["192.0.2.10/32", "198.51.100.0/28"]python
import ipaddress
import json
import os
import boto3
ec2 = boto3.client("ec2")
def get_all_entries(prefix_list_id):
entries = []
next_token = None
while True:
request = {"PrefixListId": prefix_list_id, "MaxResults": 100}
if next_token:
request["NextToken"] = next_token
response = ec2.get_managed_prefix_list_entries(**request)
entries.extend(response["Entries"])
next_token = response.get("NextToken")
if not next_token:
return entries
def normalize_ipv4_cidrs(values):
return {
str(ipaddress.ip_network(value, strict=True))
for value in values
if ipaddress.ip_network(value, strict=True).version == 4
}
def lambda_handler(event, context):
prefix_list_id = os.environ["PREFIX_LIST_ID"]
desired = normalize_ipv4_cidrs(json.loads(os.environ["DESIRED_CIDRS_JSON"]))
prefix_list = ec2.describe_managed_prefix_lists(
PrefixListIds=[prefix_list_id]
)["PrefixLists"][0]
current = {
entry["Cidr"] for entry in get_all_entries(prefix_list_id)
}
add = sorted(desired - current)
remove = sorted(current - desired)
if not add and not remove:
return {"changed": False, "version": prefix_list["Version"]}
if len(desired) > prefix_list["MaxEntries"]:
raise ValueError("Desired CIDRs exceed prefix-list MaxEntries")
request = {
"PrefixListId": prefix_list_id,
"CurrentVersion": prefix_list["Version"],
}
if add:
request["AddEntries"] = [
{"Cidr": cidr, "Description": "managed-source"} for cidr in add
]
if remove:
request["RemoveEntries"] = [{"Cidr": cidr} for cidr in remove]
result = ec2.modify_managed_prefix_list(**request)["PrefixList"]
return {
"changed": True,
"added": add,
"removed": remove,
"version": result["Version"],
"state": result["State"],
}This is a reconciliation example, not a complete ingestion pipeline. Production automation still needs an authenticated source, schema validation, retry handling for concurrent-version conflicts, change approval where required, CloudWatch alarms, and a maximum-change guard. A feed returning an empty or unexpectedly large set should fail closed instead of removing every trusted range.
Validation and rollback
After an update, wait for the prefix list to reach modify-complete, inspect its entries, and test both an allowed and a denied source.
bash
aws ec2 describe-managed-prefix-lists \
--prefix-list-ids pl-0123456789abcdef0
aws ec2 get-managed-prefix-list-entries \
--prefix-list-id pl-0123456789abcdef0AWS stores prefix-list versions. If a bad update is applied, restore a known previous version after confirming that it contains the intended ranges:
bash
aws ec2 restore-managed-prefix-list-version \
--prefix-list-id pl-0123456789abcdef0 \
--current-version 7 \
--previous-version 2Operational guardrails
- Grant the updater permission only for the intended prefix list.
- Store operational configuration such as the prefix-list ID outside the code.
- Log the old and new CIDR sets without logging credentials or confidential feed tokens.
- Alarm on failed updates and lists stuck outside
modify-complete. - Review prefix-list and security-group quota impact before raising
MaxEntries. - Prefer immutable provider feeds over DNS resolution.
- Use VPC IPAM prefix-list automation when the desired ranges already come from managed IPAM resources.