// Engineering Log

HTTP, HTTPS and TLS: Part 7 — Transition to HTTPS: redirects, HSTS, HTTPS-First and the Upgrade header

Published on 2026-10-09

// Fast route

This article belongs to the topic Servers and infrastructure.

The user types example.ru in the address bar without a scheme, follows an old http://… link, or opens a decade-old bookmark. In all these cases the first request may go over plain HTTP, and the site owner’s job is to ensure everything continues over HTTPS — ideally so that there is no plaintext request at all. There are several mechanisms that work at different stages: server redirect, HSTS, the built-in preload list, HTTPS-First mode in browsers, DNS HTTPS record. The Upgrade header, often confused with “HTTPS upgrade”, is covered separately: it switches the connection to a different protocol, for example WebSocket.

Redirect from HTTP to HTTPS

The basic level — the server on port 80 responds with a redirect to the same address over HTTPS:

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

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;   # Let's Encrypt HTTP-01 verification
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Key points:

  • 301 or 308. Both are permanent redirects; search engines transfer page weight to the new address. 308 preserves the method and request body; with 301 browsers may turn a POST into a GET. For a website the difference is usually unnoticed; for an API where clients accidentally send POST to http://, 308 is better.
  • Preserve the path. $request_uri contains the path and query. Redirecting all pages to the homepage breaks links and search indexing.
  • One step. The chain http://example.ru → https://example.ru → https://www.example.ru is two unnecessary hops. Directly point to the final address.
  • Let’s Encrypt HTTP-01 follows redirects, so a separate location for acme-challenge is not strictly required, but it avoids surprises with nonstandard configurations.

Weakness of redirects: the first request still goes over plaintext. An attacker on the same network (for example on public Wi-Fi) can intercept it and prevent the user from reaching HTTPS, serving the site over HTTP through themselves. This technique is called SSL stripping, and HSTS protects against it.

HSTS

HSTS (HTTP Strict Transport Security, RFC 6797) is a response header by which a site tells the browser: “for the specified period, contact me only over HTTPS.”

Strict-Transport-Security: max-age=31536000; includeSubDomains
  • max-age — duration in seconds (31536000 — one year). Each response with the header refreshes the duration;
  • includeSubDomains — the rule applies to all subdomains;
  • preload — consent to inclusion in the preload list (below).

Upon receiving the header, the browser:

  1. rewrites any http:// requests to this site to https:// before sending the request — a server redirect is no longer needed (in developer tools this shows up as 307 Internal Redirect);
  2. does not allow the user to bypass a certificate error: there is no “Proceed anyway” button.

Rules:

  • the browser accepts the header only over HTTPS; it ignores the header in an HTTP response;
  • start with a small max-age (300, then 86400, then a week) and increase it once you’re sure everything works over HTTPS. HSTS cannot be revoked quickly: browsers that have remembered the header will require HTTPS until the duration expires;
  • test includeSubDomains carefully: if you have intranet.example.ru or printer.example.ru without HTTPS, after enabling it they will stop opening for everyone who visited the main site.

In Nginx:

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

HSTS preload list

HSTS only takes effect after the first HTTPS visit. The first visit remains unprotected. To close that gap, there is the preload list — a list of domains baked into Chrome, Firefox, Safari and Edge. For domains on the list, the browser uses HTTPS from the very first request.

Requirements for inclusion (hstspreload.org):

  1. a valid certificate;
  2. a redirect from HTTP to HTTPS on the same host name;
  3. all subdomains available over HTTPS;
  4. the header on the main domain: max-age at least 31536000, includeSubDomains and preload.

You can submit an application at hstspreload.org; inclusion in stable browser releases takes weeks. Removal from the list takes months and does not affect old browser versions, so preload is effectively irreversible in practice. Some top-level domains are included in the list entirely: all sites in .dev, .app, .page work only over HTTPS.

HTTPS-First in browsers

Browsers do not wait for sites to configure HSTS and try HTTPS themselves.

  • Chrome since version 115 (2023) tries HTTPS first when navigating to http:// and falls back to HTTP on failure (HTTPS-Upgrades). The setting “Always Use Secure Connections” additionally warns before opening a site over HTTP. Google announced that in Chrome 154 (October 2026) this setting will be enabled by default: the browser will ask the user before the first visit to a public site without HTTPS and will not repeat the warning for sites the user visits regularly. For users with Enhanced Safe Browsing this has been enabled since April 2026.
  • Firefox since version 136 (March 2025) operates in HTTPS-First mode by default: it tries HTTPS and falls back to HTTP if the site does not respond. There is a stricter HTTPS-Only mode available separately.
  • Safari also attempts HTTPS for addresses without a scheme.

What this means for site owners: if something other than your site answers on your domain over HTTPS (hosting placeholder, someone else’s site on the same IP, certificate error), users will increasingly see that. HTTPS must work for all names you publish, and sites without HTTPS will display warnings.

Upgrade-Insecure-Requests and mixed content

Mixed content — a page loaded over HTTPS that loads scripts, styles, or images over HTTP. Browsers block such scripts, styles and frames; images, video and audio are attempted over HTTPS and, if unavailable, are not shown.

Two similarly named things:

  • Request header Upgrade-Insecure-Requests: 1. The browser sends it when navigating to a page to say “I prefer HTTPS”. The server can respond to it with a redirect to HTTPS. In practice a normal redirect is sufficient and this header is rarely used.

  • CSP directive upgrade-insecure-requests. A response header that tells the browser to rewrite all http:// resource URLs on the page to https:// before loading:

    Content-Security-Policy: upgrade-insecure-requests

    Useful when migrating an old site whose database contains thousands of http:// image links: while the links are not fixed, the directive removes warnings. Resources must be available over HTTPS — the directive does not create HTTPS where there is none.

DNS HTTPS record

A HTTPS record (RFC 9460) informs the client of connection parameters before the first request:

example.ru.  3600  IN  HTTPS  1 . alpn="h3,h2" ipv4hint=192.0.2.10
  • the presence of the record tells the browser the site is available over HTTPS: Chrome and Firefox will go straight to https://, bypassing plain HTTP, just like with HSTS;
  • alpn — which protocols are supported; with h3 the browser can connect over HTTP/3 right away;
  • ech — key for Encrypted Client Hello;
  • ipv4hint, ipv6hint — addresses so the client does not need to wait for A/AAAA responses.

Cloudflare supports the record (creates it automatically) and some DNS hosts do as well. Check with: dig HTTPS example.ru.

Upgrade header: protocol switching

Upgrade is an HTTP/1.1 mechanism to switch from HTTP to another protocol over the same TCP connection. The client offers, the server agrees with a 101 Switching Protocols response, and the connection then carries the new protocol.

WebSocket (RFC 6455) is the main example. How WebSocket differs from SSE and long polling and when to choose each is covered in the article “WebSockets, Long Polling and SSE”. Here — how the switch itself looks:

GET /ws HTTP/1.1
Host: example.ru
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

The address wss:// is WebSocket over TLS, i.e. HTTPS is established first and then the Upgrade occurs inside it.

Upgrade and Connection are hop-by-hop headers and proxies do not forward them. Therefore explicit configuration is needed for WebSocket behind Nginx:

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    location /ws/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;   # default 60s — idle connection will be closed
    }
}

Without these lines the client will receive 400 or 426, and the application log will show a plain GET without Upgrade.

In HTTP/2 and HTTP/3 there is no Upgrade header. WebSocket in them is opened via an extended CONNECT method (RFC 8441 for HTTP/2, RFC 9220 for HTTP/3) with a pseudo-header :protocol: websocket. Browsers and servers negotiate this themselves; Nginx still communicates with the application over HTTP/1.1.

Other uses of Upgrade:

  • Upgrade: h2c — switch to HTTP/2 without encryption. Browsers never supported this, and RFC 9113 (2022) deprecated the approach. Proxies that pass Upgrade: h2c to applications sometimes allow bypassing their access rules (h2c smuggling) — such headers are better to drop at the edge;
  • Upgrade: TLS/1.0 — switch to TLS within an HTTP connection (RFC 2817). It did not catch on for the web; instead a separate port 443 is used.

If the server requires switching to another protocol, it responds with status 426 Upgrade Required and an Upgrade header listing the needed protocol.

Steps to migrate a site to HTTPS

  1. Issue certificates for all used names, including www, and set up automatic renewal.
  2. Verify the site fully works over HTTPS: internal links, images, forms, third-party widgets. For legacy content links use CSP upgrade-insecure-requests.
  3. Enable a 301 redirect from HTTP to HTTPS, preserving the path.
  4. Use https:// in canonical URLs, the sitemap, in Yandex Webmaster and Google Search Console.
  5. Enable HSTS with a short max-age, after a few weeks increase to one year and add includeSubDomains.
  6. If you are confident about all subdomains — submit the domain to the preload list.

Common mistakes

  • Infinite redirect loop: the application behind a proxy does not see X-Forwarded-Proto: https and redirects back to HTTPS; the same happens with Cloudflare in Flexible mode.
  • HSTS with includeSubDomains on the main domain while subdomains lack HTTPS — internal services stop opening.
  • HSTS header served over HTTP — the browser ignores it and no protection is in place.
  • Redirecting to the homepage instead of the same page.
  • WebSocket behind Nginx without proxy_http_version 1.1 and Upgrade/Connection headers, or with default proxy_read_timeout — connections drop every minute.

// Similar task

If you are dealing with something similar

This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.

Article topic

Servers and infrastructure

VPS, Linux, web stack, migrations, hosting, databases, and core operations.

Typical tasks behind this topic

  • Move a site or service to a new server
  • Set up Linux, Nginx, databases, and backups
  • Figure out why the system behaves unstably

// Next step

If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.

Open services

// Reviews

Related reviews

apande

apande

Configuring Nginx and OpenCart

2024-09-07 · ★ 5/5

A very powerful buyer
kireevk

kireevk

Diagnosing Nginx Proxy Manager in a Docker container and resolving the issue

2024-03-15 · ★ 5/5

Settled-in customer

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply