Redirect non-www requests to www URLs in Ruby on Rails

Ideally you'd do this in your web server (Apache, nginx etc.) configuation so that the request doesn't even touch Rails at all.

Add the following before_filter to your ApplicationController:

class ApplicationController < ActionController::Base
  before_filter :add_www_subdomain

  private
  def add_www_subdomain
    unless /^www/.match(request.host)
      redirect_to("#{request.protocol}x.com#{request.request_uri}",
                  :status => 301)
    end
  end
end

If you did want to do the redirect using Apache, you could use this:

RewriteEngine on
RewriteCond %{HTTP_HOST} !^www\.x\.com [NC]
RewriteRule ^(.*)$ http://www.x.com/$1 [R=301,L]

For rails 4, use it -

  before_filter :add_www_subdomain

  private
  def add_www_subdomain
    unless /^www/.match(request.host)
      redirect_to("#{request.protocol}www.#{request.host_with_port}",status: 301)
    end
  end

While John's answer is perfectly fine, if you are using Rails >= 2.3 I would suggest to create a new Metal. Rails Metals are more efficient and they offers better performance.

$ ruby script/generate metal NotWwwToWww

Then open the file and paste the following code.

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class NotWwwToWww
  def self.call(env)
    if env["HTTP_HOST"] != 'www.example.org'
      [301, {"Content-Type" => "text/html", "Location" => "www.#{env["HTTP_HOST"]}"}, ["Redirecting..."]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

Of course, you can customize further the Metal.

If you want to use Apache, here's a few configurations.


There is a better Rails 3 way - put this in your routes.rb file:

  constraints(:host => "example.com") do
    # Won't match root path without brackets around "*x". (using Rails 3.0.3)
    match "(*x)" => redirect { |params, request|
      URI.parse(request.url).tap { |x| x.host = "www.example.com" }.to_s
    }
  end

Update

Here is how to make it domain agnostic:

  constraints(subdomain: '') do
    match "(*x)" => redirect do |params, request|
      URI.parse(request.url).tap { |x| x.host = "www.#{x.host}" }.to_s
    end
  end