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

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.

| Fact | Detail |
|---|---|
| Error | `Mixed 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://…'` |
| Firefox | `Blocked loading mixed active content “http://api.example.com/items”` |
| Where you see it | Browser console. Your code sees `TypeError: Failed to fetch` for blocked requests |
| Usual cause | An `http://` API URL in an environment variable or in code, or a redirect from HTTPS to HTTP |
| Usual fix | Use `https://` (or a relative URL), and fix the server or proxy that sends `http://` |
| BugTape catches it | Blocked `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.

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

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

```ts
// 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.

```ts
await fetch('/api/items');
```

### Fix a backend that returns http:// links behind a proxy

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

```js
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

```ts
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`:

```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:

```ts
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.

```ts
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

- [MDN: Mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Mixed_content), checked 2 Nov 2026.
- [MDN: CSP upgrade-insecure-requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/upgrade-insecure-requests), checked 2 Nov 2026.
- [Chromium mixed_content_checker.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/loader/mixed_content_checker.cc) (console messages), checked 2 Nov 2026.
- [Firefox security.properties](https://github.com/mozilla-firefox/firefox/blob/main/dom/locales/en-US/chrome/security/security.properties) (`BlockMixedActiveContent`), checked 2 Nov 2026.
- [Express: behind proxies](https://expressjs.com/en/guide/behind-proxies/), checked 2 Nov 2026.

## Related errors

- [TypeError: Failed to fetch](/guides/typeerror-failed-to-fetch/): `TypeError: Failed to fetch`
- [CORS: No 'Access-Control-Allow-Origin' header](/guides/cors-blocked-no-access-control-allow-origin/): `has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.`
- ["Script error." from cross-origin scripts](/guides/script-error-cross-origin/): `Script error.`
- [Cannot read properties of undefined](/guides/cannot-read-properties-of-undefined/): `TypeError: Cannot read properties of undefined (reading 'map')`
- [Unexpected token < is not valid JSON](/guides/unexpected-token-not-valid-json/): `SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON`

All guides: [Fix an error](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
