Direct Answer
Rails wildcard routing must not rely on real-time DNS resolution for domain validation in production. The routing layer performs a Resolv lookup on every request host before matching get '*path' constraints, and the default 5-second timeout causes intermittent "No route matches" errors when DNS is slow. SSL certificate CN/SAN fields are never consulted by the router, so a wildcard cert does not eliminate this lookup.
Why DNS Resolution Fails Intermittently
ActionDispatch resolves the request host to an IP address to confirm the domain exists. Under load or with external DNS providers, Resolv::DNS can exceed its timeout, raising Resolv::DNS::RequestError and causing the route match to fail. Rails 7.0+ adds DnsCache (backed by ActiveSupport::Cache::Store) with a 60-second TTL for successful lookups only—negative results are not cached, so every failed lookup retries on the next request.
DnsCache Behavior for Rotating Subdomains
- Cache key: the requested host (e.g.,
tenant123.example.com). - Entry stored only on successful resolution; TTL defaults to 60 seconds (
config.action_dispatch.dns_cache_ttl). - No automatic invalidation on DNS changes—stale IPs persist until TTL expiry.
- Cache size defaults to 1000 entries (
config.action_dispatch.dns_cache_size); LRU eviction applies.
Rapid subdomain rotation fills the cache with single-use entries, reducing hit rates and increasing DNS pressure.
Recommended Mitigation
- Move domain validation out of routing. Use a Rack middleware that checks
request.host against an allow-list (from config, database, or certificate SANs) before the router runs. This eliminates DNS lookups entirely. - If DNS validation is required (e.g., dynamic tenant onboarding), tune Resolv and the cache:
# config/environments/production.rb
config.action_dispatch.dns_cache_ttl = 300 # 5 minutes
config.action_dispatch.dns_cache_size = 5000
# Increase Resolv timeout (global, affects all Resolv usage)
Resolv::DNS.default_config[:timeout] = 10 # seconds
Resolv::DNS.default_config[:retry] = 2
- Pre-warm the cache for known subdomains at boot or via a background job.
Verification Steps
- Search production logs for
Resolv::DNS::RequestError: Timeout was reached coinciding with routing errors. - Enable cache instrumentation:
config.action_dispatch.dns_cache_logger = true (Rails 7.1+) or subscribe to dns_cache.action_dispatch notifications to measure hit/miss ratios. - Load-test staging with rotating subdomains (e.g.,
ab -n 10000 -c 100 -H "Host: tenant$RANDOM.example.com" https://app.example.com/) and confirm error rate drops after tuning.
One Missing Diagnostic
What is your current config.action_dispatch.dns_cache_ttl and dns_cache_size in production? If they are at defaults, increasing them is the lowest-risk first step; if already tuned, the middleware allow-list approach is necessary.