Access control in nginx starts with the simple allow and deny directives, which act as an ACL matched against the client IP. A typical block permits an internal subnet and rejects everyone else: allow 10.0.0.0/8; deny all;. Denied requests receive a 403 Forbidden. For stronger authentication, HTTP Basic Auth uses auth_basic "Restricted"; alongside auth_basic_user_file /etc/nginx/.htpasswd;, with the password file generated by htpasswd -c /etc/nginx/.htpasswd alice. These primitives compose well with location matching, so you can lock down /admin while leaving the rest of the site open.
Rate limiting protects backends from abuse and is implemented as a two-step pattern. First declare a shared memory zone that counts requests keyed by some identifier, usually $binary_remote_addr: limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;. Then apply it inside a location with limit_req zone=one burst=20 nodelay;. burst is the queue size — how many requests may pile up — and nodelay tells nginx to serve queued requests immediately instead of spacing them out at the configured rate. A similar primitive, limit_conn, restricts concurrent connections per key (typically the client IP), which is the right tool for capping WebSocket or download-heavy clients. To protect a login endpoint specifically, combine a per-minute zone with a tight location match: limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; inside location = /login { limit_req zone=login burst=10 nodelay; ... }.
Logging gives you visibility into traffic and behaviour. The default access log format is "combined": $remote_addr - $remote_user [\(time_local] "\)request" $status $body_bytes_sent "\(http_referer" "\)http_user_agent". For more detail you can define a custom log_format that adds timing variables: $request_time measures total request processing time, while $upstream_response_time, $upstream_connect_time, and $upstream_header_time expose where latency is being spent — particularly useful when a backend is the bottleneck. To reduce syscall overhead on busy servers, open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2; keeps file descriptors warm for frequently written log files. The error_log directive, default level error, captures runtime warnings and errors; raise it to info or debug when troubleshooting. Because debug is extremely noisy, pair it with debug_connection 1.2.3.4; to restrict verbose output to a single client IP, which lets you debug one request end-to-end without flooding your disks.