Home

Fix this error · Browser and network

Fix: Mixed Content: This request has been blocked; the content must be served over HTTPS

Fix blocked http:// requests on an HTTPS page: env var API URLs, proxies that build http links, wss://, and upgrade-insecure-requests.

Your page was loaded over HTTPS and then asked for something over plain http://. Browsers block scripts, stylesheets, iframes, fetch() and XHR requests to http:// URLs from an HTTPS page. The fix is to change the URL to https://, which usually means fixing a hard-coded API URL or an environment variable, and to make sure the other server supports HTTPS.

FactDetail
ErrorMixed Content: The page at 'https://app.example.com/' was loaded over HTTPS, but requested an insecure resource 'http://api.example.com/items'. This request has been blocked; the content must be served over HTTPS.
Other forms… requested an insecure script '…' · … attempted to connect to the insecure WebSocket endpoint 'ws://…'
FirefoxBlocked loading mixed active content “http://api.example.com/items”
Where you see itBrowser console. Your code sees TypeError: Failed to fetch for blocked requests
Usual causeAn http:// API URL in an environment variable or in code, or a redirect from HTTPS to HTTP
Usual fixUse https:// (or a relative URL), and fix the server or proxy that sends http://
BugTape catches itBlocked fetch/XHR, yes, as a failed request after a click within 8 seconds. Blocked scripts and images, no

What the error means

A page served over HTTPS is protected from being read or changed on the way to the user. A request from that page over plain HTTP is not. If a script or an API response could be changed on the network, the attacker could change the whole page. So browsers block this "mixed content".

Browsers sort mixed content into two groups:

  • Upgradable: <img src>, <audio>, <video>, <source> and CSS images. The browser rewrites the request to https:// and tries that. If the host has no HTTPS, the image fails to load.
  • Blockable: everything else, including <script src>, stylesheets, <iframe>, fetch(), XMLHttpRequest, web fonts, <img srcset> and <picture>. These are blocked outright.

Requests to an IP address are blocked even when they would be upgradable. Loopback addresses such as http://localhost and http://127.0.0.1 count as secure, so local development does not show the error, which is one reason it first appears in production.

Common causes

  1. The API URL in an environment variable starts with http://. NEXT_PUBLIC_API_URL=http://api.example.com or VITE_API_URL=http://… copied from a local .env, then built into the production bundle.
  2. A hard-coded URL in code or content. An http:// link to an image, font, script or embed in a component, a CMS field or a Markdown file.
  3. The backend builds absolute URLs with http://. It sits behind a proxy that ends HTTPS, sees plain HTTP itself, and returns http:// in links, pagination next URLs, file URLs or redirects.
  4. A redirect from HTTPS to HTTP. A missing trailing slash or a login redirect sends the browser to http://, and the browser blocks the redirected request.
  5. A WebSocket on ws://. It must be wss:// on an HTTPS page.
  6. A third-party embed or old snippet that still uses http://.

How to fix it

Fix the environment variable and rebuild

Next.js and Vite inline public environment variables at build time. Changing the value on the host does nothing until you build again.

# .env.production
NEXT_PUBLIC_API_URL=https://api.example.com

Fail the build when the value is wrong, so it cannot ship again:

// lib/config.ts
const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? '';

if (process.env.NODE_ENV === 'production' && !apiUrl.startsWith('https://')) {
  throw new Error(`NEXT_PUBLIC_API_URL must use https in production, got "${apiUrl}"`);
}

export const API_URL = apiUrl;

Use relative URLs for your own origin

When the API is on the same host as the page, a path such as /api/items uses the page's own scheme and avoids the problem.

await fetch('/api/items');

Tell the framework to trust the proxy's X-Forwarded-Proto header so it knows the original request was HTTPS. In Express:

app.set('trust proxy', 1); // one proxy in front, such as a load balancer
// req.protocol is now 'https' for HTTPS requests

In Django, set SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'). In FastAPI with Uvicorn, run with --proxy-headers and set --forwarded-allow-ips to your proxy's address. Then check that links and redirects in API responses start with https://.

Use wss:// for WebSockets

const scheme = location.protocol === 'https:' ? 'wss' : 'ws';
const socket = new WebSocket(`${scheme}://${location.host}/ws`);

Upgrade remaining requests with a Content Security Policy

upgrade-insecure-requests tells the browser to request every http:// subresource URL on the page as https://, including blockable content. It is a safety net for old content you cannot rewrite at once. The other host must support HTTPS, or the upgraded request fails.

Content-Security-Policy: upgrade-insecure-requests

In Next.js, add it in next.config.js:

module.exports = {
  async headers() {
    return [{
      source: '/:path*',
      headers: [{ key: 'Content-Security-Policy', value: 'upgrade-insecure-requests' }],
    }];
  },
};

If you already send a Content Security Policy, add the directive to that header. It does not upgrade links that users follow to other sites. Use HSTS (Strict-Transport-Security) on your own domain for that.

Find every http:// URL

Search the code, environment files and CMS content for http://. In DevTools, the console lists each blocked URL, and the Network tab shows (blocked:mixed-content) in the status column.

How to catch it in production with BugTape

A blocked fetch() or XHR rejects with TypeError: Failed to fetch in Chrome (NetworkError when attempting to fetch resource. in Firefox, Load failed in Safari). The BugTape browser SDK records the failed request. When it follows a click within 8 seconds, BugTape reports User-triggered request failed: /items (failed to fetch) with the full http:// request URL, the method, the element the user clicked and the replay of the page. The http:// in the URL on an HTTPS page is the cause. Repeats of the same request from the same element become one issue with a count.

What BugTape does not report on its own:

  • The Mixed Content console line. The browser prints it itself and it does not pass through console, so it is not in the recorded console output.
  • Blocked scripts, stylesheets, images and iframes. These are not fetch or XHR requests, and a failed element load does not fire the window error event the SDK listens for. If a blocked script breaks your app, the error that follows, such as a missing global, is reported.
  • Requests not tied to a click, such as a request on page load. Catch the rejection and call reportError(), which fires the window error event BugTape listens for. Include the URL so the cause is in the title:
try {
  await fetch(`${API_URL}/items`);
} catch (err) {
  reportError(new Error(`GET ${API_URL}/items failed: ${(err as Error).message}`, { cause: err }));
}

The SDK records nothing before init() runs. Set release in init() so a fix that does not hold shows up: when you mark the issue fixed, a report from a newer release reopens it as a regression, for example when an old .env value comes back.

import { init } from '@bugtape/sdk';

init({ apiKey: 'YOUR_PROJECT_KEY', release: import.meta.env.VITE_COMMIT_SHA });

Your coding agent can read the issue over MCP (npx -y bugtape connect claude, or cursor or codex), see the http:// URL, and propose the change to the config or code. You review the pull request.

FAQ

Why does it work locally but fail in production?

Local development usually runs on http://localhost, and an HTTP page can load HTTP resources. Browsers also treat localhost as secure. In production the page is HTTPS, so every http:// request from it is mixed content.

Can I allow mixed content for my site?

Not for your users. A user can allow it for one site in their own browser settings, but you cannot turn the rule off from your page. Serve the resource over HTTPS, or proxy it through your own HTTPS server.

Does upgrade-insecure-requests fix everything?

It upgrades subresource requests from your page to https://. If the other server has no HTTPS, the request still fails. It does not change links to other sites. Fixing the URLs at the source is the lasting fix.

Why are images loading but my API calls are blocked?

Images are upgradable: the browser tries them over HTTPS automatically. fetch() and XHR are blockable, so the browser refuses them without trying HTTPS. Change the API URL to https://.

Sources

All guides: Fix an error.

Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.