This Connection Is Not Private

Safari 'This Connection Is Not Private' on localhost: Real Fix

Safari blocks localhost when your self-signed cert expires. Here's how to regenerate it and tell Safari to trust it. Takes about two minutes.

You fire up your local dev server, hit https://localhost:3000 in Safari, and get slapped with "This Connection Is Not Private" — even though you know full well it's your own machine.

Here's the fix, then why it happens.

Step 1: Confirm the cert is actually expired

Don't guess. Open Terminal and check:

openssl x509 -in /path/to/your/cert.pem -noout -dates

You'll get something like:

notBefore=Jan  4 10:22:01 2024 GMT
notAfter=Jan  4 10:22:01 2025 GMT

If today's date is past notAfter, that's your culprit. If you don't know where your cert lives, common spots are ./certs/, ./ssl/, ~/.localhost-certs/, or wherever your dev server config points. Webpack Dev Server, Vite, and Next.js each default to slightly different filenames.

Step 2: Regenerate the cert

Quickest path if you don't have mkcert installed:

mkdir -p ~/.localhost-certs
cd ~/.localhost-certs
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout localhost-key.pem \
  -out localhost-cert.pem \
  -days 825 \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

The -addext flag matters. Safari is stricter than Chrome about SANs — a cert with only CN=localhost and no subjectAltName will still trigger the warning on recent macOS versions. Modern Safari on Sonoma effectively ignores CN entirely.

If you already use mkcert, even better:

mkcert -install
mkcert localhost 127.0.0.1 ::1

That drops two files in your current directory and installs the local CA into your system keychain automatically. Skips Step 3 entirely.

Step 3: Trust the cert in Keychain (if you didn't use mkcert)

Open Keychain Access (Applications → Utilities). Drag your localhost-cert.pem into the System keychain. It'll show up as "localhost." Double-click it, expand Trust, and set "When using this certificate" to Always Trust. Close the window, enter your password.

Now point your dev server at the new key and cert files. In Vite that's:

// vite.config.js
server: {
  https: {
    key: fs.readFileSync('~/.localhost-certs/localhost-key.pem'),
    cert: fs.readFileSync('~/.localhost-certs/localhost-cert.pem'),
  }
}

Restart the server. Hard-refresh Safari with Cmd+Shift+R. Done.

Why this works

Browsers treat certificates as valid only within a time window. When your self-signed cert's notAfter date passes, every request to that origin fails the TLS handshake at the certificate validation step. Safari shows "This Connection Is Not Private" and refuses to proceed — no "Advanced" bypass link like Chrome gives you on localhost.

Regenerating gives you a fresh validity window. Adding the SAN extension tells Safari the cert is legitimately for both localhost and 127.0.0.1, which Chrome infers but Safari doesn't. Trusting it in the System keychain promotes it from "self-signed, unknown authority" to "explicitly trusted by the user" — Safari's highest trust tier short of a real CA.

The reason trusting the cert alone doesn't fix an expired one: trust and validity are separate checks. You can trust a cert forever, but if it expired yesterday, Safari won't touch it.

Less common variations

The cert is valid but Safari still complains

Check the clock. A VM with a skewed system time will make every cert look expired or not-yet-valid. Run date in Terminal. If it's off by more than a few minutes, fix NTP:

sudo sntp -sS time.apple.com

You regenerated but Safari still shows the old cert

Safari aggressively caches TLS state. Quit it fully (Cmd+Q, not just close the window), then reopen. If that doesn't work, clear the cert cache:

sudo rm -rf ~/Library/Caches/com.apple.Safari
killall -HUP Safari

You're on a .local hostname instead of localhost

Bonjour-resolved names like myapp.local need that name in the SAN list too. Reissue with -addext "subjectAltName=DNS:localhost,DNS:myapp.local,IP:127.0.0.1". Otherwise Safari does a reverse lookup and flags a mismatch.

Node's built-in https module

If you're running your own https server instead of a dev framework, the same key/cert paths go in the https.createServer() options object. Nothing else changes.

Chrome is fine, Safari isn't

Almost always the SAN issue. Chrome falls back to CN matching; Safari on macOS 13+ requires SAN. Reissue with the -addext flag above and you're sorted.

Prevention

Stop hand-rolling certs. Install mkcert once and forget about expiry for years — it issues certs valid for 825 days by default, and you can extend that:

mkcert -cert-file localhost-cert.pem -key-file localhost-key.pem \
  -days 3650 localhost 127.0.0.1 ::1

Add a note to your project README with the regeneration command. I had a client last month whose entire local dev setup died because nobody documented how their certs were generated — the original dev had left the company, and the rest of the team spent a day reverse-engineering a webpack config. Don't be that team.

Also: don't reuse production certs for local dev. Ever. If it leaks, you're revoking a live cert and breaking real users.

Set a calendar reminder 30 days before expiry if you're stuck with manual certs. That's the difference between a two-minute fix and a lost afternoon.

Related Errors in Software – Web Browsers
Edge Crashes on Launch After Windows 10 KB5006670 Fix Browser Freezing on Windows 10/11 – Step-by-Step Troubleshooting Firefox Opens Links in New Windows Instead of Tabs? Fix It Aw, Snap! Fix 'Aw, Snap!' Chrome Crashes: Clear Cached Graphics

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.