How to Harden Nginx Web Server Configuration: 8 Essential Security Measures

Harden your Nginx web server by disabling version tokens, enforcing TLS 1.2/1.3 with modern cipher suites, implementing strict HTTP security headers, rate-limiting requests, and running the service as a non-root user.

The trimstray/the-book-of-secret-knowledge repository curates battle-tested resources for system administrators, including the authoritative NGINX Admin's Handbook which provides definitive guidance on how to harden Nginx web server installations against modern attack vectors. This guide synthesizes those recommendations with current CIS Benchmarks and Mozilla SSL standards to provide actionable, copy-paste security configurations that reduce your attack surface.

Reduce Information Exposure

Default Nginx installations leak version numbers and module details that aid reconnaissance attacks. According to the trimstray/the-book-of-secret-knowledge security references, the first step in any hardening regimen is eliminating this information disclosure.

Disable the server_tokens directive to prevent Nginx from appending version numbers to error pages and response headers:

server_tokens off;

For Nginx Plus builds or installations with the Headers More module, you can further sanitize responses using more_clear_headers to strip identifying headers entirely.

Enforce Modern TLS and SSL Configuration

Transport Layer Security (TLS) configuration is critical to prevent man-in-the-middle attacks and protocol downgrade attempts. The NGINX Admin's Handbook recommends explicit protocol and cipher controls rather than relying on defaults.

Explicitly allow only TLS 1.2 and TLS 1.3, disabling legacy SSL and early TLS versions vulnerable to POODLE and BEAST attacks:

ssl_protocols TLSv1.2 TLSv1.3;

Configure a modern cipher suite prioritizing Elliptic Curve Diffie-Hellman Ephemeral (ECDHE) key exchange and AES-GCM authenticated encryption:

ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;

Enable OCSP stapling to improve revocation checking performance and privacy:

ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;

Implement HTTP Strict Transport Security (HSTS) to force HTTPS connections and prevent SSL stripping attacks:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

Implement Security Headers

HTTP security headers provide browser-level protections against XSS, clickjacking, and MIME-sniffing attacks. As documented in the hardening resources, these headers should be set globally in the http block to apply to all virtual hosts.

Deploy a Content Security Policy (CSP) to mitigate cross-site scripting and data injection:

add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';";

Add protective headers to prevent clickjacking and content type sniffing:

add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy no-referrer-when-downgrade;

Limit Request Size and Implement Rate Limiting

Unrestricted request handling enables Denial-of-Service (DoS) attacks and brute-force attempts. The hardening guide recommends strict resource limits.

Cap upload sizes to prevent memory exhaustion:

client_max_body_size 1M;

Define rate limiting zones to throttle abusive clients. This configuration creates a 10MB zone tracking binary remote addresses, allowing 10 requests per second with burst capacity:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

server {
    limit_req zone=mylimit burst=20 nodelay;
}

Restrict Access to Sensitive Locations

File system exposure and unnecessary HTTP methods create privilege escalation risks. Restrict access to hidden files (dotfiles) and administrative endpoints.

Block access to hidden files and version control directories while preserving .well-known for ACME challenges:

location ~ /\.(?!well-known) {
    deny all;
}

Disable unsafe HTTP methods, allowing only GET, HEAD, and POST as required:

if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 405;
}

Run Nginx as an Unprivileged User

Executing Nginx as root creates severe privilege escalation risks if a worker process is compromised. The source code analysis emphasizes using a dedicated non-root user.

Specify an unprivileged service account in the main configuration:

user nginx;

Ensure that configuration files in /etc/nginx/ are owned by root and readable only by the service user (mode 640), preventing unauthorized modifications.

Enable Comprehensive Logging and Monitoring

Security forensics require detailed transaction logs and real-time monitoring capabilities. The trimstray repository references ngxtop as an essential monitoring utility for Nginx environments.

Configure detailed access and error logging with appropriate severity levels:

access_log /var/log/nginx/access.log main;
error_log  /var/log/nginx/error.log warn;

Integrate ngxtop (available at lebinh/ngxtop on GitHub) for real-time log analysis, request rate monitoring, and status code distribution tracking. Export logs to a SIEM for centralized correlation and alerting.

Apply OS-Level Hardening

Nginx security extends beyond application configuration to the underlying operating system. Complement your hardening with system-level controls.

  • Maintain updated binaries: Use the latest stable Nginx release to receive security patches
  • Configure host-based firewalls: Restrict inbound traffic to ports 80 and 443 using ufw or iptables
  • Enable Mandatory Access Control: Apply SELinux or AppArmor profiles to confine Nginx processes and limit file system access

Production-Ready Configuration Example

The following consolidated nginx.conf implements the critical hardening measures discussed above. Adapt paths, domain names, and CSP directives to match your specific environment:

user nginx;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
    multi_accept on;
}

http {
    # Security headers

    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy no-referrer-when-downgrade;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'";

    # TLS hardening

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers on;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # Information disclosure

    server_tokens off;

    # Request limits

    client_max_body_size 1M;
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

    # Logging

    access_log /var/log/nginx/access.log combined;
    error_log  /var/log/nginx/error.log warn;

    server {
        listen 80 default_server;
        listen 443 ssl http2 default_server;
        server_name _;

        # HTTP to HTTPS redirect

        if ($scheme = http) {
            return 301 https://$host$request_uri;
        }

        # Rate limiting

        limit_req zone=mylimit burst=20 nodelay;

        # Block hidden files

        location ~ /\.(?!well-known) {
            deny all;
        }

        root /usr/share/nginx/html;
        index index.html index.htm;
    }
}

Summary

  • Disable information leakage by setting server_tokens off and hiding version headers to prevent reconnaissance attacks
  • Enforce TLS 1.2/1.3 only with strong cipher suites (ECDHE + AES-GCM), OCSP stapling, and HSTS headers to secure data in transit
  • Deploy security headers including CSP, X-Frame-Options, and X-Content-Type-Options to protect against XSS and clickjacking
  • Implement rate limiting using limit_req_zone and restrict request sizes with client_max_body_size to mitigate DoS attempts
  • Run as non-root using the user nginx directive and strict file permissions (640) on configuration files
  • Monitor actively with ngxtop and detailed access logs for security forensics and real-time threat detection

Frequently Asked Questions

How do I verify my Nginx hardening configuration is working?

Test your configuration using online scanners like the Qualys SSL Labs Server Test and security headers inspection tools. Run nginx -t to validate syntax before reloading, then inspect response headers using curl -I https://yourdomain.com to confirm server_tokens is off and security headers are present. For TLS verification, use openssl s_client -connect yourdomain.com:443 -tls1_1 to confirm rejected connections on older protocols.

What is the difference between server_tokens off and using the Headers More module?

server_tokens off is a core Nginx directive that removes the version number from the Server header and error pages, but leaves the header itself showing "nginx". The Headers More module (via more_clear_headers Server) can completely remove the Server header or modify any response header, providing more comprehensive information hiding but requiring third-party module compilation or Nginx Plus.

Should I use Mozilla's Modern or Intermediate SSL configuration for Nginx?

Use the Modern configuration only if you can drop support for older clients (IE11 on Windows 7/8, older Android devices). The Intermediate configuration provides broader compatibility while maintaining strong security. According to the NGINX Admin's Handbook, production environments typically use Intermediate with explicit cipher lists like 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256' to balance security and accessibility.

How does rate limiting in Nginx prevent brute-force attacks?

The limit_req_zone directive creates a shared memory zone tracking request rates per IP address ($binary_remote_addr). When a client exceeds the defined rate (e.g., 10 requests per second), Nginx delays or rejects subsequent requests with a 503 error. This prevents automated tools from rapidly attempting authentication or exploiting endpoints, effectively neutralizing credential stuffing and basic DoS attacks without blocking legitimate traffic entirely.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →