Nginx Security Guide

Nginx security best practices
for 2026

Nginx powers over 34% of the world's websites — it is the default web server on DigitalOcean Droplets and most cloud VPS. But its default configuration is not secure. No security headers, version exposed, no rate limiting. This guide shows you exactly what to change.

Why nginx needs hardening

Out of the box, nginx is fast and reliable, but it is not configured for security. The default install exposes the server version in HTTP headers, sends no security headers, has no rate limiting, and serves directory listings if an index file is missing. Attackers use automated scanners to detect these defaults across millions of servers.

34%
of all websites use nginx
Source: W3Techs, April 2026
80%
use default config in production
Estimated from Shodan scan data
5 min
to apply all hardening steps
Following this guide

A misconfigured nginx server tells attackers exactly what version you run, which CVEs apply, and that you probably have not hardened anything else either. For WAF-level protection without compiling modules, see our comparison of Defensia vs ModSecurity and Cloudflare WAF. Every section below addresses a specific risk with a concrete configuration change.

Hide nginx version

By default, nginx sends a Server: nginx/1.24.0 header with every response. This tells attackers your exact version, which they can match against published CVE databases. Hiding the version is the simplest hardening step.

nginx.conf — http block

server_tokens off;

After this change, the Server header will show nginx without a version number. Error pages will also stop showing the version. Reload nginx with nginx -t && systemctl reload nginx.

Security headers

Security headers instruct the browser to enforce protections against clickjacking, MIME-type sniffing, cross-site scripting, and protocol downgrade attacks. Nginx sends none of these by default. Add them to your server block or http block.

nginx.conf — server or http block

# Prevent clickjacking — only allow framing from same origin

add_header X-Frame-Options "SAMEORIGIN" always;

# Prevent MIME-type sniffing

add_header X-Content-Type-Options "nosniff" always;

# Enable XSS protection in older browsers

add_header X-XSS-Protection "1; mode=block" always;

# Force HTTPS for 1 year including subdomains

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

# Restrict resource loading to same origin

add_header Content-Security-Policy "default-src 'self'" always;

# Disable browser features you don't use

add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Control referrer information sent to other sites

add_header Referrer-Policy "strict-origin-when-cross-origin" always;

X-Frame-Options

Prevents your page from being loaded in an iframe on another domain. Blocks clickjacking attacks where an attacker overlays an invisible frame to steal clicks.

X-Content-Type-Options

Stops browsers from MIME-type sniffing. Without this, a browser might execute a malicious file disguised as a harmless MIME type.

Strict-Transport-Security

Forces browsers to use HTTPS for all future requests. The max-age=31536000 directive keeps this active for one year. includeSubDomains extends it to all subdomains.

Content-Security-Policy

Controls which resources (scripts, styles, images) the browser is allowed to load. default-src 'self' only allows resources from your own domain. Customize for your needs.

Permissions-Policy

Disables browser APIs you do not use. Prevents malicious scripts from accessing the camera, microphone, or geolocation even if injected via XSS.

Referrer-Policy

Controls how much URL information is sent to other sites. strict-origin-when-cross-origin sends only the origin (not the full path) when navigating to another domain.

Rate limiting

Without rate limiting, a single IP can send thousands of requests per second to your server, enabling brute force attacks on login pages, credential stuffing, and denial-of-service. Nginx has a built-in rate limiting module (ngx_http_limit_req_module) that requires just two directives.

nginx.conf — rate limiting

# Define a rate limit zone (10MB shared memory, 10 requests/second per IP)

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

# Apply to a location — allow bursts of 20, don't delay

location /login {

limit_req zone=one burst=20 nodelay;

}

How it works: $binary_remote_addr uses 4 bytes per IP (vs. 15+ bytes for the string), so 10MB stores approximately 160,000 IP states. The burst=20 parameter allows 20 requests above the rate before returning 503. The nodelay flag processes burst requests immediately instead of queuing them. Apply rate limiting to login pages, API endpoints, and any form submission handlers.

SSL/TLS configuration

A properly configured TLS setup prevents protocol downgrade attacks, cipher suite weaknesses, and man-in-the-middle interception. Here is a production-ready configuration for 2026.

nginx.conf — TLS configuration

# Only allow TLS 1.2 and 1.3 — disable SSLv3, TLS 1.0, TLS 1.1

ssl_protocols TLSv1.2 TLSv1.3;

# Use server's cipher preference, not client's

ssl_prefer_server_ciphers on;

# Strong cipher suite (ECDHE for forward secrecy)

ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

# Enable OCSP stapling for faster certificate validation

ssl_stapling on;

ssl_stapling_verify on;

resolver 1.1.1.1 8.8.8.8 valid=300s;

# Disable session tickets (prevents forward secrecy bypass)

ssl_session_tickets off;

# Session cache for performance

ssl_session_cache shared:SSL:10m;

ssl_session_timeout 1d;

Why disable TLS 1.0 and 1.1? Both have known vulnerabilities (BEAST, POODLE, CRIME). All modern browsers support TLS 1.2+. PCI DSS 3.2+ requires TLS 1.2 minimum. Why disable session tickets? Session tickets reuse the same key for multiple connections, which breaks forward secrecy if the key is compromised. With ssl_session_cache, you get resumption performance without the security trade-off.

Block bad bots and scanners

Every public web server receives thousands of requests for paths that do not exist: /.env, /.git/config, /wp-login.php (on non-WordPress servers), and /phpmyadmin. These are automated scanners looking for exposed credentials and known vulnerabilities. Block them explicitly to reduce noise and attack surface.

nginx.conf — block scanner paths

# Block access to hidden files and directories

location ~ /\. {

deny all;

return 404;

}

# Block WordPress probing on non-WP servers

location ~* ^/(wp-login|wp-admin|xmlrpc)\.php {

return 444;

}

# Block common scanner targets

location ~* ^/(phpmyadmin|myadmin|pma|dbadmin) {

return 444;

}

# Block by user-agent (known bad bots)

if ($http_user_agent ~* (Scanbot|Nikto|sqlmap|w3af|Nessus)) {

return 444;

}

Why return 444? This is an nginx-specific response that closes the connection without sending any data. It is faster than 403 or 404, gives the scanner no information, and does not generate an error page. Use 404 for hidden files so legitimate tools (like Let's Encrypt's .well-known) can be excluded with a preceding location block.

WAF for nginx: ModSecurity vs Defensia

A Web Application Firewall (WAF) inspects HTTP requests for attack patterns like SQL injection, cross-site scripting, and path traversal. Nginx does not include a WAF by default. There are two main approaches.

FeatureModSecurity + CRSDefensia WAF
InstallationCompile as nginx modulecurl | bash (60 seconds)
ConfigurationOWASP CRS rules (~200 files)Zero config
Nginx changes requiredYes (load_module, modsecurity on)None
Performance impactInline inspection (adds latency)Reads logs async (zero overhead)
Attack types detected15+ (OWASP CRS)15+ (same OWASP types)
False positive tuningManual rule exclusionsScoring engine (auto-threshold)
Blocking methodInline (rejects HTTP request)iptables (blocks all traffic from IP)
DashboardNone (log files only)Real-time web dashboard
PriceFree (open source)Free (1 server), Pro EUR 9/mo

ModSecurity is a powerful inline WAF, but it requires compiling as an nginx dynamic module, maintaining OWASP Core Rule Set (CRS) updates, and tuning rules to avoid false positives. It adds latency to every request because it inspects traffic inline. Defensia reads your nginx access.log asynchronously, detects the same attack patterns, and blocks offending IPs via iptables. Zero nginx configuration changes, zero performance overhead, and the same result: attackers get blocked. Learn more about Defensia WAF →

Monitor nginx access logs

Your nginx access log is a goldmine of security intelligence. Every attack leaves a trace. Knowing what to look for turns your logs from noise into actionable threat data.

COMMON ATTACK PATTERNS IN ACCESS.LOG

# SQL injection attempt

91.108.4.30 "GET /search?q=1'+OR+1=1-- HTTP/1.1" 200

# Path traversal to read /etc/passwd

103.145.13.90 "GET /../../../../etc/passwd HTTP/1.1" 400

# WordPress xmlrpc brute force

45.83.64.11 "POST /xmlrpc.php HTTP/1.1" 200

# Shell command injection

185.220.101.7 "GET /cgi-bin/;cat+/etc/shadow HTTP/1.1" 404

# Scanner probing for .env files

5.188.210.12 "GET /.env HTTP/1.1" 404

Manually reviewing logs is impractical at scale. Defensia reads nginx access logs automatically, applies 15+ OWASP detection patterns in real time, scores each IP based on attack severity and frequency, and blocks attackers via iptables within seconds. No log rotation scripts, no grep pipelines, no custom parsers. Just install the agent and your logs become an active defense layer.

Frequently asked questions

How do I check if my nginx is secure?

Run a security header scan at securityheaders.com or use curl -I to inspect response headers. Check that Server header does not show a version number, security headers are present (X-Frame-Options, HSTS, CSP), and TLS is configured with modern protocols. For deeper analysis, Defensia monitors your nginx logs and alerts on attack patterns automatically.

Does nginx have a built-in WAF?

No. Nginx does not include a WAF by default. You can add ModSecurity as a dynamic module with the OWASP Core Rule Set, but it requires compiling and maintaining the module. Alternatively, Defensia reads nginx access logs and detects the same OWASP attack types without any nginx configuration changes.

What security headers should nginx have?

At minimum: X-Frame-Options (SAMEORIGIN), X-Content-Type-Options (nosniff), Strict-Transport-Security (max-age=31536000), Content-Security-Policy (restrict resource loading), Permissions-Policy (disable unused browser APIs), and Referrer-Policy. These protect against clickjacking, MIME sniffing, protocol downgrade, and cross-site scripting.

How do I block bots in nginx?

Use user-agent matching to block known scanner tools (Nikto, sqlmap, w3af), deny access to hidden files (location ~ /\.), and return 444 for common probe paths (/wp-login.php, /phpmyadmin, /.env). For comprehensive bot management with 70+ fingerprints and per-policy control, Defensia handles it automatically from your access logs.

Does Defensia require nginx configuration changes?

No. Defensia reads your existing nginx access.log file. It does not modify your nginx configuration, does not sit in the request path, and does not require any module installation. The Go agent runs as a separate systemd service and blocks attackers via iptables/ipset.

Sources

  • Nginx official documentation: nginx.org/en/docs/
  • OWASP Secure Headers Project: owasp.org/www-project-secure-headers/
  • Mozilla SSL Configuration Generator: ssl-config.mozilla.org
  • W3Techs Web Server Survey, April 2026: w3techs.com/technologies/overview/web_server
  • OWASP ModSecurity Core Rule Set: coreruleset.org

Automate nginx security monitoring

Defensia reads your nginx logs and blocks attackers automatically. One command. Under 30 seconds.

$ curl -fsSL https://defensia.cloud/install.sh | sudo bash
Create Free Account

No credit card required. Free for one server.