Skip to main content

NGINX / Security

NGINX Headers-More Module: Complete Guide

by ,


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

The NGINX headers-more module gives you complete control over HTTP request and response headers. Unlike the built-in add_header directive, the NGINX headers-more module lets you set, clear, and replace any header. This includes protected headers like Server, Content-Type, and Cache-Control.

Whether you’re hardening your server’s security, integrating with backend services, or implementing caching strategies, the NGINX headers-more module provides flexibility that native NGINX directives lack.

Why Use the NGINX Headers-More Module?

The native add_header directive in NGINX has significant limitations:

Limitation add_header headers-more
Modify Server header No Yes
Clear existing headers No Yes
Modify request headers No Yes
Filter by status code No Yes
Filter by content type No Yes
Wildcard header patterns No Yes
Works in if blocks Clears all headers Works correctly

The NGINX headers-more module solves the notorious add_header inheritance problem. Headers disappear when you add a new one inside a nested location block. This module fixes that behavior.

Installation

RHEL, CentOS, AlmaLinux, Rocky Linux

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-headers-more

Then add the following to the top of /etc/nginx/nginx.conf:

load_module modules/ngx_http_headers_more_filter_module.so;

Debian and Ubuntu

First, set up the GetPageSpeed APT repository, then install:

sudo apt-get update
sudo apt-get install nginx-module-headers-more

On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.

For more details, see the RPM module page or APT module page.

Directives Reference

The NGINX headers-more module provides four directives for manipulating HTTP headers.

more_set_headers

Sets or replaces response (output) headers.

Syntax: more_set_headers [-t <content-type>]... [-s <status-code>]... <header>...

Context: http, server, location, location if

Phase: Output header filter

This directive sets headers in the response sent to clients. If a header with the same name exists, it gets replaced.

# Set a custom Server header
more_set_headers "Server: MyApp/2.0";

# Set multiple headers at once
more_set_headers "X-Frame-Options: DENY" "X-Content-Type-Options: nosniff";

# Set headers only for specific status codes
more_set_headers -s "500 502 503 504" "X-Error-Handler: true";

# Set headers only for specific content types
more_set_headers -t "text/html text/plain" "X-Text-Content: true";

# Combine status and content-type filters
more_set_headers -s "200" -t "application/json" "X-API-Response: true";

more_clear_headers

Removes response headers entirely.

Syntax: more_clear_headers [-t <content-type>]... [-s <status-code>]... <header>...

Context: http, server, location, location if

Phase: Output header filter

# Remove the Server header (hides NGINX version)
more_clear_headers Server;

# Remove multiple headers
more_clear_headers "X-Powered-By" "X-AspNet-Version";

# Use wildcards to remove all headers matching a pattern
more_clear_headers "X-Debug-*";

The wildcard * character can only appear at the end of header names.

more_set_input_headers

Sets or replaces request (input) headers before they reach your application.

Syntax: more_set_input_headers [-r] [-t <content-type>]... <header>...

Context: http, server, location, location if

Phase: Rewrite tail

Use this directive to modify incoming request headers. This is useful when proxying to backend servers.

location /api {
    # Set a custom header for the backend
    more_set_input_headers "X-Real-IP: $remote_addr";

    # Override the Host header
    more_set_input_headers "Host: api.internal.example.com";

    proxy_pass http://backend;
}

# Only replace if header already exists (-r flag)
more_set_input_headers -r "Authorization: Bearer $new_token";

The -r flag ensures the header is only modified if it already exists in the request.

more_clear_input_headers

Removes request headers before they reach your application.

Syntax: more_clear_input_headers [-t <content-type>]... <header>...

Context: http, server, location, location if

Phase: Rewrite tail

# Remove potentially dangerous headers before proxying
more_clear_input_headers "X-Forwarded-For" "X-Real-IP";

# Remove all headers matching a pattern
more_clear_input_headers "X-Custom-*";

Security Hardening

System administrators should use the NGINX headers-more module to implement defense-in-depth strategies. Here are the essential security configurations.

Hide Server Information

Attackers scan for specific server versions to exploit known vulnerabilities. Remove or obfuscate server identification headers.

http {
    # Completely remove the Server header
    more_clear_headers Server;

    # OR set a custom, non-revealing value
    # more_set_headers "Server: WebServer";

    server {
        # ...
    }
}

Remove Backend Technology Headers

PHP, ASP.NET, and other frameworks often expose version information. Remove all of them using the NGINX headers-more module.

http {
    # Remove common framework headers
    more_clear_headers "X-Powered-By";
    more_clear_headers "X-AspNet-Version";
    more_clear_headers "X-AspNetMvc-Version";
    more_clear_headers "X-Runtime";
    more_clear_headers "X-Version";

    # Use wildcards for thoroughness
    more_clear_headers "X-Powered-*";
    more_clear_headers "X-AspNet-*";

    server {
        # ...
    }
}

Implement Security Headers

The security-headers module handles this automatically. However, you can use the NGINX headers-more module for fine-grained control.

server {
    # Prevent MIME type sniffing
    more_set_headers "X-Content-Type-Options: nosniff";

    # Control iframe embedding
    more_set_headers "X-Frame-Options: SAMEORIGIN";

    # Enable XSS filter (legacy, but still useful)
    more_set_headers "X-XSS-Protection: 1; mode=block";

    # Control referrer information
    more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";

    # Restrict browser features
    more_set_headers "Permissions-Policy: geolocation=(), microphone=(), camera=()";

    location / {
        # ...
    }
}

Sanitize Proxy Requests

When using NGINX as a reverse proxy, sanitize incoming headers. This prevents header injection attacks.

server {
    location /api {
        # Remove client-supplied forwarding headers (prevent spoofing)
        more_clear_input_headers "X-Forwarded-For";
        more_clear_input_headers "X-Forwarded-Proto";
        more_clear_input_headers "X-Forwarded-Host";
        more_clear_input_headers "X-Real-IP";

        # Set trusted forwarding headers
        more_set_input_headers "X-Forwarded-For: $remote_addr";
        more_set_input_headers "X-Forwarded-Proto: $scheme";
        more_set_input_headers "X-Real-IP: $remote_addr";

        proxy_pass http://backend;
    }
}

Status-Based Error Headers

Add debugging headers only for error responses. This helps troubleshooting without exposing info on success.

server {
    # Add diagnostic headers only for errors
    more_set_headers -s "400 401 403 404" "X-Error-Type: client";
    more_set_headers -s "500 502 503 504" "X-Error-Type: server";

    # Add cache-control for errors
    more_set_headers -s "500 502 503 504" "Cache-Control: no-store";

    location / {
        # ...
    }
}

Using Variables in Headers

The NGINX headers-more module supports NGINX variables in header values. This enables dynamic content.

server {
    location / {
        # Add request tracking
        more_set_headers "X-Request-ID: $request_id";

        # Add timing information
        more_set_headers "X-Request-Time: $request_time";

        # Add server information (for debugging)
        more_set_headers "X-Server-Name: $hostname";

        # Custom application version from a variable
        set $app_version "2.1.0";
        more_set_headers "X-App-Version: $app_version";
    }
}

Variables work only in header values, not in header names. This is for performance.

Content-Type Filtering

Apply headers only to specific content types using the -t flag.

server {
    # Add CSP only to HTML responses
    more_set_headers -t "text/html" "Content-Security-Policy: default-src 'self'";

    # Add CORS headers only to JSON API responses
    more_set_headers -t "application/json" "Access-Control-Allow-Origin: *";

    # Multiple content types in one directive
    more_set_headers -t "text/html text/plain" "X-Text-Response: true";

    location / {
        # ...
    }
}

Important: Don’t include charset in the -t option. Use only the MIME type (e.g., text/html).

Common Use Cases

API Gateway Configuration

server {
    listen 443 ssl http2;
    server_name api.example.com;

    # Remove server information
    more_clear_headers Server;

    # CORS headers for all API responses
    more_set_headers "Access-Control-Allow-Origin: https://app.example.com";
    more_set_headers "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS";
    more_set_headers "Access-Control-Allow-Headers: Authorization, Content-Type";

    location / {
        # Sanitize incoming headers
        more_clear_input_headers "X-Internal-*";

        # Add request metadata for backend
        more_set_input_headers "X-Request-ID: $request_id";
        more_set_input_headers "X-Forwarded-For: $remote_addr";

        proxy_pass http://api_backend;
    }
}

Caching Configuration

server {
    location /static/ {
        # Set aggressive caching for static assets
        more_set_headers "Cache-Control: public, max-age=31536000, immutable";

        # Remove unnecessary headers
        more_clear_headers "Pragma";
        more_clear_headers "Expires";

        root /var/www/html;
    }

    location /api/ {
        # Prevent caching of API responses
        more_set_headers "Cache-Control: no-store, no-cache, must-revalidate";
        more_set_headers "Pragma: no-cache";

        proxy_pass http://backend;
    }
}

Multi-Tenant Headers

map $host $tenant_id {
    tenant1.example.com  "tenant-001";
    tenant2.example.com  "tenant-002";
    default              "unknown";
}

server {
    # Pass tenant identification to backend
    more_set_input_headers "X-Tenant-ID: $tenant_id";

    location / {
        proxy_pass http://backend;
    }
}

Limitations

Connection Header

The Connection header cannot be modified. It’s generated by NGINX’s core header filter. This filter runs after headers-more. The Connection header must reflect the actual connection state.

Server Header in Subrequests

The more_clear_headers Server directive affects only the main request. For subrequests, you may still see the Server header.

if Block Restrictions

The more_set_headers directive works in location if blocks. It does not work in server if blocks:

server {
    # This does NOT work
    if ($request_uri ~* "^/api") {
        more_set_headers "X-API: true";  # Will fail!
    }

    # Use location blocks instead
    location /api {
        more_set_headers "X-API: true";  # Works correctly
    }
}

Performance

The NGINX headers-more module operates at the filter phase with minimal overhead:

  1. No external I/O: All operations happen in memory
  2. Lazy registration: Filters register only when directives are used
  3. Efficient matching: Status and content-type checks use optimized lookups

For most deployments, the module adds sub-millisecond latency. Avoid many wildcard patterns in high-traffic locations.

Testing Your Configuration

After configuring headers-more, verify the results:

# Check response headers
curl -I https://your-server.com/

# Verify Server header is hidden
curl -I https://your-server.com/ | grep -i server

# Check headers for specific status codes
curl -I https://your-server.com/nonexistent-page

# Check API headers
curl -I https://your-server.com/api/health

Validate your configuration before reloading:

nginx -t
systemctl reload nginx

Troubleshooting

Headers Not Appearing

  1. Check directive context: Ensure the directive is in the correct block
  2. Check filter order: Proxy modules may override headers after headers-more
  3. Verify module is loaded: Run nginx -T 2>&1 | grep headers_more

Headers Appearing Multiple Times

The NGINX headers-more module replaces headers. If you see duplicates, check for:

  • Multiple add_header directives (native NGINX)
  • Backend servers adding the same headers
  • Other modules like proxy_hide_header

Wildcard Patterns Not Working

Wildcards only work at the end of header names:

# Correct
more_clear_headers "X-Debug-*";

# Incorrect (will not match anything)
more_clear_headers "*-Debug";
more_clear_headers "X-*-Debug";

Conclusion

The NGINX headers-more module is essential for system administrators who need precise header control. Whether you’re implementing security hardening, integrating with APIs, or managing caching, this module provides capabilities that native directives cannot match.

For source code and examples, visit the headers-more-nginx-module repository on GitHub.

Related articles:
NGINX Security Headers Module – Automatic security headers
NGINX Cookie Flag Module – Cookie security attributes
NGINX Bot Protection – Anti-bot measures

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.