A site that answers on both www.example.com and example.com looks like two copies of the same content to search engines, and cookies or logins can behave oddly between them. The fix is to choose one hostname and permanently redirect the other to it. This guide shows the clean Nginx way: a small, separate server block that does nothing but return 301.
Before you start
- A Linux VPS with Nginx serving your site. See Host multiple websites with Nginx server blocks if you have not set one up.
- DNS records for both names pointing at the server. A redirect can only happen if the request reaches Nginx first.
- A certificate that covers both names if you use HTTPS. With Certbot that means requesting
-d example.com -d www.example.com. - SSH access with sudo.
Choose your main hostname
Either choice is fine for search ranking. What matters is picking one and sticking to it.
- non-www (
example.com): shorter and common for new sites. - www (
www.example.com): useful if you want to point the root and www at different services, or rely on CNAME-based CDNs.
Make sure the site's own settings agree. In WordPress, for example, Settings → General must use the same hostname, or WordPress and Nginx will redirect back and forth.
Why a separate server block
You could write an if ($host =...) test inside your main block, but Nginx evaluates if on every request and it is easy to get wrong. A dedicated server block for the name you are redirecting away from is faster, clearer and cannot loop with your main site.
return 301 is also better than rewrite for this job. It stops processing immediately and sends the redirect.
Redirect www to non-www over HTTPS
This is the common setup once a site has SSL. Edit the site's file, for example /etc/nginx/sites-available/example.com on Ubuntu or Debian, or /etc/nginx/conf.d/example.com.conf on AlmaLinux and Rocky Linux. The file below has three blocks: the first sends all plain HTTP to the final address, the second redirects HTTPS on www, and the third is the real site.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public;
index index.html index.php;
}Key points:
$request_urikeeps the path and query string, sohttps://www.example.com/blog?page=2lands onhttps://example.com/blog?page=2.- The www HTTPS block still needs a certificate. The browser checks the certificate before it receives the redirect, so without one visitors see a security warning.
- The HTTP block sends visitors to the final address in one hop instead of two.
Note: If Certbot manages your config, it may have added its own include lines and redirect blocks. Keep its ssl_certificate lines, and replace any duplicate redirect logic with the blocks above so there is only one rule per hostname.
Redirect non-www to www
The reverse is the same pattern with the names swapped:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://www.example.com$request_uri;
}Your main site block then uses server_name www.example.com;.
Sites without HTTPS
If the site has no certificate yet, a single extra block on port 80 is enough:
server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}Change it to https:// once you add SSL. See Redirect HTTP to HTTPS in Nginx for the full HTTPS setup.
Apply the change
Test, then reload:
sudo nginx -t && sudo systemctl reload nginxTip: Test with a temporary 302 first if you are unsure. Browsers cache 301 redirects hard, so a mistake can stick in your own browser even after you fix it. Switch to 301 once it behaves.
Check it works
Use curl -I to see only the response headers. Test each combination:
curl -I http://www.example.com/test?x=1
curl -I https://www.example.com/test?x=1
curl -I http://example.com/test?x=1Each should return:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/test?x=1Then confirm the final address serves the page:
curl -I https://example.com/You want HTTP/1.1 200 OK (or HTTP/2 200). To follow the whole chain and count the hops, add -L:
curl -sIL http://www.example.com/ | grep -iE "^HTTP|^location"Troubleshooting
- Certificate warning on www: the certificate does not include
www.example.com. Reissue it with both names:sudo certbot --nginx -d example.com -d www.example.com. - ERR_TOO_MANY_REDIRECTS: your app redirects to the other hostname while Nginx redirects back. Match the app's site URL to the Nginx choice. More causes in Fix ERR_TOO_MANY_REDIRECTS on Nginx.
- Redirect drops the path: you used
$urior nothing at all. Use$request_uri, which keeps the path and query string. - Old redirect still happens after a fix: your browser cached the 301. Test with
curlor a private window. - www does not respond at all: DNS has no record for
www. Add an A record or a CNAME to the root domain.
Next steps
- Move old URLs without losing traffic: Nginx 301 redirects and rewrite rules explained.
- Set up DNS for both names: Point a domain to your VPS.
