Your Nginx server is exposed to the internet. Every day it faces scans, brute force attempts, and bots probing for vulnerabilities. Without security headers, a basic WAF, and DDoS protection, you are not defending anything. We see it daily in projects that come to us: a bare Nginx with no security headers, rate limiting disabled, and no rules to filter attacks. You don't need an enterprise firewall: with Nginx and a few config lines, you can block most common threats.
We at Meteora Web have been managing servers for years. We come from accounting and ERP, but security in Italian SMEs is systematically underestimated. We've seen companies lose data from an attack that three headers could have prevented. This guide shows you how to configure Nginx to defend yourself without spending on expensive hardware.
Why Are Nginx Security Headers the First Line of Defense?
HTTP headers are the response sent from the server to the browser. If you don't configure them, the browser accepts any behavior, opening the door to XSS, clickjacking, MIME sniffing, and more. The main security headers you need to set on Nginx are:
Sponsored Protocol
- X-Content-Type-Options: nosniff — prevents the browser from interpreting MIME types differently from declared.
- X-Frame-Options: DENY — blocks site loading in iframes, preventing clickjacking.
- X-XSS-Protection: 1; mode=block — enables the built-in XSS filter (obsolete but still useful).
- Referrer-Policy: strict-origin-when-cross-origin — limits referrer information sent.
- Permissions-Policy — disables unnecessary browser APIs (geolocation, camera, etc.).
- Content-Security-Policy (CSP) — the most powerful: controls which resources can be loaded (scripts, styles, images).
- Strict-Transport-Security (HSTS) — forces HTTPS.
Common mistake: configuring only HSTS and CSP without the others, or copying a CSP template without personalizing it, effectively breaking the site.
Sponsored Protocol
How to Configure Security Headers on Nginx
Add these directives inside the http block of nginx.conf or in a separate included file (security-headers.conf):
# Basic security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HSTS — only if all traffic is HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Permissions-Policy — disable everything except what you need
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Content-Security-Policy — customize for your site
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://analytics.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'" always;
Note: always ensures the header is sent even for error responses (4xx/5xx). Test with nginx -t and reload.
Sponsored Protocol
Verify headers with curl:
curl -I https://yoursite.com
How to Set Up a Basic WAF with Nginx Without Expensive Modules?
A Web Application Firewall (WAF) filters malicious traffic at the application layer. You don't need a thousand-euro appliance: Nginx with ngx_http_rewrite_module and a set of Lua rules (with OpenResty) or the OWASP ModSecurity Core Rule Set provide solid protection.
For a basic WAF without ModSecurity (which requires source compilation), you can use Nginx directives to block common patterns:
Blocking SQL Injection and XSS with Maps
Define a map in the http block:
# Map to block suspicious query strings
map $query_string $block_injection {
default 0;
"~*(union.*select|insert.*into|drop.*table|
Caution: using if in location can be tricky. In Nginx, if is often discouraged for complex logic, but for simple blocks it's acceptable. Alternative: use location ~ with regex on the request URI.
To block scanning of sensitive paths:
location ~* \.(env|git|svn|htaccess|bak|old)$ {
deny all;
return 403;
}
location ~* /(wp-admin|admin|phpmyadmin)/* {
# If you don't need these areas, block them
deny all;
return 403;
}
Real example: we had a client whose .env file was publicly accessible. With this rule we blocked access in 5 minutes.
Limiting HTTP Methods
Block PUT, DELETE, TRACE, CONNECT if you don't use them:
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 405;
}
How to Limit Requests to Mitigate DDoS Attacks on Nginx?
Nginx's rate limiting (module ngx_http_limit_req_module) is your simplest weapon against application-level DDoS and brute force. It limits the number of requests per second per IP or session.
Configuring Rate Limiting
In the http block, define a memory zone:
# Zone to limit requests (10 MB = about 160,000 IPs)
limit_req_zone $binary_remote_addr zone=ddos:10m rate=30r/s;
# Server block
server {
...
location / {
limit_req zone=ddos burst=50 nodelay;
...
}
}
- rate=30r/s: maximum 30 requests per second per IP.
- burst=50: allows an extra burst of 50 requests if there is a short spike.
- nodelay: processes burst requests immediately (increases risk of overflow).
If an IP exceeds the limits, Nginx responds with 503 Service Unavailable. You can customize the response code:
limit_req_status 429; # Too Many Requests
Tip: for login and API (e.g., WooCommerce checkout) use separate zones with lower rates (5-10 r/s).
Limiting Simultaneous Connections
The limit_conn module limits concurrent connections per IP:
limit_conn_zone $binary_remote_addr zone=conn:10m;
server {
location / {
limit_conn conn 10;
}
}
Useful for mitigating slowloris attacks (which keep many connections open slowly).
Protecting UDP/HTTP2 Traffic with Tighter Limits
If you use HTTP/2, clients can multiplex many requests on a single connection. Nginx has specific parameters:
http2_max_concurrent_streams 128; # default 128
http2_recv_timeout 5s;
For HTTP/1.1, lower keepalive timeouts:
keepalive_timeout 15;
keepalive_requests 100;
How to Test and Monitor Security Configurations?
Configuring is not enough: you must verify that headers are actually sent and rate limiting works. Here's how.
Testing with curl and Online Tools
- curl -I to see headers.
- securityheaders.com (Scott Helme) — analyzes security headers.
- Mozilla Observatory — comprehensive web security test.
Simulate a DDoS attack with ab (Apache Bench) or wrk:
ab -n 1000 -c 100 https://yoursite.com/
If rate limiting is active, you should see 429/503 responses after the first requests.
Monitoring with Nginx Logs
Enable logging of limited requests:
log_format blocked '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
server {
access_log /var/log/nginx/access.log;
# Separate log for limited requests
access_log /var/log/nginx/limited.log blocked if=$limit_req_status;
}
Analyze logs with grep for suspicious patterns: grep ' 503 ' /var/log/nginx/access.log.
Frequently Asked Questions about Nginx Security
Should I use ModSecurity or is native Nginx enough?
Native Nginx with headers, rate limiting, and URI blocking is sufficient for most SMEs. If you have compliance requirements (PCI-DSS) or handle sensitive data, ModSecurity with OWASP CRS is recommended. We have integrated it for clients in healthcare and finance.
What if a WAF rule blocks legitimate traffic?
Use detailed logging. In ModSecurity, enable SecRuleEngine DetectionOnly for testing without blocking. For native Nginx, move the block into a specific location or use geo to whitelist trusted IPs.
What to Do Now
- Add security headers to
nginx.confand reload withnginx -s reload. - Configure rate limiting for critical routes (login, API, checkout).
- Block sensitive paths (.env, admin, backup).
- Test with curl and securityheaders.com.
- Monitor logs to identify ongoing attacks.
If you need a custom configuration for your project, contact us. We do this every day. Remember: an Nginx server without protection is like a store with the door open.
For more on Nginx production configuration, check our Pillar Guide on Nginx and Web Server Configuration.