NGINX Host Header Spoofing

Understand how attackers exploit the Host header in NGINX reverse proxy configurations and learn secure patterns to prevent it.

Updated: January 2025 * 8 min read

What is Host Header Spoofing?

Host header spoofing occurs when an attacker manipulates the HTTP Host header to trick the application into generating URLs, redirects, or cached content pointing to a malicious domain. This is possible when NGINX passes user-controlled Host headers to backend applications.

Real-World Impact

Host header attacks have been used for:

  • Password reset poisoning - Reset links pointing to attacker's domain
  • Web cache poisoning - Caching malicious content for all users
  • Server-side request forgery - Accessing internal services
  • Virtual host confusion - Accessing other sites on the same server

How the Attack Works

Normal Request:
GET /reset-password HTTP/1.1
Host: legitimate-site.com
Spoofed Request:
GET /reset-password HTTP/1.1
Host: attacker-site.com
If the backend uses the Host header to generate URLs, the reset link will point to attacker-site.com

The Dangerous Variables

NGINX has several variables related to the Host header:

  • $http_host - The raw Host header from the client request (user-controlled!)
  • $host - Hostname from request line, Host header, or server_name (in that order)
  • $server_name - The first server_name in the configuration (safe)
Key Insight: $http_host is always user-controlled. $host can be user-controlled in some cases. $server_name is always server-defined.

Vulnerable Patterns

Pattern 1: Passing $http_host to Backend

# Passes raw user-controlled Host header to backend
location / {
    proxy_pass http://backend;
    proxy_set_header Host $http_host;
}

# Attacker can send: Host: evil.com
# Backend receives: Host: evil.com

Pattern 2: Using $host Without server_name Validation

# Default server catches all requests
server {
    listen 80 default_server;
    # No server_name defined!

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
    }
}

# $host falls back to the request's Host header

Pattern 3: X-Forwarded-Host with User Input

# Passes user Host header as X-Forwarded-Host
location / {
    proxy_pass http://backend;
    proxy_set_header X-Forwarded-Host $http_host;
}

# If backend trusts X-Forwarded-Host for URL generation...

Secure Patterns

Use Hardcoded Host Header

# Always use the expected hostname
location / {
    proxy_pass http://backend;
    proxy_set_header Host "backend.internal";
}

# Backend always receives the correct host

Use $server_name

server {
    listen 80;
    server_name www.example.com example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $server_name;
    }
}

# Always uses the configured server_name

Define server_name and Reject Unknown Hosts

# Catch-all server that rejects unknown hosts
server {
    listen 80 default_server;
    server_name _;
    return 444;  # Close connection without response
}

# Actual server with explicit server_name
server {
    listen 80;
    server_name www.example.com example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;  # Safe because server_name is defined
    }
}

Validate Host Header in Application

# Even with safe NGINX config, validate in your application
# Django example:
ALLOWED_HOSTS = ['www.example.com', 'example.com']

# This is defense-in-depth

When $host is Safe

The $host variable is safe to use when ALL of these conditions are met:

  1. Your server block has an explicit server_name directive
  2. You have a catch-all default server that rejects unknown hosts
  3. The request matches your server_name (NGINX validated it)

Detecting with Gixy

Gixy automatically detects host header spoofing vulnerabilities:

$ gixy /etc/nginx/nginx.conf

==================== Results ====================

[host_spoofing] The proxied Host header may be spoofed.
  Severity: MEDIUM
  Description: Using $http_host or user-controlled variables for
               the Host header when proxying is insecure.
  Reason: Using "$http_host" for the "Host" header allows clients
          to control the value.
  Pseudo config:
      server {
          location / {
              proxy_pass http://backend;
              proxy_set_header Host $http_host;
          }
      }
  File: /etc/nginx/conf.d/proxy.conf
  Line: 8

==================== Summary ====================
Total issues: 1 (Medium: 1)

Testing for Vulnerability

# Test with curl
curl -H "Host: evil.com" https://target.com/password-reset

# If the response contains "evil.com" in any URLs, it's vulnerable

# Test X-Forwarded-Host
curl -H "X-Forwarded-Host: evil.com" https://target.com/password-reset

Prevention Checklist

  • Never use $http_host in proxy_set_header Host
  • Always define explicit server_name in server blocks
  • Create a catch-all default_server that rejects unknown hosts
  • Use $server_name or hardcoded hostnames for proxied Host headers
  • Configure ALLOWED_HOSTS or equivalent in your backend application
  • Be careful with X-Forwarded-Host headers too
  • Run Gixy to automatically detect these issues

Further Reading