Skip to content
Fixing API errors

Mixed content errors: calling an HTTP API from an HTTPS page

Your page is secure, the API is not, and the browser refuses to connect them. Why there is no client-side fix, and what to do when the API has no HTTPS endpoint.

SandyPublished 6 min read
red padlock on black computer keyboard, illustrating mixed content errors: calling an http api from an https page
Photo by FlyD on Unsplash.

Your site is on HTTPS. Your API call is not. The console says:

Mixed Content: The page at 'https://yourapp.com/' was loaded over HTTPS,
but requested an insecure resource 'http://api.example.com/data'.
This request has been blocked; the content must be served over HTTPS.

The request never left the machine. Unlike a CORS error, where the server responded and the browser withheld the result, here the browser refused before opening a connection.

Short answer

A secure page cannot make insecure requests. There is no client-side fix and no flag you can set for your users. Either use the API's https:// endpoint, or, if it has none, proxy the call through your own HTTPS server. Everything else is a workaround that only helps on your own machine.

Why browsers are absolute about this

A page served over HTTPS makes a promise: what you see was not tampered with in transit. One plain HTTP request breaks that promise entirely.

Anyone positioned between your user and the API, on public Wi-Fi, at an ISP, or anywhere along the path, can read and rewrite an HTTP response. If that response is JSON your page renders, they control what your users see. If it is a script, they control the page.

The padlock would be a lie. So browsers refuse, and they do not offer a developer-facing escape hatch, because any escape hatch would be used to ship insecure sites.

Check whether HTTPS already works

Most APIs support TLS and simply document the wrong URL, or their documentation predates their certificate. Test before doing anything else:

curl -sS -o /dev/null -w "%{http_code}\n" https://api.example.com/data

A 200 means you are finished. Change the scheme in your code and move on.

Watch for redirects, which will not save you:

curl -sS -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  http://api.example.com/data

If the HTTP endpoint 301s to HTTPS, that is good for curl and useless in the browser: the initial insecure request is blocked before the redirect is ever seen. You must start from https://.

When HTTPS genuinely is not available

Some APIs, particularly older government, academic and municipal services, have no TLS listener at all. The only approach that works is to put your own HTTPS server in the middle.

app/api/legacy/route.ts
export const revalidate = 300;
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const station = searchParams.get('station');
 
  if (!station || !/^[a-z0-9-]{1,32}$/i.test(station)) {
    return Response.json({ error: 'invalid station' }, { status: 400 });
  }
 
  // Server-to-server. No browser, so no mixed content rule applies.
  const upstream = await fetch(
    `http://legacy.example.gov/api?station=${encodeURIComponent(station)}`,
    { signal: AbortSignal.timeout(8000) },
  );
 
  if (!upstream.ok) {
    return Response.json({ error: 'upstream unavailable' }, { status: 502 });
  }
 
  return Response.json(await upstream.json());
}

Your browser talks to your origin over TLS. Your server talks to the legacy API over HTTP. The browser never sees an insecure request, so nothing is blocked.

This is worth being clear-eyed about: the connection between your server and the API is still unencrypted, and still interceptable. Proxying moves the risk rather than removing it. For public, non-sensitive data such as weather observations or timetables, that trade is usually fine. For anything involving credentials or personal data, an API with no TLS in 2026 is one you should not be using.

What does not work

upgrade-insecure-requests

Content-Security-Policy: upgrade-insecure-requests

This rewrites http:// to https:// before the request is made. It is genuinely useful when migrating a site whose resources all support TLS. It does nothing when the target has no HTTPS endpoint: you swap a blocked request for a failed connection.

Protocol-relative URLs

fetch('//api.example.com/data'); // inherits the page's scheme

On an HTTPS page this resolves to https://, so if the API has no TLS you are back to a connection failure. The pattern is also deprecated.

Telling users to allow insecure content

Browsers do let a visitor permit insecure content per site. Relying on that means asking every user to weaken their own security to use your site, which they will not do, and which support will not enjoy explaining.

Disabling security locally

Running Chrome with security flags disabled makes the error disappear on your machine only. Your users run normal browsers.

Why it worked in development

Almost always because your dev server runs on http://localhost. Both the page and the API are insecure, so nothing is mixed. Browsers additionally treat localhost as a secure context, so some behaviour differs there regardless.

To catch this before deploying, run local HTTPS:

next dev --experimental-https

Then a mixed content problem shows up on your machine rather than in production.

Choosing an API that avoids this

The simplest fix is to select for it up front. Every listing in our catalogue records whether the API is served over HTTPS, and you can filter on it.

Why browsers block it rather than warning

The behaviour looks heavy-handed until you consider what an unencrypted request from an encrypted page actually exposes.

The page arrived over HTTPS, so its contents were authenticated and encrypted. A request from that page over plain HTTP is neither. Anyone on the network path — a shared wifi access point, an ISP, anyone who has positioned themselves between the two — can read it and, more importantly, change it. A modified response is then executed or rendered inside a page the user believes is secure, with the padlock still showing.

That is the reason the block is absolute for active content like scripts and API responses, rather than being a warning. A warning would place the decision with someone who has no way to evaluate it, and the padlock would be making a promise the page could not keep.

Passive content — images, video, audio — is treated slightly more leniently in some browsers, because a tampered image is a smaller problem than tampered code. Even there the trend has been towards automatic upgrading or blocking, and relying on the leniency is building on something being actively removed.

The related mechanism worth knowing is automatic upgrading. Modern browsers will silently retry some mixed-content requests over HTTPS before blocking them. That is helpful and it makes the failure inconsistent: a request that works in one browser fails in another, and a resource that upgrades successfully today fails when the HTTPS endpoint goes down. Depending on it is depending on a fallback, not a fix.

Finding every instance before your users do

Mixed content is easy to miss because it frequently affects one feature rather than the whole page, and it does not appear at all when testing over plain HTTP locally.

Two mechanisms find it reliably. The console lists every blocked request explicitly, naming the URL, which is enough to fix it. And Content-Security-Policy can be used in report-only mode to collect violations from real traffic without breaking anything — which catches the paths your own testing did not reach.

The upgrade-insecure-requests directive is the pragmatic middle ground. It tells the browser to rewrite HTTP subresource requests to HTTPS automatically, which fixes every case where the origin supports HTTPS and you simply had the wrong scheme written down. It does not help where the origin genuinely has no HTTPS endpoint, and it is a transition aid rather than a permanent answer.

The cases it cannot fix are the ones needing a real decision: an API that is HTTP-only. There the options are to proxy it server-side, where no browser restriction applies, or to replace it. Our catalogue records HTTPS support per listing precisely so that choice can be made before you build, and the browser-ready collection lists the entries that are keyless, CORS-enabled and HTTPS together.

Summary

SituationWhat to do
HTTPS endpoint existsChange the scheme. Done.
HTTP redirects to HTTPSUse the HTTPS URL directly; the redirect is never reached
No TLS at all, public dataProxy through your own HTTPS server
No TLS at all, sensitive dataUse a different API
Works locally, fails deployedYour dev server is HTTP; test with local HTTPS

Mixed content blocking is not an obstacle to route around. It is the one guarantee HTTPS makes, enforced. When you hit it, the question to ask is not how to bypass it but why you are sending user data over a channel anyone can read.

Common questions

What is a mixed content error?

It happens when a page loaded over HTTPS tries to load a resource over plain HTTP. Browsers block active content such as scripts and fetch requests outright, because an attacker who can modify the insecure request can compromise the secure page.

Can I disable mixed content blocking for my users?

No. A visitor can override it for themselves in browser settings, but you cannot do it on their behalf, and you should not rely on it. Any fix must work for a default browser configuration.

Does upgrade-insecure-requests fix this?

Only when the API actually supports HTTPS. The directive rewrites http:// to https:// before the request is made. If the server has no TLS listener, the upgraded request fails to connect instead of being blocked.

Why does it work in development but not production?

Locally your page is usually served over http://localhost, so both are insecure and no mixing occurs. Localhost is also treated as a secure context by browsers. Deploy to HTTPS and the mismatch appears.

What if the API genuinely has no HTTPS endpoint?

Proxy it through your own HTTPS server. Your browser talks to you over TLS, and your server talks to the API over HTTP. That is the only approach that works in a default browser.

Sources

Written by

Sandy

I build and run this site on my own: the crawler that assembles the catalogue, the checker that probes every listing, and the writing. Before this I built SaveFromInternet and GrabReels, which meant living with other people’s APIs full time — parsers breaking when a platform shipped a change, rate limits arriving without warning, endpoints disappearing overnight. This directory exists because I got tired of free API lists that had never been checked.

APIs mentioned in this article

Zippopotam.us

Geocoding

The Zippopotamus API provides postal and zip code data for over 60 countries, allowing users to easily access detailed location information. It is particularly useful for form auto-completion and supports JSON response format for seamless integration.

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

Newton

Science & Math

Symbolic and Arithmetic Math Calculator

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

Read next