Service Security Deep Dive#


Nginx, as one of the most widely used web servers globally, serves as the traffic entry point for countless websites and applications. However, default configurations often harbor security risks. This article systematically explains various dimensions of Nginx security hardening based on real production environment experience, helping readers build a secure and reliable web service.

⚠️ Disclaimer: All configuration examples in this article are general templates. Please adjust according to your actual environment; do not copy them directly.


I. Basic Security Configuration#

1.1 Run as a Non-Privileged User#

The Nginx master process starts as root, but worker processes should use a normal user:

# Use dedicated user to run worker processes
user nginx nginx;

# Or use www-data
user www-data www-data;

Even if a worker process is compromised, the attacker only has normal user privileges and cannot control the system core.

1.2 Hide Version Information#

Exposing the specific version number gives attackers a basis for targeted attacks:

# Disable version display
server_tokens off;

# You can also modify the version identifier at compile time
# ./configure --with-cc-opt='-DNGX_HTTP_SERVER_STRING="Server: Custom"'

1.3 Limit Process Open File Count#

# Maximum open files per worker process
worker_rlimit_nofile 65535;

II. Access Control and Request Restrictions#

2.1 Restrict Request Methods and Size#

# Only allow common HTTP methods
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$) {
    return 405;
}

# Limit request body size (prevent large file upload attacks)
client_max_body_size 10M;

# Limit request header buffers
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;

2.2 Rate Limiting#

Core configuration for preventing brute force and CC attacks:

# Define rate limiting zone (in http block)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

# Apply rate limiting (in server or location block)
location /api/ {
    limit_req zone=api_limit burst=20 nodelay;
    limit_conn conn_limit 10;
    proxy_pass http://backend;
}

# Stricter limits for login endpoints
location /login {
    limit_req zone=api_limit burst=5 nodelay;
    # Additional delay validation
    auth_request /auth;
}

Parameter Description:

ParameterDescription
rate=10r/sAllow 10 requests per second
burst=20Allow burst of 20 requests to queue
nodelayProcess burst requests immediately, otherwise they will be delayed

2.3 Connection Limits#

# Limit concurrent connections per IP
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn perip 10;

# Limit total connections per virtual server
limit_conn_zone $server_name zone=perserver:10m;
limit_conn perserver 1000;

III. Sensitive Path and File Protection#

3.1 Block Access to Hidden Files#

# Block all files/directories starting with dot (.git, .env, .htaccess, etc.)
location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

3.2 Protect Sensitive File Types#

# Block access to backup, config, log, and other files
location ~* \.(bak|config|sql|log|sh|inc|swp|old|save|orig|tmp)$ {
    deny all;
    return 404;
}

# Protect system files
location ~* (composer\.json|composer\.lock|package\.json|package-lock\.json|Dockerfile|\.env) {
    deny all;
    return 404;
}

3.3 Restrict Directory Listing#

# Disable automatic directory listing
autoindex off;

# Can be enabled for specific directories (e.g., file download sites)
location /downloads/ {
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;
}

3.4 Prevent Image Hotlinking#

location ~* \.(jpg|jpeg|png|gif|webp)$ {
    valid_referers none blocked *.example.com example.com;
    if ($invalid_referer) {
        return 403;
        # Or return a hotlink protection image
        # rewrite ^/.*$ /images/hotlink.jpg break;
    }
}

IV. HTTPS and SSL/TLS#

4.1 Force HTTPS Redirect#

# HTTP forced redirect to HTTPS
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

4.2 Modern SSL/TLS Configuration#

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;

    # Certificate configuration
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/private.key;

    # Only enable TLS 1.2 and 1.3
    ssl_protocols TLSv1.2 TLSv1.3;

    # Strong cipher suites
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';

    # Prefer server cipher order
    ssl_prefer_server_ciphers on;

    # Session cache
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling (accelerate certificate validation)
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /path/to/chain.pem;

    # DNS resolver (for OCSP)
    resolver 8.8.8.8 1.1.1.1 valid=300s;
    resolver_timeout 5s;
}

4.3 HSTS Configuration (Optional)#

# Strict Transport Security (enable in phases)
# Phase 1: Test period (5 minutes)
add_header Strict-Transport-Security "max-age=300" always;

# Phase 2: Short term (1 day)
add_header Strict-Transport-Security "max-age=86400" always;

# Phase 3: Production (1 year, including subdomains)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

⚠️ Important: Before enabling preload, ensure:

  • All subdomains support HTTPS
  • Certificates are valid long-term and uninterrupted
  • You will never fall back to HTTP

V. Security Response Headers#

This is an often overlooked but extremely important security layer:

server {
    # Prevent clickjacking
    add_header X-Frame-Options "SAMEORIGIN" always;

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

    # Prevent MIME type sniffing
    add_header X-Content-Type-Options "nosniff" always;

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

    # Permissions policy (control browser features)
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Content Security Policy (customize based on actual needs)
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:" always;

    # Cross-Origin Resource Policy
    add_header Cross-Origin-Resource-Policy "same-origin" always;

    # Cross-Origin Embedder Policy
    add_header Cross-Origin-Embedder-Policy "require-corp" always;

    # Cross-Origin Opener Policy
    add_header Cross-Origin-Opener-Policy "same-origin" always;
}

VI. Logging and Monitoring#

6.1 Proper Log Configuration#

# Custom log format (includes more security-related information)
log_format security '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_response_time '
                    '$ssl_protocol $ssl_cipher '
                    '"$http_x_forwarded_for"';

# Access log
access_log /var/log/nginx/access.log security buffer=32k flush=5s;

# Error log level: warn (log warnings and above)
error_log /var/log/nginx/error.log warn;

6.2 Anomalous Request Monitoring#

# Log 404 requests (to detect scanning behavior)
location / {
    # ... normal configuration
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}

# Separately log attack attempts
map $status $log_4xx {
    ~^4  1;
    default 0;
}

access_log /var/log/nginx/attack.log combined if=$log_4xx;

6.3 Log Analysis#

#!/bin/bash
# Analyze scanning behavior

# Find suspicious 404 scans
echo "=== Suspicious Scan IP TOP10 ==="
grep " 404 " /var/log/nginx/access.log |     awk '{print $1}' | sort | uniq -c | sort -rn | head -10

# Find brute force attempts
echo "=== Login Endpoint Requests TOP10 ==="
grep "POST /login" /var/log/nginx/access.log |     awk '{print $1}' | sort | uniq -c | sort -rn | head -10

# Find SQL injection attempts
echo "=== SQL Injection Pattern Requests ==="
grep -i "select\|union\|insert\|drop\|--\|#" /var/log/nginx/access.log

VII. Defending Common Attacks#

7.1 SQL Injection Prevention (WAF-level Rules)#

# Basic SQL injection filter rules
set $block_sql_inject 0;
if ($query_string ~* "(\bselect\b.*\bfrom\b|\bunion\b.*\bselect\b|information_schema|benchmark\()") {
    set $block_sql_inject 1;
}
if ($block_sql_inject = 1) {
    return 403;
}

7.2 Path Traversal Prevention#

# Block directory traversal
location ~* \.(\.|%2e|%2E){2}(/|%2f|%2F) {
    deny all;
    return 404;
}

# Normalize URI
location ~* (\.\./|\.\.\\) {
    deny all;
}

7.3 User-Agent Scanning Prevention#

# Block common scanning tools
if ($http_user_agent ~* (nikto|sqlmap|nessus|nmap|acunetix|wpscan|dirbuster|gobuster)) {
    return 444;  # Nginx special status code, directly close connection
}

7.4 Slow Attack Protection#

# Set client request timeouts
client_body_timeout 10s;   # Request body read timeout
client_header_timeout 10s; # Request header read timeout
send_timeout 10s;          # Response send timeout

# Limit request body size (prevent large packet attacks)
client_max_body_size 1M;

VIII. Proxy Security#

Nginx Reverse Proxy#

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

    # Pass real client IP
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Hide backend server information
    proxy_hide_header X-Powered-By;
    proxy_hide_header Server;

    # Limit proxy timeouts (prevent slow connections)
    proxy_connect_timeout 5s;
    proxy_send_timeout 10s;
    proxy_read_timeout 10s;

    # Buffer responses (prevent memory overflow)
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
}

IX. Monitoring and Alerting#

9.1 Key Metrics#

#!/bin/bash
# Nginx security monitoring script

# Monitor abnormal status code ratio
TOTAL=$(tail -10000 /var/log/nginx/access.log | wc -l)
ERRORS=$(tail -10000 /var/log/nginx/access.log | grep -E " 40[0-9]| 50[0-9]" | wc -l)
ERROR_RATE=$((ERRORS * 100 / TOTAL))

if [ $ERROR_RATE -gt 20 ]; then
    echo "Warning: Error rate too high ${ERROR_RATE}%"
    # Send alert
fi

# Monitor request frequency per IP
tail -10000 /var/log/nginx/access.log |     awk '{print $1}' | sort | uniq -c | sort -rn |     awk '$1 > 200 {print "Suspicious IP: "$2" Requests: "$1}'

9.2 Automated Blocking#

#!/bin/bash
# Automatically block attacking IPs

# Block IPs with more than 100 requests in 5 minutes
tail -5000 /var/log/nginx/access.log |     awk '{print $1}' | sort | uniq -c |     awk '$1 > 100 {print $2}' |     while read IP; do
        iptables -I INPUT -s $IP -j DROP
        echo "$(date): Banned $IP" >> /var/log/nginx/ban.log
    done

Summary#

Nginx security hardening is a continuous process, not a one-time configuration. As attack techniques evolve and business requirements change, security policies need constant adjustment and optimization.

Core Principles#

PrincipleDescription
Least privilegeOnly expose necessary ports and features
Defense in depthDon’t rely on a single security measure
Continuous monitoringLogs are the best security sensors
Regular assessmentRegularly review and update security configurations

🛡️ Remember: There is no absolute security, only relative security. The key is to establish a security mechanism of timely detection, rapid response, and continuous improvement.

你又来啦!好开心 🐟