CloudFront IP ranges and the real client IP
CloudFront appears in ip-ranges.json under two service codes, and which one you need depends on the question. Which addresses do viewers connect to? Which addresses connect to my origin? And once your origin only sees CloudFront, where has the viewer's own address gone?
CLOUDFRONT vs CLOUDFRONT_ORIGIN_FACING
| Code | What it covers | Direction | Use it for |
|---|---|---|---|
CLOUDFRONT | Edge servers and regional edge caches | Viewers connect to these addresses | Recognising CloudFront in DNS answers and logs; outbound rules for clients |
CLOUDFRONT_ORIGIN_FACING | The servers that fetch content from origins | CloudFront connects to your origin from these | Origin firewalls, and deciding whose X-Forwarded-For to trust |
CLOUDFRONTrows markedGLOBALare edge locations. Rows with a Region are regional edge caches; CloudFront's own list of CloudFront IPs labels them that way. That list has no origin-facing section, so useip-ranges.jsonor the managed prefix list for origins.- In the data we checked, every IPv4 origin-facing range sat inside a
CLOUDFRONTrange, so an origin-facing address matches both codes. 130.176.0.1 shows this. At an origin, trust the narrower origin-facing list. - Edge addresses have reverse DNS names such as
server-13-32-12-39.bud50.r.cloudfront.net. Thebud50part is the edge location (Budapest), the same code CloudFront returns in itsX-Amz-Cf-Popresponse header. Try 13.32.12.39.
Lock your origin to CloudFront
If your origin is in a VPC, allow inbound traffic only from the AWS-managed prefix lists com.amazonaws.global.cloudfront.origin-facing (IPv4) and com.amazonaws.global.ipv6.cloudfront.origin-facing (IPv6). AWS keeps them current, so there is nothing to update. The allowlisting guide has the CLI and Terraform. The CloudFront list counts as many rules against your security group quota, so check its weight first.
The prefix list lets in every CloudFront distribution, not only yours. Anyone can create a distribution that uses your server as its origin. Add a second check:
- a secret custom header that your distribution adds to origin requests and your origin or load balancer requires (rotate it now and then);
- or VPC origins, which keep a private load balancer or instance reachable only through CloudFront;
- or, for S3, origin access control instead of IP rules.
Not on AWS? Allow the CLOUDFRONT_ORIGIN_FACING ranges from ip-ranges.json and automate updates. The lists below give you the current set.
Get the viewer's real IP
Behind CloudFront, the connection your server sees comes from a CloudFront server. The viewer's address travels in headers:
X-Forwarded-For. CloudFront appends the address it received the request from to the end of this header. Anything before that came from the client and can be forged. A load balancer between CloudFront and your server appends the CloudFront server's address after it.CloudFront-Viewer-Address. The viewer's IP and source port, for example198.51.100.10:46532. CloudFront only sends it if you add it in an origin request policy (it can't go in a cache policy). Remove the port after the last colon.
nginx
Run the script further down once before you add the include line: until the file it includes exists, nginx -t fails.
# /etc/nginx/conf.d/cloudfront-realip.conf
include /etc/nginx/cloudfront-ips.conf; # set_real_ip_from lines, generated below
real_ip_header X-Forwarded-For;
real_ip_recursive on;
nginx only rewrites $remote_addr when the connection comes from a trusted address. With real_ip_recursive on it takes the last address in the header that isn't trusted, which is the one CloudFront appended. If a load balancer sits in between, add its subnets to set_real_ip_from too. $realip_remote_addr still holds the CloudFront server's address if you want it in your logs.
Generate the trusted list, and run the script on a schedule or from the SNS change notification:
#!/bin/sh
set -eu
out=/etc/nginx/cloudfront-ips.conf
tmp=$(mktemp)
curl -fsS https://ip-ranges.amazonaws.com/ip-ranges.json \
| jq -r '(.prefixes[] | select(.service=="CLOUDFRONT_ORIGIN_FACING") | .ip_prefix),
(.ipv6_prefixes[] | select(.service=="CLOUDFRONT_ORIGIN_FACING") | .ipv6_prefix)
| "set_real_ip_from \(.);"' > "$tmp"
# Never install an empty list (a failed download produces one)
[ -s "$tmp" ] || { echo "empty list, keeping the old one" >&2; rm -f "$tmp"; exit 1; }
cp "$out" "$out.bak" 2>/dev/null || true
mv "$tmp" "$out"
chmod 644 "$out"
nginx -t && nginx -s reload || { if [ -f "$out.bak" ]; then mv "$out.bak" "$out"; fi; echo "nginx rejected the new list" >&2; exit 1; }
Apache
# /etc/apache2/cloudfront-ips.txt: one CIDR per line, generated below
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxyList /etc/apache2/cloudfront-ips.txt
Generate that file with the same jq filter, printing . instead of the set_real_ip_from line. Apache doesn't allow a comment on the same line as a directive, so keep comments on lines of their own.
Current CloudFront ranges
These lists are built from the current ip-ranges.json when you click. Nothing downloads before that.
CLOUDFRONT_ORIGIN_FACING (origin firewalls, real-IP trust)
CLOUDFRONT (edge servers and regional edge caches)
Related guides
Sources
- AWS: Locations and IP address ranges of CloudFront edge servers (managed prefix lists)
- AWS: Request and response behavior for custom origins (
X-Forwarded-For) - AWS: Add CloudFront request headers (
CloudFront-Viewer-Address) - AWS: Restrict access to Application Load Balancers (custom header, VPC origins)
- AWS: Restrict access to an Amazon S3 origin
- AWS: AWS-managed prefix lists (weights)
- nginx: ngx_http_realip_module; Apache: mod_remoteip