# Welcome to Puma 8.0: Into the Arena.

Puma 8 brings IPv6 by default, increased control over the threadpool, and more.

Here's what you should do:

1. Review the Upgrade section below to look for breaking changes that could affect you.
2. Upgrade to version 8.0 in your Gemfile and deploy.
3. Open up a new bug issue if you find any problems.
4. Join us in building Puma! We welcome first-timers. See [CONTRIBUTING.md](./CONTRIBUTING.md).

For a complete list of changes, see [History.md](./History.md).

## What's New

### Smarter concurrency controls

**IO-bound requests can now go past your normal thread ceiling.** Puma 8 adds `max_io_threads` and injects `env["puma.mark_as_io_bound"]` into the Rack env so your app or middleware can tell Puma when a request has become mostly I/O wait. That helps mixed workloads a lot: slow API calls, report generation, and similar wait-heavy requests no longer need to crowd out CPU-bound work as aggressively. This landed in [#3816](https://github.com/puma/puma/pull/3816) and was refined in [#3894](https://github.com/puma/puma/pull/3894).

```ruby
# config/puma.rb
threads 0, 5
max_io_threads 5
```

```ruby
# config.ru
run lambda { |env|
  env['puma.mark_as_io_bound'].call
  report = SlowReportService.fetch

  [200, { 'content-type' => 'application/json' }, [report]]
}
```

We anticipate this will mainly by used by framework authors who have threads or types of requests they know are extremely IO-bound, and don't recommend it for use at the application level.

**Thread pool limits can be changed at runtime.** Puma now exposes `Puma::Server#update_thread_pool_min_max`, and hook/plugin code can do the same through `Puma::ServerPluginControl`.

```ruby
# from a plugin or other trusted in-process integration
server.update_thread_pool_min_max(min: 2, max: 12)
```

If you already use `before_thread_start`, note that hook behavior changed in this release; see the Upgrade section below.

### Cleaner config for single and cluster mode

**New `single` and `cluster` blocks let one config file express both modes cleanly.** These blocks run after config files are loaded, so you can keep the mode-specific settings in one obvious place instead of scattering `if` logic through `config/puma.rb`. This makes shared configs much easier to read and reuse across development, review apps, and production. See [#3621](https://github.com/puma/puma/pull/3621).

```ruby
workers ENV.fetch('WEB_CONCURRENCY', 0)

single do
  silence_fork_callback_warning
end

# Only runs if workers > 0
cluster do
  preload_app!
  before_worker_boot do
    # Do a thing
  end
end
```

### Better debugging and operations

**`shutdown_debug` can now be limited to forced shutdowns.** If you want thread backtraces only when a graceful shutdown turns into a forced one, use `shutdown_debug on_force: true`. That keeps normal deploy logs quieter while still giving you the "what is hanging?" escape hatch when you need it. See [#3671](https://github.com/puma/puma/pull/3671).

```ruby
shutdown_debug on_force: true
force_shutdown_after 30
```

**Thread backtrace via signal works on more platforms.** On systems without `SIGINFO`, Puma now uses `SIGPWR` for thread backtrace dumps. See [#3829](https://github.com/puma/puma/pull/3829).

**Phased restart is safer with `fork_worker`.** Puma no longer reforks from a stale worker 0 during phased restarts in `fork_worker` mode. There is nothing new to configure, but if you have tooling that watches worker order, check the Upgrade section because the rollout sequence changed. See [#3853](https://github.com/puma/puma/pull/3853).

### Networking and performance

**Puma now prefers IPv6 wildcard binds when the host supports them.** When Puma detects a non-loopback IPv6 interface, the default TCP host becomes `::` and the default bind becomes `tcp://[::]:PORT`. That lines Puma up better with modern dual-stack hosts, while still falling back to IPv4 when IPv6 is unavailable. See [#3847](https://github.com/puma/puma/pull/3847). If you need IPv4-only behavior, pin the bind explicitly.

```ruby
bind 'tcp://0.0.0.0:9292'
```

**There are also a couple of hot-path performance wins.** JRuby gets a faster HTTP parser with fewer copies and cheaper lookups, and Puma now avoids redundant header key downcasing when building responses. See [#3838](https://github.com/puma/puma/pull/3838) and [#3874](https://github.com/puma/puma/pull/3874).

## Upgrade

Check the following list to see if you're depending on any of these behaviors:

1. Puma will now listen on `::` (IPv6) by default. Previously, it listened to `0.0.0.0` (IPv4). Systems that support both IPv6 and IPv4 (Ubuntu and Mac commonly do) will still support receiving to `0.0.0.0`. See [[the support table in the PR](https://github.com/puma/puma/pull/3847)](https://github.com/puma/puma/pull/3847) for a binding compatibility. For systems that ONLY bind to IPv6 (without IPv4 support) this may be a breaking change. You can overwrite this default behavior by setting `bind 'tcp://0.0.0.0:9292'`, `port ENV.fetch('PORT', 9292), '0.0.0.0'`, or `set_default_host '0.0.0.0'` explicitly to remain IPv4 only. Review any firewall rules, health checks, deploy scripts, or host-string parsing code that assumed `0.0.0.0`, `127.0.0.1`, or unbracketed `HOST:PORT` formatting.
2. If you explicitly configure `bind 'tcp://[::]:...'`, `bind 'ssl://[::]:...'`, or equivalent `ssl_bind '::', ...`, Puma will now rewrite that unspecified IPv6 bind to `0.0.0.0` when the host has no non-loopback IPv6 interface, and it will warn on boot. If you were using `::` to force IPv6-only behavior or to detect IPv6 availability, switch to a concrete IPv6 address instead of `::`, or ensure the machine actually has a usable IPv6 interface before Puma starts.
3. `before_thread_start` hooks now receive a `Puma::ServerPluginControl` argument. Update any zero-arity lambdas, method objects, or other strict-arity hook code to accept one argument, for example `before_thread_start { |_control| ... }`, or Puma can raise `ArgumentError` when the hook runs.
4. On platforms without `SIGINFO`, Puma now traps `SIGPWR`, and `pumactl info` sends `SIGPWR` there. If your supervisor, init script, or container tooling already uses `SIGPWR` for something else, change that wiring before upgrading and update any runbooks that assumed Puma would ignore `SIGPWR`.
5. Requests rejected by `supported_http_methods` are now treated as parser/client errors earlier in request processing. If you use `supported_http_methods`, re-test unsupported-method requests and do not depend on the old 501 timing, connection handling, or `lowlevel_error_handler` behavior; if your error handler still sees these requests, make sure it tolerates a less-normalized Rack `env`.
6. `http_content_length_limit` enforcement is stricter now. A `413` on an HTTP/1.1 keep-alive request now forces `connection: close`, and chunked request bodies are rejected as soon as they cross the limit during parsing. Update clients, proxies, and tests not to reuse the socket after a `413`, and re-test any upload flows or custom error handling that depended on Puma's previous oversized-body behavior.
7. If you use `fork_worker`, phased restart order changed. During `USR1` or `pumactl phased-restart`, worker `0` is now reinserted and restarted/reforked first after replacement. Update deployment scripts, canary logic, or monitoring that assumed the old worker sequence or keyed rollout steps off worker index order.
