HTTPS Everywhere Mixed Content for NZ sites: CSP upgrade-insecure-requests, www to apex redirect chain fix

Quick answer

Mixed content is what happens when an HTTPS page loads a sub-resource — usually an image, script, or stylesheet — over plain http://. The page itself is encrypted, but the insecure sub-resource can be tampered with by anyone on the network between the visitor and your server. Browsers block the worst cases outright and warn on the rest. The fastest fix is a one-line Content-Security-Policy header: Content-Security-Policy: upgrade-insecure-requests, which tells the browser to silently rewrite every http:// sub-request on the page to https:// before it goes out.

If you want to check your own site first, run the Free Website Security Check — it reports mixed content as a mixed finding and points at the exact resources to update.

What mixed content actually is

Every HTTPS page is made up of dozens of small requests: the HTML document, every <img>, every CSS file, every JavaScript file, every web font, every analytics beacon. Each of those requests is its own URL, and each one is either http:// or https://. A page is fully secure only when every one of those requests is HTTPS. If even one is plain HTTP, you have mixed content.

There are two flavours, and the browser treats them very differently (1):

  • Active mixed content — scripts, stylesheets, iframes, and other resources that can change what the page does. Browsers block these outright. The page loads, but the script never runs; the iframe is replaced with a warning.
  • Passive mixed content — images, audio, and video. Browsers warn about these but still load them, because blocking a banner image would visibly break too many sites. The lock icon in the address bar gets a yellow triangle or red strike-through.
A page can show the green padlock and still have mixed content. The padlock only confirms the main document is encrypted; it does not guarantee the contents of the page came over an encrypted channel. Always check the browser console, not just the address bar, before declaring a page clean.

For New Zealand sites, the most common source of mixed content is a leftover hardcoded http:// URL — usually a logo, a tracking pixel, or an embedded video player — that nobody updated when the site moved to HTTPS years ago. A free scan flags these in seconds.

Why mixed content matters for NZ schools and small businesses

Three reasons it sits in the NZAI baseline rather than being filed under "minor":

  • Browser warnings erode trust. A yellow triangle on the lock icon is the first thing a parent checking the school newsletter URL sees. The page is not actually unsafe, but the warning reads like it is. Visitors bounce, support inboxes fill, and the IT team gets blamed for something nobody did.
  • Active mixed content can be exploited. A blocked script is fine. A loaded script is a vector for the network attacker to swap your analytics beacon for a credential-harvesting payload. The browser blocks this on most modern browsers, but older phones and old browsers still let it through.
  • CERT NZ and the NCSC both name HTTPS-only delivery as a baseline control. "Baseline controls" are the things the agencies expect every New Zealand organisation to have done before an incident is investigated (2)(3). Mixed content is the most visible sign that the HTTPS-only rule is not actually enforced everywhere on the site.

For the broader educator safety pathway, including account security, devices, phishing, and student data, see Building Cyber-Safe Habits as an NZ Educator.

How to find mixed content on your own site

Three reliable ways, in order of speed.

1. Run the free NZAI scanner

The Free Website Security Check crawls a page and reports any http:// reference it finds, with the URL and where it lives in the HTML. If the scanner reports a mixed finding, follow the resource URL into your CMS or template and fix it there.

2. Browser DevTools (Chrome, Firefox, Edge)

Open the page, press F12, switch to the Console tab. Any mixed-content warning appears with the prefix Mixed Content: followed by the offending URL. You can also filter the Network tab by the Scheme column (Chrome) to see only http:// requests. Each one is a fix in your template.

3. curl from the command line

Pull the page source and grep for http://:

curl -s https://yoursite.nz/ | grep -oE 'http://[^"'\'' ]+' | sort -u

That gives you a deduplicated list of every plain-HTTP reference the page contains. Anything not on your own domain is a third-party you need to either upgrade or replace.

Browser auto-upgrade — the easy win

Modern browsers already do a limited version of HTTPS upgrade on their own. When a page is HTTPS and an <img src="http://..."> appears, Chrome, Firefox and Safari will try https:// before giving up (4). This works for passive mixed content (images, audio, video). It does not work for active mixed content (scripts, stylesheets), and it silently fails for any third-party that does not serve HTTPS at all.

That means browser auto-upgrade is a safety net, not a fix. If your image CDN does not support HTTPS, the request fails and the image does not load. If your tracking vendor is HTTP-only, the request is blocked. The right answer is to either upgrade the URL in your template or replace the resource with an HTTPS equivalent — never to rely on the browser to keep saving you.

CSP upgrade-insecure-requests — the one-line fix

The single most useful header for killing mixed content is one you add to your Content-Security-Policy:

Content-Security-Policy: upgrade-insecure-requests

That directive tells the browser: before issuing any request on this page, if the URL starts with http://, rewrite it to https://. It applies to every sub-resource — images, scripts, stylesheets, fonts, iframes — before the request leaves the browser (5).

The result:

  • An <img src="http://example.com/logo.png"> becomes <img src="https://example.com/logo.png"> automatically.
  • A <script src="http://cdn.example.com/jquery.js"> becomes <script src="https://cdn.example.com/jquery.js"> automatically.
  • Any request that cannot be upgraded (the third-party serves no HTTPS) is blocked entirely, with a console warning.
upgrade-insecure-requests is a strictly safer default than nothing. If a resource has no HTTPS version, the user sees nothing rather than an insecure resource — which is the right trade-off for almost every public-facing site.

If you already have a CSP, add the directive alongside your existing rules:

Content-Security-Policy: default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; upgrade-insecure-requests

If you do not yet have a CSP, this one line is a perfectly fine first header — and it can sit on its own for a while before you tighten the rest of the policy. For the full set of recommended headers, see the Security Headers Explained guide.

www to apex and back again — the redirect trap

A surprisingly common source of mixed content is the www ↔ apex redirect. Your site lives on https://yourschool.nz (the apex, no www), but a hardcoded link somewhere in your template points at https://www.yourschool.nz/.... If www.yourschool.nz is not actually configured with a certificate, the browser silently falls back to plain HTTP for the sub-resource, and you have mixed content on a fully-HTTPS page.

The fix is to make sure only one hostname works. Two patterns:

Pattern 1: redirect everything to the apex (preferred for NZ schools)

Set up your DNS and server so that www.yourschool.nz 301-redirects to yourschool.nz for every request, on HTTPS, with no exceptions:

# nginx
server {
    server_name www.yourschool.nz;
    listen 443 ssl http2;
    ssl_certificate     /etc/ssl/certs/yourschool.nz.fullchain.pem;
    ssl_certificate_key /etc/ssl/private/yourschool.nz.key;
    return 301 https://yourschool.nz$request_uri;
}

Pattern 2: redirect everything to www

The mirror image — every request to yourschool.nz 301s to www.yourschool.nz. Works equally well, but most NZ schools and small businesses have grown attached to the bare-apex look in marketing material, so pattern 1 is usually preferred.

Pattern 3: use a single canonical URL in templates

Whichever pattern you pick, hardcode the canonical hostname into your CMS templates, your robots.txt, your sitemap, and your analytics. The fewer hostname variants you allow in your own content, the fewer redirect chains and mixed-content warnings your visitors will see. For the deeper mechanics of long redirect chains and how to flatten them into a single hop, see the Redirect Chains guide.

HTTPS Everywhere Mixed Content for NZ sites — fixed with upgrade-insecure-requests and www-to-apex redirects

Copy-paste fixes for the four common stacks

Below are minimal snippets that put upgrade-insecure-requests in place for nginx, Apache, Express, and Cloudflare. Pair this with HSTS so the upgraded requests stay upgraded. For the HSTS deep-dive, see the HSTS Strict-Transport-Security guide.

nginx

Inside your server { ... } block:

add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; upgrade-insecure-requests" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Reload with nginx -s reload (or sudo systemctl reload nginx on systemd hosts).

Apache

Inside your <VirtualHost>, or in an .htaccess in the document root:

<IfModule mod_headers.c>
  Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; upgrade-insecure-requests"
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

If the headers disappear on Apache, the most common cause is a mod_headers mismatch — confirm mod_headers is loaded (apache2ctl -M | grep headers).

Express (Node.js)

Use helmet, which supports upgradeInsecureRequests directly:

import helmet from 'helmet';
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        upgradeInsecureRequests: [],
      },
    },
  })
);

Cloudflare

If you front your site with Cloudflare, you can add the header from the dashboard:

  1. Go to Rules → Transform Rules → Modify Response Headers
  2. Create a rule matching all requests, action Set static, header Content-Security-Policy, value upgrade-insecure-requests
  3. Deploy

Setting the header at Cloudflare only protects requests that pass through the CDN. Set it at origin as well so direct-origin traffic is covered too.

Verify before you ship

Setting upgrade-insecure-requests is half the job. Two ways to confirm it is live:

1. Run the free NZAI scanner

The Free Website Security Check reports the mixed finding as fixed once the header is in place and there are no remaining http:// references in your templates. Run it after every config change.

2. Browser DevTools

Open any page on your site, press F12, switch to the Network tab, and reload. The Scheme column should show only https:// for every request. Any http:// row is a request that did not get upgraded — either because the CSP directive is missing, or because the third-party does not serve HTTPS at all.

Common mixed-content mistakes to avoid

From running the free scanner across New Zealand school and small-business sites, the most frequent missteps are:

  • Adding upgrade-insecure-requests but leaving http:// URLs in templates. The browser upgrades what it can; anything that has no HTTPS equivalent is silently blocked. Always fix the source, not just the symptom.
  • Forgetting third-party widgets. Old embedded YouTube iframes, old survey widgets, old chat plugins — anything pasted into the site years ago — is a likely culprit. Audit every iframe and every <script src="..."> tag.
  • CMS hardcoded URLs. Some CMSes store the site URL in a single setting; if that setting still reads http://, every generated link is mixed content. Update the setting once and the problem goes away everywhere.
  • Mixed content on staging only. A common pattern is to put HSTS and CSP on production, leave staging on plain HTTP, and have links between the two break. Either match staging's protocol to production, or block staging at the firewall.
  • Treating yellow-triangle warnings as cosmetic. Passive mixed content is technically less dangerous than active, but it still trains visitors to ignore warnings. A clean padlock on every page is worth chasing down.

Knowledge check

Test your understanding of HTTPS-everywhere and mixed content with these questions. Click or tap each card to flip it and see the answer.

Q1Your site is fully HTTPS and the address bar shows the green padlock. A parent emails to say they see a yellow triangle with a strike-through on the lock icon. The site is, in fact, fully HTTPS. What is the most likely cause?tap to flip
Answer: This is the classic mixed-content pattern. The main document is HTTPS, so the padlock shows, but at least one sub-resource on the page is still being requested over plain HTTP — usually a logo, an image, a web font, or an embedded widget. The browser shows the yellow triangle to warn that the page is not fully secure even though the main document is. Open the browser console (F12) and look for Mixed Content: warnings; each one names the offending URL. Fix the URL in your template or add the upgrade-insecure-requests CSP directive to make the browser rewrite the request automatically (1).
Q2You add Content-Security-Policy: upgrade-insecure-requests to your nginx config, reload, and check the page. The padlock is now solid green, but the school's old embedded survey widget is no longer visible on the page. What happened?tap to flip
Answer: The upgrade-insecure-requests directive rewrites every http:// request on the page to https:// before the browser issues it. If the third-party survey widget does not serve HTTPS at all, the upgraded request fails — and the browser blocks the response. This is the correct behaviour: you would rather see no widget than an insecure one. Fix it by either (a) switching to a survey vendor that serves HTTPS, (b) hosting the widget's iframe yourself on HTTPS, or (c) removing the widget. Browsers used to do a quieter form of upgrade for images; they do not do it for iframes (5).
Q3Your school site lives at https://yourschool.nz (no www). Hardcoded somewhere in the page footer is a link to https://www.yourschool.nz/about/. Visitors see a yellow-triangle warning on the lock icon. The link works. The page works. What is going on?tap to flip
Answer: You have a www ↔ apex redirect mismatch. Either www.yourschool.nz has no TLS certificate (so the browser silently falls back to HTTP for the sub-resource, creating mixed content), or www.yourschool.nz redirects to the apex in a way that loses the HTTPS flag on the way through. Pick one canonical hostname (almost always the apex for NZ schools) and either redirect every other hostname to it on HTTPS, or 404 them outright. Then audit every template, asset URL, and hardcoded link so only the canonical hostname appears. For the deeper redirect mechanics, see the Redirect Chains guide (1).

Sources and references

[1] Mozilla. (2025). MDN Web Docs — Mixed content. https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content

[2] CERT NZ. (2025). Top 11 cyber security tips for your business. https://www.cert.govt.nz/business/guides/top-tips-for-cyber-security/

[3] New Zealand. National Cyber Security Centre. (2025). Cyber security baseline controls for organisations. https://www.ncsc.govt.nz/

[4] Google. (2025). Chrome — Automatically upgrading mixed content to HTTPS. https://developer.chrome.com/blog/automatic-https-upgrades/

[5] W3C. (2025). CSP: upgrade-insecure-requests. https://www.w3.org/TR/upgrade-insecure-requests/

[6] New Zealand. Parliament. (2020). Privacy Act 2020. https://www.legislation.govt.nz/act/public/2020/0031/latest/whole.html

What to do next