Learning nginx - Security Hardening & Rate Limiting (DDoS & Brute-force Mitigation)
Episode 11 of 21

Learning nginx - Security Hardening & Rate Limiting (DDoS & Brute-force Mitigation)

This episode explains NGINX hardening: hiding the version, IP-based access control, request and connection rate limiting, security headers, and body size limits for attack mitigation.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

An NGINX exposed to the internet is the front door of your application. Without protection, it becomes a target for brute-force attacks, DDoS, and exploits. This Episode 11 covers security hardening and rate limiting — the first line of defense at the NGINX layer.

You'll hide the server version identity, restrict access based on IP, apply rate limiting for requests and connections, install security headers, and limit upload body sizes to defeat large-payload attacks. This is exactly what DevOps teams do before going to production.

Hiding the NGINX Version Identity

server_tokens off

The NGINX version shown in the Server header is free information for attackers. Hide it with server_tokens:

Hide the server version
http {
    server_tokens off;
}

Once enabled, the Server: nginx header no longer shows the full version. This small piece of information reduces the attack surface that can be exploited automatically.

Access Control Restrictions

IP Whitelisting and Blacklisting

For admin areas or internal APIs, restrict who can enter:

Restrict admin access by IP
location /admin {
    allow 192.168.1.0/24;
    allow 10.0.0.1;
    deny all;
}

The allow and deny directives are evaluated in order: the first two allow lines permit the internal subnet and one specific IP, then deny all; rejects everyone else. For internal dashboards, this pattern is far safer than a password alone.

Implementing Rate Limiting

Request Rate Limit with limit_req

Rate limiting restricts the request speed from a single client. The zone is declared in http, then used in location:

Request rate limiting
http {
    limit_req_zone $binary_remote_addr zone=api_zone:10m rate=10r/s;
 
    server {
        location /api/ {
            limit_req zone=api_zone burst=20 nodelay;
            proxy_pass http://backend_app;
        }
    }
}

limit_req_zone creates an api_zone with a 10 megabyte capacity and a rate of 10 requests per second per IP. The limit_req zone=api_zone burst=20 nodelay; directive allows a burst of 20 requests at once, with the rest immediately rejected with status 503. This is the main bulwark against brute-force logins and API spam.

Connection Rate Limit with limit_conn

In addition to requests, also limit the number of parallel connections per IP:

Connection rate limiting
http {
    limit_conn_zone $binary_remote_addr zone=conn_zone:10m;
 
    server {
        location / {
            limit_conn conn_zone 10;
            proxy_pass http://backend_app;
        }
    }
}

limit_conn_zone and limit_conn conn_zone 10; restrict a single IP to opening at most 10 parallel connections. This prevents one client from exhausting all worker connections.

Security HTTP Headers and Body Limits

Standard Security Headers

Several headers make browsers block attacks more aggressively:

Complete security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'" always;
add_header Referrer-Policy "no-referrer" always;
  • X-Frame-Options: SAMEORIGIN prevents clickjacking by blocking embeds in outside frames.
  • X-Content-Type-Options: nosniff prevents browsers from guessing MIME types.
  • Content-Security-Policy restricts which content sources may be loaded.

Note: add_header in the server context is not inherited by locations that have their own add_header directive. Put it in an include for consistency — details in episode 14.

Body Size Limits

Giant uploads can exhaust disk and memory. Set a limit:

Body size limit
server {
    client_max_body_size 10M;
}

client_max_body_size 10M; rejects requests with a body larger than 10 megabytes with status 413. If your application allows large uploads, adjust this number — but never leave it unlimited.

Conclusion

Episode 11 built NGINX's first-line defense: you hide the server version, restrict IP-based access, apply request and connection rate limiting, install security headers, and limit upload body sizes.

Key takeaways:

  • server_tokens off; hides the NGINX version from the Server header.
  • allow and deny restrict access based on IP.
  • limit_req_zone and limit_req limit request rate per IP.
  • limit_conn_zone and limit_conn limit parallel connections.
  • Security headers block clickjacking, MIME sniffing, and XSS in browsers.
  • client_max_body_size prevents large-payload attacks.

In the next episode we'll discuss authentication and authorization in NGINX — HTTP Basic Authentication with htpasswd, subrequest authentication using auth_request, and JWT token validation to secure internal endpoints.

Learning nginx - Security Hardening & Rate Limiting (DDoS & Brute-force Mitigation) | Learning nginx