Rails 403 error
[ActionDispatch::HostAuthorization::DefaultResponseApp] Blocked hosts: app.example.com
"[ActionDispatch::HostAuthorization::DefaultResponseApp] Blocked hosts: app.example.com" is the line Rails logs when a request arrives for a host that config.hosts does not allow. The browser gets an empty 403 Forbidden. Rails checks the Host header and the last X-Forwarded-Host value, if there is one. In production config.hosts is empty by default and nothing is checked, so the error means something filled the list: config/environments/production.rb, an initializer or an agent's edit. Add your custom domain and the platform hostname to the list, exclude the health check path, and deploy again. If RAILS_ENV is not production, the development list applies instead.
What does "Blocked hosts" mean, and why is the page empty?
The Rails source (actionpack/lib/action_dispatch/middleware/host_authorization.rb) says the middleware guards against DNS rebinding attacks by permitting only the hosts you list. For a blocked request the default response app logs the line below at error level and answers 403 Forbidden. The body is empty unless config.consider_all_requests_local is true, so on a deployed app you usually see a blank page and have to read the log.
With that setting on, the page prints the second text below. Its template tells you to make sure each host contains only numbers, letters, dashes and dots, and then to add it to config.hosts.
[ActionDispatch::HostAuthorization::DefaultResponseApp] Blocked hosts: app.example.com
Blocked hosts: app.example.com
To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:
config.hosts << "app.example.com"How do I allow my custom domain on Render, Railway, Fly.io or Heroku?
The Rails guide says production starts with an empty list and no Host header checks, and that you add entries yourself. A string matches that host with or without a port. A dot at the start, as in ".example.com", permits the domain and all subdomains. A Regexp is wrapped in anchors, so it must match the whole hostname.
Allow every host that reaches the app, not only the custom domain. Render sets RENDER_EXTERNAL_HOSTNAME to the onrender.com hostname of a web service, and Railway sets RAILWAY_PUBLIC_DOMAIN to the public domain, of the form example.up.railway.app. On Fly.io and Heroku, copy the exact host from the Blocked hosts line and add it. Because the middleware also looks at X-Forwarded-Host, the line can list two hosts.
# config/environments/production.rb
config.hosts << "app.example.com"
config.hosts << ".example.com"
config.hosts << ENV["RENDER_EXTERNAL_HOSTNAME"] if ENV["RENDER_EXTERNAL_HOSTNAME"].present?
config.hosts << ENV["RAILWAY_PUBLIC_DOMAIN"] if ENV["RAILWAY_PUBLIC_DOMAIN"].present?
config.host_authorization = { exclude: ->(request) { request.path == "/up" } }Why does the deploy fail its health check, or only the development list apply?
Render says it sets one of your domains as the Host header of its health checks, your verified custom domain if you have one and the onrender.com address otherwise, and that a 4xx or 5xx response marks the instance unhealthy. A host missing from config.hosts therefore returns 403 to the check too. The Rails 8 production.rb template has an exclude for /up, the default health endpoint, but it is commented out. A request that matches exclude skips the host check.
If RAILS_ENV is not production on the host, the development list applies: .localhost, .test, all IPv4 and IPv6 addresses, and the hosts in RAILS_DEVELOPMENT_HOSTS. A domain name such as app.example.com is then blocked. Render lists RAILS_ENV as production for the Ruby runtime, so check that nothing overrides it.
Checklist
How to fix it, then check the fix
- 01Open the host's logs and copy every host after "Blocked hosts:". There may be two.
- 02Find every place the app sets the list or the environment:
grep -rnE "config\.hosts|host_authorization|RAILS_DEVELOPMENT_HOSTS|RAILS_ENV" config Dockerfile render.yaml fly.toml - 03In config/environments/production.rb add the custom domain, or ".example.com" for the domain and its subdomains, plus the platform hostname from its environment variable.
- 04Add config.host_authorization = { exclude: ->(request) { request.path == "/up" } } so the platform health check passes whatever its Host header is.
- 05Deploy, then request the site and the health endpoint:
curl -s -o /dev/null -w "%{http_code}\n" https://app.example.com/ curl -s -o /dev/null -w "%{http_code}\n" https://app.example.com/up - 06The codes are no longer 403 and the log has no new Blocked hosts line.
Related
Related guides
Sources
Official docs behind this checklist
- Rails source, actionpack/.../middleware/host_authorization.rb
- Rails source, the blocked_host rescue template
- Rails source, railties/.../application/configuration.rb (hosts default)
- Rails source, the production.rb template (hosts and /up exclude)
- Rails guides, Configuring (config.hosts, host_authorization)
- Render, Health checks (Host header, status codes)
- Render, Default environment variables (RENDER_EXTERNAL_HOSTNAME, RAILS_ENV)
- Railway, Variables reference (RAILWAY_PUBLIC_DOMAIN)
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
Why do I get 403 and not a Rails error page?
The default response app of the host middleware answers 403 Forbidden. The Rails source says the body carries debug info only if config.consider_all_requests_local is true, so in production the log line is usually the only message.
Can I just remove the config.hosts line?
Yes. The Rails guide says an empty list means no Host header checks, which is the production default. The middleware exists to stop DNS rebinding attacks, so keep the list if you want that protection.
Do I need to list the port?
No. In the Rails source a string without a port matches the host with or without one, and a Regexp is wrapped to allow an optional port.