WAF Egress IP Addresses

Overview

The Expedited WAF works as a reverse proxy. Visitors connect to the WAF edge network, the WAF applies your security rules, and allowed requests are then forwarded on to your Heroku application.

Visitor WAF Edge Network Your Heroku App

Because of this, every request that reaches your application is sent by one of the WAF’s egress IP addresses, not by the visitor directly. The visitor’s original IP address is passed along in the X-Forwarded-For header.

The IP ranges

Requests from the WAF to your application will always come from these five ranges:

192.88.134.0/23
185.93.228.0/22
66.248.200.0/22
208.109.0.0/22
2a02:fe80::/29

How often do these ranges change?

Essentially never. The list has not changed in over five years.

The ranges are fixed

These are fixed and should they ever be added to, we will notify all users within an appropriate timeframe.

Common uses

Allowing WAF traffic through application-level IP filtering

If your application filters or throttles by client IP (for example with Rack::Attack or similar middleware), make sure these five ranges are never blocked. The client IP your framework reports for a request (Rack’s req.ip, Rails’ request.remote_ip) is normally a WAF address, so all of your legitimate visitor traffic appears to come from them.

For the same reason, rate limiting should key on the visitor’s own address, which the WAF passes in the X-Real-IP header, rather than on that reported client IP. From your app’s point of view, every visitor shares the same small pool of WAF addresses, so per-IP throttles keyed on the reported client IP will lump all of your visitors together.

Only trust X-Real-IP on requests that came through the WAF. Anyone who reaches your Heroku app URL directly can send the header with any value, so lock your app down to WAF traffic first (see “Verifying that requests came through the WAF” below).

Verifying that requests came through the WAF

To accept only requests that passed through the WAF, and reject anything sent straight to your Heroku app URL, check the address that connected to Heroku against these ranges inside your application.

On the Heroku Common Runtime, the connection your dyno sees always comes from Heroku’s router, so the socket’s source IP can’t tell you anything. Instead, Heroku’s router appends the address that connected to it as the last entry of the X-Forwarded-For header. For a request through the WAF, that last entry is a WAF address. Check that entry:

  • Use the last entry, never the first. The first entry is the visitor’s address, and anyone can put any value there by sending their own X-Forwarded-For header.
  • If you’ve added these ranges to Rails’ config.action_dispatch.trusted_proxies, don’t use request.remote_ip for this check: Rails skips trusted addresses, so it returns the visitor’s address instead of the WAF’s.
  • There’s no secret header the WAF can add to prove a request came through it. The IP ranges are the check.

For a Rails app, a Rack::Attack blocklist does this:

# config/initializers/rack_attack.rb
require "ipaddr"

EXPEDITED_WAF_RANGES = %w[
  192.88.134.0/23 185.93.228.0/22 66.248.200.0/22 208.109.0.0/22 2a02:fe80::/29
].map { |range| IPAddr.new(range) }.freeze

Rack::Attack.blocklist("direct to origin") do |req|
  next false unless Rails.env.production?

  connecting_ip = req.get_header("HTTP_X_FORWARDED_FOR").to_s.split(",").last.to_s.strip
  ip = (IPAddr.new(connecting_ip) rescue nil)
  ip.nil? || EXPEDITED_WAF_RANGES.none? { |range| range.include?(ip) }
end

Requests sent straight to your Heroku app URL then get a 403. The check runs only in production: in local development there’s no X-Forwarded-For header, so every request would be blocked. Webhooks and other services that call your herokuapp.com URL directly, rather than your custom domain, are blocked too, so point them at your custom domain first. The same check works in any framework: split the header on commas, take the last entry, and compare it to the five ranges.

Heroku Private Space customers can enforce this at the network level instead, so that direct-to-app requests are rejected before they reach a dyno. See the article “Heroku Private Space Trusted IP Range Configuration” for that setup.

What these ranges are not

These are the addresses the WAF connects from when forwarding visitor requests to your application. They are unrelated to outbound requests your application makes to other services, which leave directly from your Heroku dyno and never pass through the WAF. See “WAF and Outbound Connections” for more detail.

Need Help?