Nginx Variables Explained: $host vs $http_host vs $server_name – Key Differences and Use Cases


2 views

When working with Nginx configuration, these three variables often cause confusion:

# The server_name defined in your nginx.conf
$server_name = configured server name in server block

# The 'Host' header from HTTP request
$http_host = raw HTTP Host header (including port if present)

# Processed hostname (no port, lowercase, from $http_host or $server_name)
$host = normalized hostname (no port)

Let's examine how each variable behaves in different scenarios:

# Request: GET / HTTP/1.1 Host: example.com:8080

$http_host → "example.com:8080"  # Raw header exactly as received
$host → "example.com"            # Strips port automatically
$server_name → "example.com"     # From server configuration

When the Host header is missing, Nginx will:

1. Use $server_name for $host
2. Leave $http_host empty

For your specific rewrite case, I recommend:

location = /vb/showthread.php {
    if ($arg_p) {
        # Best practice for canonical URLs
        return 301 $scheme://$host/forum/index.php?posts/$arg_p/;
    }
}

Why $host is preferred here:

  • Automatically handles port normalization
  • More secure than raw $http_host
  • Falls back to $server_name when needed

Important differences in security context:

# Potential XSS risk with raw $http_host:
add_header X-Host $http_host;  # Unsafe if header contains malicious chars

# Safer alternative:
add_header X-Host $host;       # Normalized value

When working with non-standard ports:

# For applications that need the original port
set $full_host $host:$server_port;

# Versus:
set $full_host $http_host;  # Might be empty if no Host header

To inspect these values during development:

location /debug {
    add_header X-Debug-Host $host;
    add_header X-Debug-Http_Host $http_host;
    add_header X-Debug-Server_Name $server_name;
    return 200 "Debug information sent in headers";
}

When working with Nginx rewrite rules, these three variables often cause confusion:

  • $host: Contains the hostname from the request line or the "Host" header (when present). It's normalized to lowercase and excludes port numbers.
  • $http_host: Exactly matches the "Host" HTTP header as sent by the client, including port if specified.
  • $server_name: Represents the server_name directive matched for the current request.

Consider these real-world scenarios:

# Client request: GET / HTTP/1.1
# Host: example.com:8080

# Configuration:
server {
    server_name example.com;
    listen 80;
    
    location /test {
        return 200 "$host|$http_host|$server_name";
    }
}

The response would be: example.com|example.com:8080|example.com

For your specific rewrite case:

location = /vb/showthread.php {
    if ($arg_p) {
        return 301 $scheme://$host/forum/index.php?posts/$arg_p/;
    }
}

Why $host is optimal here:

  • It's safer than $http_host as it's normalized (no port issues)
  • It's more dynamic than $server_name when dealing with multiple domains
  • Works correctly even if Host header is missing (falls back to server_name)

Important behaviors to note:

  1. When Host header is missing:
    • $host falls back to server_name
    • $http_host becomes empty
  2. With non-standard ports:
    • $host strips the port
    • $http_host preserves it
  3. With invalid Host headers:
    • $host uses the valid portion
    • $http_host shows the raw (possibly invalid) value

For complex multi-domain setups:

server {
    server_name primary.com secondary.com;
    
    if ($host != $server_name) {
        return 301 $scheme://$server_name$request_uri;
    }
}

For port-agnostic redirects:

location /secure {
    return 301 https://$host$request_uri;
}