Redirect HTTP traffic to HTTPS with Nginx

Configure an HTTPS redirect, preserve paths and query strings, make health-check exceptions work, and enable HSTS deliberately.

Best practices
Illustration of server operations and request logs

Use a port-80 server block to redirect requests to a working HTTPS endpoint. Configure and test the certificate on port 443 first. Keep the original path and query string with $request_uri.

Configure the redirect and TLS endpoint

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

server {
    listen 443 ssl;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

This chooses example.com as the canonical HTTPS host. To preserve both accepted hostnames, use https://$host$request_uri and ensure unexpected Host values are rejected by the appropriate default server. The certificate must cover every hostname that reaches the TLS block.

Keep a health-check exception reachable

A server-level return runs before location selection. Putting a /healthz location above it in the file does not create an exception. Move the redirect into a catch-all location:

server {
    listen 80;
    server_name example.com www.example.com;

    location = /healthz {
        default_type text/plain;
        return 200 "ok\n";
    }
    location / {
        return 301 https://example.com$request_uri;
    }
}

Use this alternative when the load balancer expects a plain HTTP health check. ACME HTTP challenge handling may also need an exception, depending on the certificate automation method.

Add HSTS after HTTPS is reliable

Place the header in the HTTPS server, not as a substitute for its certificate setup:

add_header Strict-Transport-Security "max-age=31536000" always;

This tells supporting browsers to use HTTPS for future requests during that period. Start with an operationally appropriate shorter policy if you are still validating the migration. Add includeSubDomains or preload only after checking their wider requirements. Review add_header inheritance if locations define other headers.

When TLS terminates at an earlier load balancer, Nginx's local $scheme can be http even for an external HTTPS request. Coordinate redirects and trusted forwarded-protocol handling across that boundary to avoid loops; do not trust arbitrary client-supplied forwarding headers.

Validate the full path

sudo nginx -t && sudo systemctl reload nginx
curl -I 'http://example.com/some/page?a=1'
curl -I 'https://example.com/some/page?a=1'

Check status, Location, certificate validation, and the final application response. Test health checks and certificate challenges separately when configured.

FAQ

Should I use 301, 302, or 308?

301 is a permanent redirect commonly used for websites; 302 is temporary. 307 and 308 preserve the request method, with 308 permanent. Some clients change POST to GET after 301 or 302, so test API behavior and prefer clients calling HTTPS directly. A redirect cannot retroactively protect data already sent over HTTP.

Is an if statement always unsafe?

No. A simple if with return is not the same as every problematic rewrite configuration. A separate HTTP server block is usually easier to inspect and avoids unnecessary conditional logic.

Why is the application still generating HTTP links?

Check its trusted-proxy settings and forwarded protocol information. Forwarding a header only helps if the application uses it correctly.

References: rewrite processing and return, HTTPS configuration.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting