Redirects are an important part of the web, and Rails gives us a clean and expressive way to use them with the redirect_to
helper method.
But what's actually going on when you call redirect_to
?
It's easy to treat it as a magical Rails incantation that just sends users elsewhere, but it's important to understand how it really works. Knowing what happens at the HTTP level, and how Rails constructs that response, helps you reason about edge cases, debug redirect loops, or customize behavior in advanced scenarios.
Let’s walk through how redirects work, how to implement one manually in Rails, how the redirect_to
helper simplifies things, and what’s actually happening under the hood.
Note: This post focuses on redirects from Rails controllers. For router-level redirects, check out the following post from my Rails Router Handbook: How to Redirect Requests from Rails Router

What Is a Redirect?
A redirect is an instruction sent by a server to a browser (or any HTTP client), telling it to make a new request to a different URL.
At its core, an HTTP redirect is a response from the server that includes a specific status code and a Location
header, instructing the browser to initiate a new request to a different URL.
For example, here’s what a basic HTTP redirect response might look like:
HTTP/1.1 302 Found
Location: /login
Content-Length: 0
Content-Type: text/html; charset=utf-8
When a browser receives a response with a 3xx
HTTP status and a Location
header, it knows it needs to stop processing the current response and initiate a new request to the URL specified in that header.
There are two broad types of redirects: temporary and permanent.
- Permanent redirects (like
301 Moved Permanently
or308 Permanent Redirect
) signal that the resource has been permanently moved to a new location. Search engines will update their indexes and transfer SEO ranking to the new URL. - Temporary redirects (like
302 Found
,303 See Other
, or307 Temporary Redirect
) indicate that the resource has been moved, but only temporarily. Clients should continue to use the original URL for future requests.
Choosing the correct type of redirect is especially important for SEO:
- Use a
301
or308
if you want search engines to index the new URL and deprecate the old one. - Use a
302
,303
, or307
if the change is temporary or during A/B testing.
Search engines interpret these status codes differently, and misuse can lead to ranking loss or indexing errors. When in doubt, consider what you want to happen over the long term.
How Status Codes Affect the Follow-up Request
Redirect status codes don’t just tell the browser where to go, but they also affect how the follow-up request is made. Specifically, some redirect codes instruct the browser to discard the original request method and body, and use a simple GET instead. Others preserve the original method and payload.
Here’s how different status codes handle method preservation:
- 301 Moved Permanently, 302 Found: The behavior varies by client. Many browsers treat it as a GET redirect, even if the original request was a POST. This is for historical reasons and can lead to inconsistent behavior.
- 303 See Other: Clearly instructs the client to make a GET request to the new location, regardless of the original method. This is the safest and most predictable way to redirect after form submissions.
- 307 Temporary Redirect, 308 Permanent Redirect: Preserves the original HTTP method and body. If the original request was a POST, the redirected request will also be a POST.
This distinction is subtle but important. If you’re redirecting after a destructive or sensitive action, and want to ensure the client switches to a safe, idempotent method like GET, choose 303 See Other
. It will always result in a GET.
If you want to preserve the original method and payload, use 307
or 308
(for example, when redirecting between API endpoints that expect POST bodies).
Redirecting Manually in Rails
Before we look at how to use the redirect_to
method in Rails, let’s take a moment to understand what it’s actually doing under the hood. Rails provides the helper to abstract away the complexity, but it’s worth knowing how you could implement a redirect manually.
The goal here is to send an HTTP response that tells the browser: "Stop what you're doing and go load this other URL instead." To do that, we need to manually set the correct status code (usually 302 for a temporary redirect), set the Location
header, and ensure the response body is appropriately empty.
Let’s see how you’d implement a basic redirect in Rails without using redirect_to
. In this example, our goal is to log the user out and then redirect them to the root path of the application, i.e. the homepage.
class SessionsController < ApplicationController
def destroy
reset_session
response.status = 302
response.headers['Location'] = root_path
self.response_body = nil
end
end
This does the following:
- It sets the HTTP status to
302 Found
(the default for temporary redirects). - It sets the
Location
header to the destination URL. - It clears the response body.
But as you can see, it’s verbose and easy to get wrong. Forgetting to set the status code or leaving a response body in can cause unexpected behavior.
That’s where redirect_to
comes in.
The redirect_to Helper
Rails provides a convenient method: redirect_to
, which wraps up all the low-level redirect logic into one clean call.
class SessionsController < ApplicationController
def destroy
reset_session
redirect_to root_path
end
end
Much simpler.
The redirect_to
method automatically:
- Sets the correct status code (
302 Found
by default). - Sets the
Location
header. - Ensures the response body is empty.
It also gives you flexibility. You can customize the status code:
redirect_to root_path, status: :see_other
Or redirect to full URLs, named routes, polymorphic paths, or even use fallback locations with redirect_back
.
Exploring Options for redirect_to
The redirect_to
helper supports a variety of inputs, making it flexible and expressive. According to the Rails documentation, here are the supported formats:
String URL: Redirects to a full URL. If the host + protocol is not provided, the current protocol and host is prepended to the string.
redirect_to "https://example.com"
redirect_to "/projects"
Named route or path helper: Redirects using a named route.
redirect_to dashboard_path
Record: The URL will be generated by calling url_for
with the options
, which will reference a named URL for that record.
redirect_to @product
Hash of route params: The URL will be generated by calling url_for
with the options
.
redirect_to controller: 'products', action: 'show', id: 1
Custom status code: You can pass status as a symbol (:see_other
) or an integer (301
).
redirect_to root_path, status: :moved_permanently
Proc: A block that will be executed in the controller’s context.
redirect_to proc { edit_post_url(@post) }
All these options make redirect_to
a very flexible tool in your Rails toolbox.
Redirecting to External URLs Safely
By default, Rails
protects against redirecting to external hosts for your app’s safety, so called open redirects. For example, the following redirect will raise an UnsafeRedirectError
error.
redirect_to params[:redirect_url]
If you intend to redirect to an external site, and you’re certain the destination is safe, you can explicitly opt into it:
redirect_to "https://rubyonrails.org", allow_other_host: true
This bypasses the open redirect protection, but should be used with caution. If the URL comes from user input, it’s better to validate it against an allowlist or redirect to a known fallback.
Redirect Users Back to Where They Came From
Sometimes, you want to to return users to the page they were previously on, especially after performing an action like signing in, submitting a form, or dismissing a modal. Rails provides a built-in helper method for this purpose: redirect_back_or_to
.
This method attempts to redirect the user to the referrer (i.e., the page that linked to the current request). If that information is unavailable, either due to browser privacy settings, security policies, or the request simply lacking the Referer
header, then Rails will instead redirect to a specified fallback location.
redirect_back_or_to({ action: "show", id: 5 })
redirect_back_or_to @post
redirect_back_or_to "http://www.rubyonrails.org"
redirect_back_or_to "/images/screenshot.jpg"
redirect_back_or_to posts_url
redirect_back_or_to proc { edit_post_url(@post) }
redirect_back_or_to '/', allow_other_host: false
How It Works
Under the hood, redirect_back_or_to
checks the Referer
header (request.referer
). If that value is present and considered safe, it issues a redirect to it. Otherwise, it falls back to the location you provide.
Modern browsers may omit the Referer
header in certain cases. For example, if the navigation was triggered by a cross-origin request. Because of this, always supply a reliable fallback.
Under the Hood: How redirect_to Method Works
Here’s the source code for the redirect_to
method:
def redirect_to(options = {}, response_options = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
proposed_status = _extract_redirect_to_status(options, response_options)
redirect_to_location = _compute_redirect_to_location(request, options)
_ensure_url_is_http_header_safe(redirect_to_location)
self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
self.response_body = ""
self.status = proposed_status
end
As you can see, it raises an error if you pass nil
or if a response was already set, preventing you from sending two responses (e.g., calling render
and then redirect_to
in the same action).
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
Then it determines if it's safe to redirect to a URL on a different host. This is part of open redirect protection (default is not allowed unless explicitly permitted).
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
Next, it figures out the HTTP status code for the redirect, like 302
, 303
, or 307
, using provided options or defaults.
proposed_status = _extract_redirect_to_status(options, response_options)
Converts options
into a full URL string. For example:
redirect_to post_path(@post)
→"/posts/123"
redirect_to "https://example.com"
→ same string
It also sanitizes the location to ensure it can safely be included in the HTTP Location
header without causing malformed responses.
redirect_to_location = _compute_redirect_to_location(request, options)
_ensure_url_is_http_header_safe(redirect_to_location)
Then it sets the Location
header based on the URL, route, or options you pass in. Additionally, it also ensures that the redirect is either:
- within the same host (
allow_other_host: false
), or - explicitly allowed (
allow_other_host: true
)
self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
Finally, it sets the response to be empty (""
) because browsers don’t care about the body for redirects. It also applies the HTTP status (302 Found
by default).
self.response_body = ""
self.status = proposed_status
The heavy lifting is done by _compute_redirect_to_location
, which supports the different formats Rails allows: strings, symbols, hashes, route helpers, or full URLs.
def _compute_redirect_to_location(request, options)
case options
# The scheme name consist of a letter followed by any combination of letters,
# digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
# terminated by a colon (":"). See
# https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
# starts with a double slash "//".
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
options.to_str
when String
request.protocol + request.host_with_port + options
when Proc
_compute_redirect_to_location request, instance_eval(&options)
else
url_for(options)
end.delete("\0\r\n")
end
I think it's pretty self-explanatory.
url_for
method and try to understand how it works. Pay attention to the different types of arguments it accepts and the various ways you can use it in a Rails application.That's a wrap. I hope you found this article helpful and you learned something new. Redirects are simple in principle, but easy to mishandle in practice. Rails’ redirect_to
helper takes care of the details for you: setting the right status, and the Location
header, and emptying out the response body.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.