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

CodeWhat it coversDirectionUse it for
CLOUDFRONTEdge servers and regional edge cachesViewers connect to these addressesRecognising CloudFront in DNS answers and logs; outbound rules for clients
CLOUDFRONT_ORIGIN_FACINGThe servers that fetch content from originsCloudFront connects to your origin from theseOrigin firewalls, and deciding whose X-Forwarded-For to trust

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:

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)

Sources