Skip to main content
Blog|
How-to guides

How to redirect HTTP to HTTPS in Nginx

|
Apr 5, 2026|9 min read
HOW-TO GUIDESHow to redirect HTTP to HTTPSin NginxHOSTNEYhostney.comApril 5, 2026

Every website should serve all traffic over HTTPS. Browsers display “Not Secure” warnings for HTTP sites. Google uses HTTPS as a ranking signal. Payment processing requires it. User credentials transmitted over HTTP are visible to anyone on the network. The redirect from HTTP to HTTPS is one of the first things to configure on any new server.

In Nginx, this redirect is a few lines of configuration. Getting it wrong, however, creates redirect loops, mixed content errors, or SSL connection failures that make the site unreachable. This guide covers the correct configuration, the common mistakes, and how to verify everything works.

Why HTTPS matters#

Security

HTTP transmits everything in plaintext. Every request URL, every form submission, every cookie, every piece of content is visible to anyone who can observe the network traffic between the browser and the server. On public Wi-Fi, a corporate network, or any network you do not control, this means passwords, session tokens, and personal data are exposed.

HTTPS encrypts the entire connection. The browser and server negotiate a TLS session before any data is exchanged. Everything after the handshake is encrypted and cannot be read or modified by a third party.

SEO

Google confirmed HTTPS as a ranking signal in 2014. The weight is not massive, but it exists. More importantly, Chrome and other browsers display “Not Secure” next to the URL for HTTP sites. Visitors who see this warning are more likely to leave. The indirect SEO impact from increased bounce rate is potentially larger than the direct ranking signal.

Browser behavior

Modern browsers are increasingly aggressive about HTTPS:

  • Chrome shows “Not Secure” in the address bar for any HTTP page
  • Firefox shows a crossed-out lock icon
  • Some browsers block mixed content (HTTP resources loaded on an HTTPS page) entirely
  • Features like geolocation, camera access, service workers, and HTTP/2 require HTTPS

Compliance

PCI DSS requires HTTPS for any page that handles payment data. HIPAA requires encryption in transit for health information. GDPR requires appropriate security measures for personal data. For most businesses, HTTPS is not optional.

The Nginx redirect configuration#

Basic redirect

The standard approach uses a separate server block for port 80 that redirects all traffic to HTTPS:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$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;

    # ... rest of your HTTPS configuration
}

The first server block listens on port 80 (HTTP) and issues a 301 permanent redirect to the HTTPS version of the same URL. The $host variable preserves the original hostname (example.com or www.example.com). The $request_uri variable preserves the full path and query string.

return 301 is the correct directive for this redirect. It is faster than a rewrite rule because Nginx processes it immediately without evaluating regex patterns.

Why 301 and not 302

A 301 redirect is permanent. It tells browsers and search engines that the HTTP URL should never be used again. The browser caches the redirect and goes directly to HTTPS on subsequent visits. Search engines transfer ranking value from the HTTP URL to the HTTPS URL.

A 302 redirect is temporary. Browsers do not cache it. Search engines keep the HTTP URL in their index. Using 302 for an HTTP-to-HTTPS redirect is a common mistake that splits SEO signals between the HTTP and HTTPS versions of every page.

Redirect with www normalization

Most sites want to redirect both HTTP-to-HTTPS and www-to-non-www (or vice versa) in a single hop:

# Redirect HTTP to HTTPS and www to non-www
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# Redirect HTTPS www to non-www
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;

    return 301 https://example.com$request_uri;
}

# Main site (HTTPS, non-www)
server {
    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/html;
    # ... rest of configuration
}

This configuration handles four cases:

  • http://example.com  redirects to  https://example.com  (one hop)
  • http://www.example.com  redirects to  https://example.com  (one hop)
  • https://www.example.com  redirects to  https://example.com  (one hop)
  • https://example.com  serves the site (no redirect)

Note that both the www HTTPS block and the main block need SSL certificates. The www block must complete the TLS handshake before it can issue the redirect. If the certificate does not cover www.example.com , visitors to that URL see an SSL error instead of the redirect.

Using rewrite instead of return

Some older guides use rewrite for the redirect:

server {
    listen 80;
    server_name example.com;
    rewrite ^(.*)$ https://example.com$1 permanent;
}

This works but is less efficient than return 301 . The rewrite directive evaluates a regex pattern on every request. The return directive does not need regex evaluation. For a simple redirect, return 301 is the better choice.

Common mistakes#

Redirect loop

The most common mistake. This happens when the HTTPS server block also contains a redirect to HTTPS, or when a reverse proxy or CDN in front of Nginx sends requests to the backend over HTTP.

Symptom: The browser shows “too many redirects” or ERR_TOO_MANY_REDIRECTS.

Cause 1: Both blocks redirect. If you put the redirect inside the same server block that handles both HTTP and HTTPS:

# WRONG - this creates a loop
server {
    listen 80;
    listen 443 ssl;
    server_name example.com;

    # This redirects ALL requests, including HTTPS ones
    return 301 https://example.com$request_uri;
}

The fix is to use separate server blocks for port 80 and port 443, as shown in the basic configuration above.

Cause 2: Reverse proxy or CDN. If Cloudflare, a load balancer, or another reverse proxy sits in front of Nginx, the proxy may terminate SSL and forward requests to Nginx over HTTP. Nginx sees an HTTP request and redirects to HTTPS. The proxy follows the redirect, sends another HTTP request to Nginx, and the loop continues.

The fix depends on the proxy. For Cloudflare, set the SSL mode to “Full” or “Full (Strict)” so Cloudflare connects to Nginx over HTTPS. For other proxies, check the X-Forwarded-Proto header:

server {
    listen 80;
    server_name example.com;

    # Only redirect if the original request was HTTP
    if ($http_x_forwarded_proto = "http") {
        return 301 https://$host$request_uri;
    }
}

Cause 3: WordPress forcing HTTPS independently. If WordPress has its own HTTPS redirect (through a plugin or wp-config.php) on top of the Nginx redirect, the two can conflict. Let Nginx handle the redirect and remove any HTTPS redirect plugins from WordPress.

Mixed content after enabling HTTPS

The redirect works but the site looks broken. Images are missing, styles are not applied, or the browser shows a warning about insecure content.

This happens when the site’s HTML contains hardcoded http:// URLs for images, stylesheets, scripts, or other resources. The page loads over HTTPS but tries to load resources over HTTP. Browsers block or warn about this mixed content.

The fix for WordPress:

  1. Update  siteurl  and  home  in Settings > General to use  https://
  2. Run a search-and-replace in the database to change all  http://yourdomain.com  references to  https://yourdomain.com
  3. Use WP-CLI for a serialization-safe replacement:  wp search-replace 'http://example.com' 'https://example.com'

SSL certificate not covering all domains

The redirect sends traffic from http://www.example.com to https://www.example.com , but the SSL certificate only covers example.com (without www). The browser shows an SSL error for the www version.

The fix: issue a certificate that covers both domains. With certbot:

certbot certonly --nginx -d example.com -d www.example.com

For a complete guide to certificate management, see How to install an SSL certificate.

Using $server_name instead of $host

# Potentially wrong
return 301 https://$server_name$request_uri;

$server_name uses the first value in the server_name directive. If your server block has server_name example.com www.example.com , $server_name is always example.com , even if the visitor requested www.example.com . This works if you want to normalize to non-www, but it is confusing if you expect it to preserve the original hostname. Use $host if you want to preserve the hostname the visitor used.

Not preserving the request URI

# Wrong - loses the path and query string
return 301 https://example.com;

This redirects every URL to the homepage. A visitor requesting http://example.com/blog/article?id=123 ends up at https://example.com/ instead of https://example.com/blog/article?id=123 . Always include $request_uri to preserve the full path and query string.

Testing the redirect#

With curl

# Check the redirect (do not follow it)
curl -I http://example.com

Expected output:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/

Verify that:

  • The status code is 301 (not 302)
  • The Location header points to the HTTPS version
  • The Location includes the correct hostname

Test with a path:

curl -I http://example.com/blog/article

The Location header should include the full path: https://example.com/blog/article .

Check for redirect chains

curl -ILs http://www.example.com 2>&1 | grep -E "^HTTP|^location"

This follows all redirects and shows each hop. Ideally you see one redirect (HTTP to HTTPS). If you see two or more, you have a chain that should be consolidated.

Check in browser developer tools

Open the Network tab (F12), visit the HTTP URL, and look at the first request. It should show a 301 status with the HTTPS URL in the Location header. If you see multiple 301/302 redirects before the page loads, you have a chain.

HSTS: enforcing HTTPS at the browser level#

The redirect handles visitors who type http:// or follow an HTTP link. But the first request is still over HTTP before the redirect kicks in. HSTS (HTTP Strict Transport Security) eliminates this by telling the browser to always use HTTPS for your domain, even if the user types http:// .

Add the HSTS header in your HTTPS server block:

server {
    listen 443 ssl;
    server_name example.com;

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

    # ... rest of configuration
}

The max-age=31536000 tells the browser to remember the HSTS policy for one year (in seconds). After the first visit over HTTPS, the browser automatically upgrades all future HTTP requests to HTTPS without making the initial HTTP request at all.

Only enable HSTS after you have verified that HTTPS works correctly. HSTS is difficult to undo. If you enable it and then discover an SSL problem, browsers will refuse to connect over HTTP, and the cached HSTS policy can persist for the full max-age period. Start with a short max-age (like 300 seconds) for testing, then increase to a year once everything is confirmed working.

The includeSubDomains flag applies the policy to all subdomains. Only use this if all subdomains have valid SSL certificates and serve over HTTPS.

HTTPS and HTTP/2#

HTTP/2 requires HTTPS. Once your redirect is in place and all traffic is served over HTTPS, enable HTTP/2 in Nginx:

server {
    listen 443 ssl http2;
    server_name example.com;
    # ...
}

HTTP/2 provides significant performance improvements: multiplexed requests, header compression, and server push. These are free performance gains that only require HTTPS and the http2 parameter on the listen directive.

How Hostney handles HTTPS redirects#

On Hostney, HTTPS is enabled automatically for every site. SSL certificates are provisioned via Let’s Encrypt when the domain is added, and the HTTP-to-HTTPS redirect is configured at the server level. You do not need to edit Nginx configuration files or set up certbot manually.

The redirect uses a 301 status code with the full URI preserved. The SSL configuration uses TLS 1.2 and 1.3 with modern cipher suites. HSTS is not enabled by default but can be configured through the control panel’s security headers settings for each domain.

For sites migrated to Hostney from a host that did not use HTTPS, the main step after migration is updating the database URLs from http:// to https:// . This can be done from the control panel’s Search & Replace tab or via WP-CLI over SSH.