Sayeed's Blog

Quick Fix: Laravel CSS and Assets Not Loading in Ngrok (Mixed Content Error)

No Comments

If you have ever tried to share a local Laravel project with a remote client or test an external webhook, you have probably fired up a tunnel like ngrok, Cloudflare Tunnels, or Expose.

The tunnel connects perfectly, but when you open the URL, the site looks completely broken. The CSS isn't loading, the JavaScript is throwing errors, and your browser console is screaming about "Mixed Content."



Because modern tunnels serve your site over secure HTTPS, but your local Laravel app is trying to load assets over plain HTTP, the browser blocks the assets for security reasons. If you are using modern Laravel (version 11 or 12) with Vite, there are three quick steps to get your styling back on track.

Here is the quick fix.

1. Force Secure Requests via Meta Tag

The fastest way to tell the browser to upgrade all HTTP asset requests to HTTPS is by adding a Content Security Policy (CSP) meta tag.

Open your main blade layout file (usually resources/views/layouts/app.blade.php or resources/views/welcome.blade.php) and paste this directly inside the <head> section:

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

This acts as a blanket rule, ensuring that any asset (like a stylesheet or an image) requested over http:// is automatically bumped up to https://, matching your ngrok tunnel's protocol.

2. Trust All Proxies in Laravel

Since your app is sitting behind a proxy (the tunnel), Laravel doesn't natively realize it should be generating secure URLs. In older versions of Laravel, you would edit the TrustProxies middleware class.

In modern Laravel (11+), you handle this directly in the bootstrap/app.php file. Update your middleware configuration to trust all proxies by using the * wildcard:

// bootstrap/app.php

->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(at: '*');
})

This tells Laravel to respect the X-Forwarded-Proto headers sent by ngrok, meaning helper functions like asset() will now correctly generate https:// links.

3. Build Your Vite Assets (Don't Run Dev)

If you are using Vite (the default in modern Laravel), you might be used to keeping npm run dev running in the background.

However, Vite's development server runs on a specific local port (usually :5173) and relies on web sockets for Hot Module Replacement (HMR). When you access your site through an ngrok URL, the browser tries to find the Vite dev server at the ngrok address, which fails.

The fix: Shut down your Vite development server (Ctrl+C on the npm run dev process) and compile your assets for production instead.

Run the following command in your terminal:

npm run build

This compiles your CSS and JS into static files inside the public/build directory. Your blade files using the @vite() directive will now seamlessly load these static files instead of trying to connect to the local HMR server.

Summary Checklist:

  • [x] Added the <meta> tag to the <head> of your layout.
  • [x] Added $middleware->trustProxies(at: '*'); to bootstrap/app.php.
  • [x] Stopped npm run dev and ran npm run build.

Refresh your ngrok URL, and your fully styled, fully functional Laravel application should be ready to go.

Leave a Comment