Skip to content
Muhammet Şafak
tr
Asked by: Oğuz Answered:

How do I handle panics in a Go HTTP server with recovery middleware?


Question

In our HTTP API written in Go, an unexpected panic occurred due to a nil pointer dereference. The panic caused the whole process to shut down abruptly and dropped every HTTP request that was in flight at the time. How do I set up a central middleware that uses Go's `recover()` to catch panics, log them and return a graceful 500 to the client?

Answer

Short answer: an unrecovered panic in one handler crashes the whole process and drops every in-flight request. The fix is to wrap each request in a recovery middleware — so a panic in one request is contained to that request.

Short answer

The real issue is this: in Go, if a goroutine panics and nobody recovers, the program shuts down entirely. Because of one nil deref, thousands of healthy requests die with you. I cover the details of building the service with the standard library in writing HTTP services in Go.

Why

  1. A panic is process-level, not request-level. Unrecovered, the blast radius covers the whole process, not one request.

  2. recover only catches its own goroutine. If you spawn go func(){...}() inside a handler, a panic there won’t hit your middleware’s recover.

  3. A silently swallowed panic is as dangerous as an unrecovered one. Without a metric, the same bug stays hidden for days.

What to do

  1. Wrap each request in defer recover(). Inside the middleware set up defer func(){ if r := recover(); r != nil { /* log + write 500 */ } }().

  2. Put this middleware at the outermost edge of the chain. Recovery must wrap the panics of every handler and middleware that comes after it.

  3. Log the stack + request id, but don’t leak internals. Log the stack trace with debug.Stack() and the request’s id; return a generic 500 to the client.

  4. Make the panic visible — emit a metric. Increment a counter on every recover.

  5. Give every goroutine you spawn its own recover. Otherwise the process still dies.

Bottom line: I’d set up a per-request recover middleware + a per-goroutine recover + metrics. Two caveats: recover is for unexpected bugs (nil deref), not a substitute for returning errors — return expected errors as error. And keep a supervisor that restarts the process anyway.

Related Reading

Expertise: Go Developer
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind