# Redirects, HSTS and Canonical Hosts — Nginx

Source: https://www.skillbyai.com/en/nginx/t-redirects

> Redirect HTTP to HTTPS, choose a canonical host and enable HSTS safely.

## One canonical, secure address

Serve every site on **one canonical URL**: redirect HTTP to HTTPS and pick either `www.example.com` or `example.com`, redirecting the other, which avoids duplicate content and cookie confusion. Use **`return 301`** for permanent redirects and keep the path and query string with `$request_uri`. Prefer `return` over **`rewrite`** whenever possible; `rewrite` uses regular expressions and is needed only for pattern-based URL changes. Use **308** instead of 301 when a redirect must preserve the HTTP method and body (for example API POST requests). **HTTP Strict Transport Security (HSTS)**, the `Strict-Transport-Security` header, tells browsers to use HTTPS for your domain for a period, blocking downgrade attacks; start with a short `max-age`, then increase it to a year once everything works, and only add `includeSubDomains` and `preload` when every subdomain supports HTTPS, because preloading is hard to undo. Leave the HTTP-to-HTTPS redirect server able to answer ACME challenges if you use HTTP-01 certificate validation.

## Redirect HTTP and the bare domain to one HTTPS host

ACME challenges still work over plain HTTP.

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://www.example.com$request_uri; }
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;
    ssl_certificate     /etc/ssl/example/fullchain.pem;
    ssl_certificate_key /etc/ssl/example/privkey.pem;
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name www.example.com;
    ssl_certificate     /etc/ssl/example/fullchain.pem;
    ssl_certificate_key /etc/ssl/example/privkey.pem;
    add_header Strict-Transport-Security "max-age=31536000" always;
    root /var/www/example;
}
```

## Roll out HSTS gradually

A long HSTS max-age with `includeSubDomains` instantly breaks any subdomain still on HTTP, and browsers remember it for months. Start with `max-age=300`, verify, then raise it.

**Quiz:** Which is the simplest correct way to redirect all HTTP traffic to HTTPS while keeping the path?

- [ ] rewrite ^ https://$host;
- [ ] proxy_pass https://$host;
- [x] return 301 https://$host$request_uri;
- [ ] try_files https://$host;

*Answer:* return 301 https://$host$request_uri;. return with $request_uri preserves the path and query string without regex processing.
