{"record":{"id":"f4eb0c9a0cc27942","repo":"socketry/falcon","slug":"unable-to-resolve-hostname","errorCode":null,"errorMessage":"Unable to resolve #{hostname}!","messagePattern":"Unable to resolve #(.+?)!","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/falcon/environment/proxy.rb","lineNumber":64,"sourceCode":"\t\t\t\tend\n\t\t\t\t\n\t\t\t\treturn hosts\n\t\t\tend\n\t\t\t\n\t\t\t# Look up the host context for the given hostname, and update the socket hostname if necessary.\n\t\t\t# @parameter socket [OpenSSL::SSL::SSLSocket] The incoming connection.\n\t\t\t# @parameter hostname [String] The negotiated hostname.\n\t\t\tdef host_context(socket, hostname)\n\t\t\t\thosts = self.hosts\n\t\t\t\t\n\t\t\t\tif host = hosts[hostname]\n\t\t\t\t\tConsole.debug(self){\"Resolving #{hostname} -> #{host}\"}\n\t\t\t\t\t\n\t\t\t\t\tsocket.hostname = hostname\n\t\t\t\t\t\n\t\t\t\t\treturn host.ssl_context\n\t\t\t\telse\n\t\t\t\t\tConsole.warn(self, hosts: hosts.keys){\"Unable to resolve #{hostname}!\"}\n\t\t\t\t\t\n\t\t\t\t\treturn nil\n\t\t\t\tend\n\t\t\tend\n\t\t\t\n\t\t\t# Generate an SSL context which delegates to {host_context} to multiplex based on hostname.\n\t\t\tdef ssl_context\n\t\t\t\t@server_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|\n\t\t\t\t\tcontext.servername_cb = Proc.new do |socket, hostname|\n\t\t\t\t\t\tself.host_context(socket, hostname)\n\t\t\t\t\tend\n\t\t\t\t\t\n\t\t\t\t\tcontext.session_id_context = self.ssl_session_id\n\t\t\t\t\t\n\t\t\t\t\tcontext.set_params(\n\t\t\t\t\t\tciphers: ::Falcon::TLS::SERVER_CIPHERS,\n\t\t\t\t\t\tverify_mode: ::OpenSSL::SSL::VERIFY_NONE,\n\t\t\t\t\t)","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/socketry/falcon/blob/5107b0713fd78dbe8b08ee123391b1f4bcffdf12/lib/falcon/environment/proxy.rb#L46-L82","documentation":"This is a Console warning, not a raised exception. The proxy environment builds a hosts hash keyed by each upstream environment's authority, but only environments defining all three keys :authority, :ssl_context and :endpoint are registered (lib/falcon/environment/proxy.rb:35-49). During the TLS handshake, OpenSSL's SNI callback (servername_cb installed by ssl_context) calls host_context with the hostname the client sent; when it is not a key in hosts, Falcon warns 'Unable to resolve <hostname>!', logs the known hostnames, and returns nil — so no per-host certificate context is selected and the handshake fails.","triggerScenarios":"A client connects with SNI 'localhost', a bare IP, or any hostname that differs from the configured authority (www.example.com vs example.com); or a proxied environment is silently excluded from hosts because it lacks one of authority/ssl_context/endpoint, so its hostname can never resolve.","commonSituations":"Local testing with curl https://localhost/ against a proxy configured only for the production hostname; adding a vhost but forgetting the authority or ssl_context key; extra DNS aliases or CNAMEs pointing at the proxy that were never declared as environments; monitoring probes connecting by IP without a matching SNI name.","solutions":["Register the missing hostname: add an environment whose authority exactly matches the SNI name clients send (each must define authority, ssl_context and endpoint)","For local tests, connect with the configured name: curl --resolve app.example.com:443:127.0.0.1 https://app.example.com/","Audit Falcon::Environment::Proxy#hosts.keys and add any DNS alias as its own environment"],"exampleFix":"# before: proxy only registers authority 'app.example.com'\n#   curl https://localhost/ -> Console.warn: Unable to resolve localhost!\n#   host_context returns nil and the TLS handshake fails\nclass ProxyEnvironment\n  include Falcon::Environment::Proxy\n\n  def environments\n    [app_environment] # authority 'app.example.com'\n  end\nend\n\n# after: every hostname the proxy answers is registered\nclass ProxyEnvironment\n  include Falcon::Environment::Proxy\n\n  def environments\n    [app_environment, localhost_environment]\n    # authorities: 'app.example.com', 'localhost'\n    # each environment defines :authority, :ssl_context and :endpoint,\n    # otherwise Proxy#hosts silently skips it\n  end\nend","handlingStrategy":"validation","validationCode":"# Before exposing the proxy: assert every served hostname is registered\nhosts = proxy_environment.hosts # keys are exactly the SNI names answered\nexpected = %w[app.example.com www.app.example.com localhost]\n\nmissing = expected - hosts.keys\nabort \"Proxy has no host context for: #{missing.join(', ')}\" unless missing.empty?","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Assert at boot that every DNS name pointing at the proxy appears in proxy_environment.hosts.keys","Give each proxied environment authority, ssl_context and endpoint — missing any one silently drops it from hosts","Test with the real SNI name (curl --resolve host:443:127.0.0.1 https://host/), not localhost or an IP","Alert on Console 'Unable to resolve' lines in production logs; each one is a client failing SNI"],"tags":["falcon","tls","sni","reverse-proxy","hostname","ruby"],"backgroundTag":"sni-hostname-mismatch","analyzedSha":"5107b0713fd78dbe8b08ee123391b1f4bcffdf12","analyzedAt":"2026-08-23T14:31:56.885Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}