Share a Laravel app
Share a Laravel app from php artisan serve with a Horizon tunnel, with HTTPS links, trusted proxies and APP_URL set.
Share your Laravel app from php artisan serve with a public HTTPS URL, with generated links pointing at the tunnel.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - A Laravel 11 or later app. The
bootstrap/app.phpcode below uses its middleware API.
Start a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:8000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:8000
Request log https://hrzn.run/dashboard/tunnels/my-appYour public URL is https://my-app.hrzn.run. Keep this terminal open.
Trust the forwarded host
During a web request, url() and route() use the scheme and host of the current request. The CLI sets Host to the local address, so Laravel builds links with that address. The public host arrives in X-Forwarded-Host.
Tell Laravel to trust that header with the TrustProxies middleware. Trust the host header only. Leave X-Forwarded-Proto and X-Forwarded-Port out, because the values Horizon sends don't describe the public https address.
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(
at: '*',
headers: Request::HEADER_X_FORWARDED_HOST,
);
})at: '*' trusts every sender. That's fine on your laptop, where only the Horizon CLI connects. Remove it before you deploy.
Set the https scheme and APP_URL
Tell Laravel the public scheme explicitly. URL::forceScheme('https') makes url(), route() and asset() generate https links.
use Illuminate\Support\Facades\URL;
public function boot(): void
{
if ($this->app->environment('local')) {
URL::forceScheme('https');
}
}APP_URL is the URL Laravel uses where no request exists, such as Artisan commands and queued jobs. Set it to the public URL so links in queued emails and notifications point at the tunnel.
APP_URL=https://my-app.hrzn.runRun php artisan config:clear if you cached your configuration, then restart php artisan serve.
Check it works
Add a route that prints the URL Laravel generates.
use Illuminate\Support\Facades\Route;
Route::get('/where', fn () => url('/'));Open https://my-app.hrzn.run/where. On the first visit Horizon shows a Before you continue page. Select Continue to site. The page prints https://my-app.hrzn.run. The Horizon terminal prints:
GET 200 /whereTroubleshooting
Links start with http://127.0.0.1:8000
Laravel isn't trusting X-Forwarded-Host. Check trustProxies in bootstrap/app.php and that the headers include Request::HEADER_X_FORWARDED_HOST.
Links start with http:// instead of https://
Check that URL::forceScheme('https') runs. It's inside an environment('local') check, so APP_ENV must be local.
Scripts and styles don't load
If you run npm run dev, Vite serves assets from your own machine, which a visitor can't reach. Stop it and run npm run build instead. Laravel then serves the built files through the tunnel.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and update APP_URL. -s needs a subdomain you reserved, see Pricing.
Nothing reaches your app
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that
php artisan servelistens on port 8000, the port in yourhrzn tunnelcommand.
Next steps
- Read Laravel's guide to configuring trusted proxies.
- Share a different stack: Rails or Django.
Share a Rails dev server
Share a Rails dev server with a Horizon tunnel, and fix the "Blocked hosts" page with config.hosts and the CSRF origin check.
Expose a Docker container
Expose a Docker container running a web app to the internet with a Horizon tunnel, using a published port such as -p 3000:3000.