How to Redirect Non-WWW to WWW URLs in Nginx

How to Redirect Non-WWW to WWW URLs in Nginx

How to Redirect Non-WWW to WWW URLs in Nginx

This is one of those tasks that looks trivial and then quietly causes SEO headaches, duplicate content warnings, and broken SSL certificates when it’s done wrong. I’ve inherited more than one server where example.com and www.example.com both served the exact same content independently, with no redirect between them — which search engines treat as two separate sites with duplicate content, splitting your SEO value between two URLs instead of consolidating it on one.

This guide covers doing the www redirect properly: picking a canonical domain, setting up the redirect correctly (301, not 302), making sure it works with HTTPS, and avoiding the common mistakes that break this in subtle ways.

Why This Matters

A few concrete reasons to get this right, beyond “it looks tidier”:

Step 1: Decide Your Canonical Domain

This is genuinely a business/branding decision, not a technical one — I’m choosing “redirect non-www to www” for this guide since that’s the specific direction you asked about, but the reverse (www to non-www) is equally valid and follows the identical pattern, just swapped. Once you decide, stick with it everywhere: your DNS records, your marketing materials, your social profiles, all should point people at the version you’re keeping canonical.

Step 2: Set Up DNS for Both Variants

Before touching Nginx, make sure both example.com and www.example.com actually resolve to your server. In your DNS provider:

If www doesn’t resolve at all, no Nginx configuration in the world will let you redirect it — the DNS lookup fails before the request even reaches your server.

Step 3: Get a Certificate Covering Both Variants

This step trips people up constantly. You need a single certificate valid for both hostnames, or the redirect will trigger a certificate warning before it can even execute (since the browser validates TLS before processing any HTTP-level redirect).

Using Certbot:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

Passing both -d flags issues a single certificate covering both names via Subject Alternative Names (SAN). Confirm it worked:

sudo certbot certificates

You should see both hostnames listed under the same certificate.

Step 4: Configure the Redirect in Nginx

Here’s the clean, correct way to do this — one server block that only exists to redirect, and one server block that serves your actual content:

# Block 1: catches the non-www domain (both HTTP and HTTPS) and redirects to www
server {
    listen 80;
    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;
}

# Block 2: the real site, served only from www
server {
    listen 80;
    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;

    # Redirect HTTP to HTTPS on the www host too
    if ($scheme = http) {
        return 301 https://www.example.com$request_uri;
    }

    root /var/www/example.com/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Let’s break down why this is structured the way it is:

A cleaner alternative that avoids the if directive (which Nginx’s own documentation recommends minimizing use of) is to split the www block into two separate listen blocks instead:

server {
    listen 80;
    server_name www.example.com;
    return 301 https://www.example.com$request_uri;
}

server {
    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;

    root /var/www/example.com/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

This is functionally identical but avoids the if block, which I’d generally recommend as the more idiomatic Nginx pattern.

Enable and test:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 5: Update Your Canonical Tags

Nginx-level redirects handle the HTTP layer, but it’s worth also confirming your HTML includes a matching canonical tag, reinforcing the same signal directly to search engines and crawlers that read page content:

<link rel="canonical" href="https://www.example.com/blog/post-1" />

This isn’t an Nginx configuration step, but I mention it because I’ve seen sites get the redirect right at the server level while their CMS or static site generator still emits canonical tags pointing at the non-www version — sending mixed signals that undermine the whole point of the redirect.

Testing Your Setup

Use curl -I to inspect the redirect chain directly:

curl -I http://example.com/some-page

Expected output includes:

HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/some-page

Test all four combinations to be thorough:

curl -I http://example.com
curl -I https://example.com
curl -I http://www.example.com
curl -I https://www.example.com

The first three should each return a 301 pointing at https://www.example.com; the last should return your actual site content with a 200.

Also test that query strings and paths survive the redirect intact:

curl -I "http://example.com/products?category=shoes&sort=price"

The Location header should show the identical path and query string, just on the www HTTPS host.

Troubleshooting Common Issues

Redirect loop (ERR_TOO_MANY_REDIRECTS) — Usually caused by both server blocks matching the same server_name, or a CDN/load balancer in front of Nginx doing its own www redirect that conflicts with Nginx’s. Double check server_name values are distinct and that nothing upstream (Cloudflare page rules, for example) is also redirecting.

Certificate warning before redirect appears — The certificate doesn’t cover both hostnames. Re-run Certbot with both -d flags, or check openssl s_client -connect example.com:443 -servername example.com and inspect the certificate’s SAN list.

Redirect works but drops the path — $request_uri was omitted from the return directive. Every redirect to / regardless of the original path is the telltale symptom.

Redirect works for HTTP but not HTTPS (or vice versa) — Check that both listen 80 and listen 443 ssl are present in the redirecting server block, and that the SSL certificate paths are valid there too — Nginx needs a valid cert even just to serve the 301 over HTTPS.

Search engines still showing both versions weeks later — 301s are respected by crawlers but re-indexing takes time. Submit both URL versions in Google Search Console and confirm the canonical is registering correctly; this is patience, not a configuration bug at that point.

Security Considerations

sudo certbot renew --dry-run
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Be cautious with HSTS — once a browser caches it, that policy is hard to undo for users, so only add it once you’re confident in your HTTPS setup’s stability.

Performance Tips

Real-World Use Cases

Best Practices Recap

It’s a small piece of configuration, but getting it right the first time saves you from untangling split SEO signals and confused analytics data months down the line.

Exit mobile version