How to Secure Laravel from Host Header Injection (HHI): A Complete Guide

Learn how Host Header Injection works and how to secure your Laravel project using trusted hosts, middleware, Nginx/Apache rules, and security best practices.

Monish Roy
Monish Roy
Published on November 19, 2025

If you've ever deployed a Laravel app behind Cloudflare, Nginx, a load balancer, or even just on a shared host, you've probably wondered: "Is my app safe if someone tampers with the Host header?"

The short answer: Probably not - unless you've taken the right steps. Host Header Injection is one of those sneaky vulnerabilities that rarely triggers alarms in scanners but can lead to password-reset poisoning, cache poisoning, phishing, and even full account takeovers.

In this guide I'll walk you through everything in plain English - no copy-paste fluff - with real-world examples that I've used (and seen exploited) in production apps.

What Exactly Is Host Header Injection?

The Host header tells the server which domain the client wants to reach. It's mandatory in HTTP/1.1.

GET /password/reset HTTP/1.1
Host: evil.com
...

If your Laravel app blindly trusts this header when generating URLs (password reset links, signed URLs, redirects, emails, etc.), an attacker can inject their own domain.

Common Real-World Attacks

  • Password Reset Poisoning – Reset link sent to https://evil.com/reset?token=abc
  • Web Cache Poisoning – CDN caches a poisoned page for all users (very common with Cloudflare/ Fastly)
  • Open Redirects / Phishing – Redirect to attacker-controlled domain
  • SSRF via signed URLs – Attacker changes host in temporary signed URL
  • Account takeover via OAuth / social login callbacks
True story: In 2023–2024 several Laravel SaaS apps were compromised via password reset poisoning because they only relied on config('app.url') without validating the incoming Host header.

Why Laravel Is Vulnerable Out-of-the-Box

Laravel uses Symfony's HttpFoundation component. By default it does trust the Host header and X-Forwarded-Host if the request comes from anywhere. That's fine for local development, but deadly in production behind proxies.

The Correct Way to Fix It in Laravel (2025)

1. Use Laravel's Built-in TrustHosts Middleware (The #1 Fix)

Laravel ships with \Illuminate\Http\Middleware\TrustHosts::class. Most developers forget to enable or configure it properly.

In app/Http/Kernel.php make sure it's at the top of the $middleware array (global):

protected $middleware = [
    \App\Http\Middleware\TrustHosts::class, // ← Put this first
    \App\Http\Middleware\TrustProxies::class,
    \Fruitcake\Cors\HandleCors::class,
    // ...
];

Then extend it (recommended) or configure directly:

// app/Http/Middleware/TrustHosts.php
namespace App\Http\Middleware;

use Illuminate\Http\Middleware\TrustHosts as Middleware;

class TrustHosts extends Middleware
{
    public function hosts(): array
    {
        return [
            '^(.+\.)?yourdomain\.com$',   // main domain + all subdomains
            '^(.+\.)?yourapp\.app$',
            // add staging, localhost if needed
            // 'localhost',
            // '127.0.0.1',
        ];
    }
}

Alternative super-simple version using APP_URL:

public function hosts(): array
{
    return [$this->allSubdomainsOfApplicationUrl()];
}

This automatically trusts example.com, www.example.com, api.example.com, etc. if your .env has APP_URL=https://example.com.

Once enabled, any request with a Host that doesn't match will throw a Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException → Laravel returns 400 Bad Request. Perfect.

2. Configure TrustProxies Correctly (Especially Behind Cloudflare / LB)

If you're behind any proxy (Cloudflare, AWS ELB, Nginx, Traefik, etc.) you must configure TrustProxies or Laravel will trust malicious X-Forwarded-Host headers.

// app/Http/Middleware/TrustProxies.php
protected $proxies = '*'; // or specific IPs: ['103.21.244.0/22', '103.22.200.0/22', ...] for Cloudflare

protected $headers = 
    \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_HOST |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_PORT |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_AWS_ELB;

// Better in 2025: only trust what you need
protected $headers = \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR | \Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO;

Pro tip: Do NOT trust X_FORWARDED_HOST unless your proxy is the only one that can set it (e.g. you override it in Nginx).

3. Override X-Forwarded-Host at the Web Server Level

Best defense in depth — make sure your reverse proxy always sets the correct value.

Nginx example:

proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;

Apache:

RequestHeader set X-Forwarded-Host %{HTTP_HOST}e

4. Never Use $request->getHost() for URL Generation

Bad:

URL::signedRoute('password.reset', [], null, false, $request->getHost());

Good:

// Always use config('app.url') or APP_URL
URL::signedRoute('password.reset'); // Laravel uses APP_URL automatically

// Or force the host
URL::signedRoute('password.reset', [], null, false, parse_url(config('app.url'), PHP_URL_HOST));

For password resets, Laravel already does this correctly if TrustHosts is active.

5. Server-Level Protection (Nginx/Apache Catch-All)

Even if Laravel is perfect, a misconfigured server can still serve your app on rogue domains.

Nginx catch-all block (Nginx):

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444; # or 444; closes connection without response
}

This returns no response for unknown Host headers — attackers get nothing to poison.

Testing If You're Still Vulnerable

Use curl:

curl -I -H "Host: evil.com" https://yourdomain.com/password/reset

# Should return 400 → you're safe
# If you see 200 or a redirect to evil.com → fix it now!

Or use Burp/ZAP and change Host header.

Quick Checklist (Copy-Paste Into Your Notes)

ActionDone?
Enable & configure TrustHosts middleware
Set APP_URL correctly in .env
Configure TrustProxies (especially behind proxy)
Do not trust X-Forwarded-Host if possible
Override X-Forwarded-* headers in web server
Add catch-all virtual host
Test with malicious Host header

Bonus: Laravel 11+ & Laravel 12 Changes (2025)

In Laravel 11/12 the TrustHosts middleware is still there but now you can configure it via the boot method if you prefer:

// App\Providers\AppServiceProvider.php
use Illuminate\Http\Request;

public function boot()
{
    if (app()->environment('production')) {
        Request::setTrustedHosts(['^(.+\.)?yourdomain\.com$']);
    }
}

Conclusion

Host Header Injection is not a "maybe" vulnerability - it's a "when" if you run behind any proxy or CDN.

The fix is literally 5-10 lines of configuration, yet it stops devastating attacks.

Implement TrustHosts + proper TrustProxies today and sleep better tonight.

Got a Laravel app in production right now? Go check your Kernel.php - I bet TrustHosts is commented out or missing. Fix it before someone else does it for you (the hard way).

Have questions or war stories? Drop them in the comments - I read every single one.

Happy (and secure) coding!
 - Monish Roy




Leave a Comment

Please to leave a comment.