Skip to content
Go back

Debugging Random HTTPS Redirects to `localhost` in Safari During Local Development

| Authored: 7 Aug, 2025 | Edited: 4 days ago

While working on a local Express.js project, I ran into a strange problem: occasionally, clicking a link like /about would open https://localhost:3002/about instead of http://localhost:3002/about. Since nothing was running over TLS locally, Safari would display:

Safarfi displaying error page, cannot open https://localhost:3002/about

Inspecting the HTML confirmed there were no https:// URLs, just relative links. Yet sometimes they worked, sometimes they didn’t. Even more oddly, my server logs showed no incoming request when it failed. If I manually changed https:// to http:// in the address bar, the page loaded fine.


The Cause

This wasn’t a server bug, it was the browser.

Safari (and other browsers) can force HTTPS in certain situations, even for localhost:

  1. HSTS caching
    If a site ever sends a Strict-Transport-Security (HSTS) header, browsers remember it and will always try HTTPS for that host, even if you later serve it over HTTP. Once set, HSTS applies to all future requests until it expires or is manually cleared.

  2. HTTPS-Only / Auto-upgrade
    Some browsers offer an “upgrade to HTTPS” mode. If it’s on, any HTTP link may be rewritten to HTTPS automatically.

  3. Other scheme-forcers
    A <base href="https://...">, a service worker redirect, or cached state from another app using the same hostname/port.


Why It Happened in My Case

Looking at my app.js, I was using helmet for security headers:

const helmetOptions = {};
if (process.env.NODE_ENV === "production") {
  helmetOptions.contentSecurityPolicy = {
    /* ... */
  };
} else {
  helmetOptions.contentSecurityPolicy = false;
}
app.use(helmet(helmetOptions));

By default, helmet sends an HSTS header unless you explicitly disable it. So even in development, Safari may have cached HSTS for localhost, causing it to auto-upgrade requests.


The Fix

Here’s what I did:

1. Disable HSTS in development

if (process.env.NODE_ENV === "production") {
  helmetOptions.contentSecurityPolicy = {
    /* strict CSP */
  };
} else {
  helmetOptions.contentSecurityPolicy = false;
  helmetOptions.hsts = false; // Prevents HTTPS auto-upgrade in dev
}
app.use(helmet(helmetOptions));

2. Clear HTTPS state for localhost in Safari

Safari → SettingsPrivacyManage Website Data…
Search for localhost and 127.0.0.1 → Remove.

3. Check for other causes


The Result

After:

… the random HTTPS redirects disappeared completely.


Tip: If you ever run a local dev server behind a reverse proxy (like Caddy) that uses HTTPS for localhost, browsers may cache that as HSTS too. In that case, either use a different dev hostname (e.g., http://dev.local:3002) or clear the cached state before switching back to plain HTTP.


Share this post on:

Previous Post
How to avoid breaking globally installed node tools during Homebrew NVM upgrades
Next Post
Uncovering Audio Source with macOS Control Center