Ruby & Rails

Everything that runs before your controller

A Rails request passes through twenty-odd middleware before your code sees it. Knowing the stack turns a class of mystery into a list.

24 June 2026 2 min read Mohammad Aaquib Jawed

bin/rails middleware prints the list. Most developers have never run it, and it explains a surprising number of otherwise baffling behaviours.

It is just a stack of callables

Each middleware receives the environment, may do something, calls the next one, and may do something with the response on the way back out. That symmetry is the whole design: code before the call runs on the way in, code after runs on the way out.

Rails ships a stack that handles static files, session cookies, parameter parsing, flash messages, host authorization, SSL redirects and exception rendering. Your application is the last thing in it.

Why it explains weird bugs

Several behaviours people attribute to "Rails magic" are just a middleware sitting earlier in the stack than their code.

A request rejected before any controller runs — that is host authorization, and it is why an unfamiliar Host header returns a blocked response rather than hitting a route.

A redirect to HTTPS that happens before your health check — that is the SSL middleware, and it is why an endpoint you excluded from authentication still redirects.

A static file served instead of your route — the static file middleware runs before the router, so a file in public shadows a path you have defined.

Each of these is impenetrable if you assume the request starts at your controller, and obvious once you have seen the list.

If your code is not being reached, something earlier in the stack answered. The stack is printable.

Adding your own

Middleware is the right place for anything that must apply to every request regardless of routing: request tagging, low-level metrics, blocking obviously hostile traffic before it costs you a full application boot.

It is the wrong place for anything needing application context. You have no session, no current user and no route information until later in the stack. Reaching for application state from middleware is a sign the logic belongs in a controller concern instead.

Position matters. Inserting before the session middleware means no session. Inserting after the exception handler means your errors will be caught by it.

Cost per request

Every middleware runs on every request, including ones your application will 404. That is usually negligible, and occasionally not — a middleware doing a database lookup or parsing a large body on every request is a fixed tax on your entire traffic.

It is also a security surface: middleware sees requests before routing, so a bug there is reachable on any path. Anything you add should be small enough to read in one sitting.

All writing Reply by email