← Home

Rails & Cloudflare IP Country

When using Cloudflare DNS with proxying enabled, Cloudflare will enrich requests with the country code corresponding to the requesting ip address:

request.headers["CF-IPCountry"]

Sadly this will not work in development. Instead, we can “fake” it using a Rack middleware that asks Cloudflare for the country of the development machine itself:

# lib/middleware/cloudflare_ip_country_faker.rb
class CloudflareIpCountryFaker
  # For use in development, will pretend to be behind cloudflare
  # using ip and country of the development machine as seen by CF
  def initialize(app)
    @app = app
    response = Net::HTTP.get("cloudflare.com", "/cdn-cgi/trace")
    @trace = Hash[response.scan(/(.*)=(.*)/)]
  end

  def call(env)
    env["HTTP_X_FORWARDED_FOR"] = @trace["ip"]
    env["HTTP_CF_IPCOUNTRY"] = @trace["loc"]
    @app.call(env)
  end
end

Make sure to insert the middleware in your development environment:

# config/environments/development.rb
require "middleware/cloudflare_ip_country_faker"

Rails.application.configure do
  # ...
  config.middleware.insert_before ActionDispatch::RemoteIp, CloudflareIpCountryFaker
  Rails.backtrace_cleaner.add_silencer { |line| line =~ /lib\/middleware/ }
  # ...
end

We’ve added our middleware to the Rails.backtrace_cleaner silencer list, otherwise application errors might show up as originating from the middleware.

You should also add the middleware directory to your autolib ignore list:

# config/application.rb
config.autoload_lib(ignore: %w[assets tasks middleware])

And with that we can rely on the IP country header in both development and production!