Web Security: Headers, Rate Limiting, and Firewalling
Securing a web server goes far beyond TLS. This guide covers the HTTP security headers every site should send, rate limiting to prevent abuse, web application firewalls, and access control at the server level.
Security Headers
Modern browsers enforce several security policies via HTTP response headers. Add them in your Nginx server block or Apache VirtualHost.
Nginx
# Content Security Policy -- restrict resource loading origins
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none';" always;
# Prevent clickjacking
add_header X-Frame-Options "DENY" always;
# Stop MIME-type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Control referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Restrict browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
# HSTS (see SSL/TLS guide for details)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none';"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
Header Explanation
| Header | Purpose |
|---|---|
Content-Security-Policy |
Controls which resources the browser may load; mitigates XSS |
X-Frame-Options |
Prevents your site from being embedded in iframes (clickjacking) |
X-Content-Type-Options |
Stops browsers from guessing MIME types |
Referrer-Policy |
Controls how much referrer info is sent with requests |
Permissions-Policy |
Restricts access to browser APIs (camera, mic, etc.) |
Rate Limiting
Protect login endpoints, APIs, and form submissions from brute-force attacks.
Nginx limit_req
# Define a rate limit zone (10 requests/second per IP)
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
}
server {
# Apply to sensitive endpoints
location /api/login {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://app_backend;
}
}
zone=api_limit:10m-- allocates 10 MB of shared memory (~160,000 IPs).rate=10r/s-- allows 10 requests per second per IP.burst=20-- allows short bursts up to 20 requests before throttling.nodelay-- serves burst requests immediately rather than queuing them.
Nginx limit_conn
Limit simultaneous connections from a single IP:
http {
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
server {
location /downloads/ {
limit_conn conn_limit 5;
limit_conn_status 429;
}
}
ModSecurity and OWASP Core Rule Set
ModSecurity is a web application firewall (WAF) that inspects requests for attack patterns. The OWASP Core Rule Set (CRS) provides pre-built rules for SQL injection, XSS, file inclusion, and more.
Install for Nginx
sudo apt install libmodsecurity3 libnginx-mod-http-modsecurity
Install for Apache
sudo apt install libapache2-mod-security2
sudo a2enmod security2
Enable OWASP CRS
# Download the Core Rule Set
git clone https://github.com/coreruleset/coreruleset.git /etc/modsecurity/crs
cp /etc/modsecurity/crs/crs-setup.conf.example /etc/modsecurity/crs/crs-setup.conf
Configure ModSecurity to load the rules:
# /etc/modsecurity/modsecurity.conf
SecRuleEngine On
Include /etc/modsecurity/crs/crs-setup.conf
Include /etc/modsecurity/crs/rules/*.conf
Start in DetectionOnly mode first to identify false positives before
switching to On (blocking mode).
fail2ban for Web Servers
fail2ban monitors log files and bans IPs that show malicious patterns.
Nginx
# /etc/fail2ban/jail.d/nginx.conf
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
Apache
# /etc/fail2ban/jail.d/apache.conf
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 5
bantime = 3600
# Restart and check status
sudo systemctl restart fail2ban
sudo fail2ban-client status nginx-http-auth
IP-Based Access Control
Restrict access to admin areas by IP address.
Nginx
location /admin/ {
allow 10.0.0.0/8;
allow 192.168.1.100;
deny all;
proxy_pass http://app_backend;
}
Apache
<Location /admin>
Require ip 10.0.0.0/8
Require ip 192.168.1.100
</Location>
Return to the Web Servers hub or continue to SSL/TLS Setup and Performance Tuning.