#3985·puma

NoMethodError in Client#parser_execute when the read buffer is only CRLFs (bare `\r\n\r\n` request)

Author: roque86Created Aug 5, 2026Updated Aug 5, 2026

Describe the bug

The rescue block in Puma::Client#parser_execute that enriches HttpParserError messages raises NoMethodError: undefined method 'split' for nil when the read buffer contains nothing but CRLFs — for example when a client sends a bare \r\n\r\n.

https://github.com/puma/puma/blob/master/lib/puma/client.rb#L314-L315

ruby
req, _ = @buffer.split "\r\n\r\n"
request_line, headers = req.split "\r\n", 2

String#split discards trailing empty fields, so "\r\n\r\n".split("\r\n\r\n") returns [], req is nil, and the next line raises. The same happens for any number of repeated separators:

ruby
"".split("\r\n\r\n")                  # => []            -> req = nil
"\r\n\r\n".split("\r\n\r\n")          # => []            -> req = nil
"\r\n\r\n\r\n\r\n".split("\r\n\r\n")  # => []            -> req = nil
"\r\n\r\nfoo".split("\r\n\r\n")       # => ["", "foo"]   -> req = "" (fine)

Worth noting that this rescue path is not limited to actual TLS-on-plaintext-port connections. The parser raises that same "…non-SSL Puma?" message for any malformed request (ext/puma_http11/puma_http11.c#L399, and Http11.java for JRuby), so e.message.include?('non-SSL') matches ordinary malformed input too.

Two consequences:

  1. The accurate, actionable HttpParserError ("Invalid HTTP format, parsing fails…") is replaced by a NoMethodError that points at Puma internals.
  2. Because NoMethodError is not an HttpParserError, Server#client_error falls through to the else branch and responds 500 instead of 400.

The reactor itself is unaffected — Server#reactor_wakeup rescues, calls client_error and closes the client — so this is not a stability problem, just a wrong status code and a lost diagnostic.

Puma config:

None; reproduced with the hello-world app from this template and no config file:

bundle exec puma -C - -b tcp://127.0.0.1:9292 hello.ru

To Reproduce

hello.ru:

ruby
run lambda { |env| [200, {"content-type" => "text/plain"}, ["Hello World"]] }

Send a bare CRLFCRLF:

$ printf '\r\n\r\n' | nc 127.0.0.1 9292
HTTP/1.0 500 Internal Server Error
content-length: 573

Puma caught this error: undefined method 'split' for nil (NoMethodError)
/usr/local/bundle/gems/puma-8.0.1-java/lib/puma/client.rb:297:in 'parser_execute'
/usr/local/bundle/gems/puma-8.0.1-java/lib/puma/client.rb:264:in 'try_to_finish'
...

It can also be shown at the parser level, without a server:

ruby
require "puma"

begin
  Puma::HttpParser.new.execute({}, "\r\n\r\n", 0)
rescue => e
  puts e.class                     # Puma::HttpParserError
  puts e.message                   # Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?
  p "\r\n\r\n".split("\r\n\r\n")   # => []   <- req is nil in parser_execute
end

Expected behavior

The original HttpParserError should surface (400), rather than a NoMethodError (500). Something like:

ruby
req, _ = @buffer.split "\r\n\r\n"
raise e if req.nil?

Happy to open a PR if you'd like.

Desktop (please complete the following information):

  • OS: Linux
  • Puma Version: 8.0.1 (also present unchanged on master at lib/puma/client.rb#L314-L315, and in the 8.0.2 docs)
  • Ruby Version: jruby 10.0.5.0 (3.4.5) / OpenJDK 21.0.11. The failing code is plain Ruby and the Java and C parsers raise the same message, so I would expect CRuby to behave identically, though I have only reproduced it on JRuby.

How we noticed

A handful of these per day in production behind a load balancer, presumably scanners or broken clients opening a connection and sending an empty request.