Most sites answer on two names, example.com and www.example.com, and on both http and https. That is four addresses for every page. Search engines treat them as separate pages with the same content, links and analytics get split between them, and a cookie set on one name may not be sent to the other.
The fix is to choose one address and permanently redirect the other three to it. On Apache that takes a few lines in the .htaccess file in the site's root folder.
It makes no difference to search ranking. Choose the one you already use on your business cards, in your email signature and in existing links, and keep to it. A bare domain is shorter. A www name is easier to point at a CDN, because a DNS CNAME record is allowed on www but not on the bare domain. This site uses the bare domain.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L,NE]
The condition matches any host name that starts with www. and captures the rest of it as %1. The rule sends the visitor to the same path on that name. Because the domain is captured rather than typed in, the same lines work unchanged on any site and on a staging copy.
The query string is carried across automatically, so www.example.com/page?id=5 goes to example.com/page?id=5.
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(.+)$
RewriteRule ^ https://www.%1%{REQUEST_URI} [R=301,L,NE]
The first condition skips host names that already start with www. Be careful with this one on a server that also answers on subdomains, because shop.example.com would become www.shop.example.com. On such a server use the fixed form instead:
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L,NE]
If you also redirect http to https as a separate rule, a visitor to http://www.example.com is bounced twice: first to https on www, then to the bare domain. Each hop costs a round trip. Combine them so every wrong address goes straight to the right one:
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L,NE]
Apache reads the first two conditions as either: the request is plain http, or the host starts with www. The third condition always matches; its job is to capture the host name without the www, ready for the rule. The correct address, https://example.com, passes the first two conditions and is left alone.
The browser checks the certificate before Apache sees the request, so a visitor to https://www.example.com gets a security warning, not your redirect, if the certificate only covers example.com. Make sure it lists both names. AutoSSL in cPanel and Let's Encrypt usually include www when its DNS record exists.
A permanent redirect is cached by the browser, often until the cache is cleared. A mistake in the rule keeps happening to you even after you fix the file. While testing use R=302, and change it to R=301 once it works. Test with curl, which caches nothing.
Behind Cloudflare in Flexible mode, or a load balancer that handles HTTPS, Apache receives every request as plain http, so %{HTTPS} off is always true and the site redirects forever. Either set Cloudflare to Full (strict), or test the header the proxy adds instead:
RewriteCond %{HTTP:X-Forwarded-Proto} !https [OR]
Requests for www never reach the server, and so are never redirected, unless www has a DNS record pointing at it. Add an A record for www, or a CNAME to the bare domain.
Ask for each wrong address with curl and read the Location header:
curl -I http://example.com/page
curl -I http://www.example.com/page
curl -I https://www.example.com/page?id=5
Each one should return a single redirect straight to the final address:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/page
And the final address itself should return 200 with no Location header. Two 301s in a row, or a Location pointing at a .php file, means the rules are in the wrong order.
| Goal | Conditions | Rule target |
|---|---|---|
| www to non-www | %{HTTP_HOST} ^www\.(.+)$ |
https://%1%{REQUEST_URI} |
| non-www to www | %{HTTP_HOST} !^www\. then ^(.+)$ |
https://www.%1%{REQUEST_URI} |
| https and non-www | %{HTTPS} off [OR] www, then capture without www |
https://%1%{REQUEST_URI} |
Please chip in if helpful