🔥 Heroku R14 Errors in Rails? Fix Puma Before You Lose Your Mind

🔥 Heroku R14 Errors in Rails? Fix Puma Before You Lose Your Mind

If your Rails app on Heroku is constantly throwing R14 memory errors and running like molasses, check your Puma config first.

🛠️ The Quick Fix

If you're on a 512MB Heroku dyno (like the Basic or Free plan), do not run multiple Puma workers. Just set your Puma config like this:

# config/puma.rb

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
# ❌ No workers, no preload_app!

Remove or comment out any workers or preload_app! calls.


😅 Quick Personal Note

I’ve been working as a developer for about 8 years — mostly on product and feature work — but I don’t touch production-level infrastructure stuff very often.

So when my Heroku app started acting up with R14 errors, I figured, “How hard could it be to fix?”

It took me months.
Not because the answer was complicated — but because it was buried in Puma and Heroku nuance I’d never needed to know until now.


🧠 What Was Happening (And Why Nothing Else Worked)

I tried everything first:

  • Removing gems
  • Refactoring memory-heavy code
  • Profiling allocations
  • Reading Stack Overflow and random blog posts

None of it helped.

Eventually, I compared the Puma config of this slow, crashing app with another Rails app I had running fine on the same dyno size.

The broken one had:

if ENV["RAILS_ENV"] == "production"
  require "concurrent-ruby"
  worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
  workers worker_count if worker_count > 1
end

That line was silently spawning multiple Puma workers — and chewing through memory like crazy.


🚨 Why This Breaks Your App on Heroku

Each Puma worker is a full process with its own memory space. If you're on a 512MB dyno and you spawn 2 workers, you're instantly in trouble.

  • 2 workers × ~400–500MB each = 🚨
  • Heroku starts throwing R14 (Memory Quota Exceeded) errors
  • If it gets worse, R15 and crash
  • App starts swapping memory to disk = slow
  • Response times spike to multiple seconds

📉 The Before & After

After switching to a single Puma process with threads only:

✅ Memory Usage:

  • Before: Peaked at ~1GB, avg ~723MB
  • After: Stable around 250MB

✅ R14 Errors:

  • Before: Endless
  • After: Gone

✅ Response Time:

  • Before: Regular 2–4 second spikes
  • After: Consistent ~150ms

🧐 Why Puma Recommends Workers (But You Shouldn’t Use Them)

Puma’s docs and many blog posts talk about using workers + threads for performance. And that’s true — if you have the memory.

On Heroku’s Standard-1X (1GB+) or on your own VPS, clustering with workers can help throughput.

But on Heroku Free or Basic? One worker is all you can afford. Threads are your friend here.


✅ TL;DR

If you're on a 512MB dyno:

  • Use threads only (5 is safe)
  • Avoid workers and preload_app!
  • Enjoy smooth sailing

Final Thought

I'm sharing this because I wish someone had shared it with me. Debugging memory issues in production Rails apps is hard enough — no one needs to be blindsided by a default Puma config that silently wrecks performance on Heroku.

If you're struggling with R14s or slow response times, take 5 minutes to check your puma.rb. It might just save you months of head-scratching.

Back to blog

Leave a comment