nginx-configNGINX 1.30.5

NGINX Open Source research baseline

Research checked: 2026-09-20, updated 2026-10-01 (digest, image modules, OCSP, ACME). This note covers the free, open-source NGINX server. NGINX Plus-only features are out of scope.

Current supported target

Sources:

Security release floor

The latest stable patch is a security requirement, not a cosmetic update. NGINX 1.30.5 / 1.31.6 fixes CVE-2026-90439, a buffer overflow in the experimental HTTP/3 module. Earlier 1.30 patch releases also fixed issues in regular expressions used by map, slice, SSI, gRPC/proxy v2, charset, rewrite, HTTP/2 proxying, SCGI/uWSGI, HTTP/3, and OCSP resolver handling. The current 1.30.5 stable release contains all of those core fixes.

The practical rules are:

  1. Check the official security advisory page whenever the pinned version changes.
  2. Rebuild deployed images after a fixed NGINX package and fixed base image libraries are available. NGINX also depends on OpenSSL, zlib, PCRE, and the operating system.
  3. Load only modules the deployment uses. Dynamic modules, njs, and third-party modules have their own update lifecycles. In particular, njs advisories are not fixed by changing only an NGINX core version.
  4. Record nginx -V in diagnostics. It proves the running version, linked TLS library, and compile-time modules; a config file alone does not.

Source: NGINX security advisories and the dated release news.

Baseline that is safe for most deployments

These settings have a clear effect and do not depend on guessed traffic values.

worker_processes auto;

events {
    # Includes client and upstream connections. The operating-system file
    # descriptor limit must be at least this large for each worker. This is an
    # illustrative capacity; calculate the production value from peak load.
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    server_tokens off;

    # Efficient for normal files served from local filesystems.
    sendfile   on;
    tcp_nopush on;  # Has an effect only with sendfile.

    # Keep the built-in tcp_nodelay on, keepalive_requests 1000,
    # keepalive_time 1h, and sendfile_max_chunk 2m defaults.

    # Illustrative limit: choose the real value from application requirements.
    # Never use 0 (unlimited) as a generic default.
    client_max_body_size 10m;

    # A shared TLS session cache reduces repeat-handshake CPU work.
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;
}

worker_processes auto starts with the available CPU count. It is a starting point, not proof that more workers improve an I/O-heavy or CPU-limited workload. worker_connections includes connections to proxied/FastCGI servers, so a reverse proxy may consume roughly two connections per active request. Its real ceiling is also bounded by the worker’s open-file limit.

Let NGINX select the event method. Explicit use epoll; makes a config less portable and normally adds no benefit because NGINX already selects the most efficient available method. Leave accept_mutex off on modern Linux and BSD; the official docs say it is unnecessary with EPOLLEXCLUSIVE or reuseport. Leave multi_accept off unless a burst-heavy workload demonstrates a gain.

tcp_nodelay is already on. Do not repeat it as if that were a modern tuning change. sendfile_max_chunk is already 2 MiB, which prevents one fast file transfer from monopolizing a worker. Do not restore the old unlimited behavior.

Sources: core and event directives and HTTP core directives.

File descriptor sizing

Do not paste worker_rlimit_nofile 65535 without matching the service/container limit. First measure peak client plus upstream connections and open cached files. Then make these values agree:

A useful upper bound for client connections is not simply worker_processes * worker_connections when the same worker also opens upstream and file descriptors. stub_status exposes accepted/handled counts; a growing gap can reveal resource limits. Keep that endpoint on a loopback or protected management listener.

Source: worker connection semantics and free stub_status module.

TLS and HTTP protocols

TLS baseline

listen 443 ssl;
http2 on;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

Sources: SSL module, HTTPS configuration, and RFC 6797 for HSTS.

HTTP/2

Use the current syntax:

listen 443 ssl;
http2 on;

listen 443 ssl http2; is deprecated. The http2 directive was added in 1.25.1 and requires ngx_http_v2_module; TLS HTTP/2 also requires ALPN. Keep the default http2_max_concurrent_streams 128 unless tests show a reason to change it. Old directives such as http2_idle_timeout, http2_max_requests, http2_max_field_size, and http2_max_header_size are obsolete; their general HTTP equivalents now cover HTTP/1, HTTP/2, and HTTP/3.

Source: HTTP/2 module.

HTTP/3

HTTP/3 is available but still marked experimental by NGINX. Keep it out of the default configuration and expose it as an advanced opt-in.

Prerequisites and constraints:

The current stable patch floor matters especially here: CVE-2026-90439 affects NGINX 1.29.2 through 1.31.5, including stable 1.30.0 through 1.30.4. It is fixed in 1.30.5 and 1.31.6.

Sources: HTTP/3 module and QUIC build/configuration guide.

Static files and browser caching

A good static baseline is:

location /assets/ {
    try_files $uri =404;

    # Use this long lifetime only for content-hashed filenames.
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Source: headers and expiry module HTTP core ETag behavior, RFC 8246 for immutable, and RFC 9111 cache semantics.

Open-file cache

open_file_cache is off by default. It can reduce repeated filesystem lookups for a large, hot static tree:

open_file_cache          max=1000 inactive=20s;
open_file_cache_valid    30s;
open_file_cache_min_uses 2;
# Keep missing-file errors uncached when deployments add files in place.
open_file_cache_errors   off;

This is an opt-in setting. max consumes file descriptors and memory. Cached metadata can delay visibility of file replacement, permission, or existence changes until revalidation. Size it from the observed hot file set and deployment method, and count the descriptors in the process limit.

Source: open file cache directives.

Compression

Dynamic gzip is useful for text but trades CPU for bytes. A conservative option:

gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_comp_level 1;
gzip_types
    text/css
    text/javascript
    application/javascript
    application/json
    application/xml
    image/svg+xml;

Sources: gzip filter and precompressed gzip files.

Large-file I/O

Use ordinary sendfile on first. aio, aio threads, directio, and custom output buffers are specialized options for large files or slow storage:

Source: AIO, direct I/O, and sendfile documentation.

PHP / FastCGI

The correctness and security baseline matters more than guessed buffer sizes:

location ~ \.php$ {
    # Never send a nonexistent script path to PHP-FPM.
    try_files $uri =404;

    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

Sources: FastCGI module, upstream keepalive, and official try_files FastCGI example.

Request hardening and safe operational defaults

Use limits that match application behavior; global copied values can block real users or fail to stop expensive endpoints.

Sources: HTTP core limits, real IP module, request rate limiting, and concurrent request limiting.

Logging and observability

Do not turn off access logs merely to increase a synthetic throughput number. They are needed to validate performance and investigate incidents. A buffered file log reduces write frequency:

log_format timed '$remote_addr [$time_iso8601] "$request" $status '
                 '$body_bytes_sent rt=$request_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log timed buffer=32k flush=1s;

Tradeoffs: a crash can lose data still in the buffer, and variable log paths do not support buffered writes. Include request time, upstream time/status, bytes, protocol, and a safe correlation ID. Never log authorization, cookies, query secrets, or request bodies by default. Keep error_log at warn or error in normal production; debug logging is high-volume and may expose sensitive data.

Source: HTTP log module.

Settings that need evidence before enabling

SettingWhy it is not a universal optimizationEvidence needed
worker_cpu_affinityContainers/cgroups, NUMA, and schedulers change the useful mapping.CPU saturation and repeatable benchmark improvement.
reuseportCreates a listener per worker and has documented security implications if misused.Accept-queue imbalance or scaling evidence.
multi_accept onA worker accepts all waiting connections at once; this can change fairness.Burst workload comparison with errors and tail latency.
Very large worker_connectionsConsumes file descriptors and connection memory; upstreams count too.Peak concurrency, memory, and matching OS limits.
Short global timeoutsCan reject slow mobile clients and valid uploads/streams.Endpoint timing and slow-client tests.
open_file_cacheUses descriptors and serves cached metadata until revalidation.Hot-file count, deploy behavior, lower filesystem lookup cost.
gzip_comp_level 5+More CPU can increase tail latency for small byte savings.Compression ratio, CPU, p95/p99 latency.
gzip_proxied anyMay compress personalized or secret-bearing responses.Data classification and BREACH review.
aio / directio / thread poolsOS, filesystem, alignment, and file size determine the result.Actual large-file storage benchmark.
fastcgi_cacheIncorrect keys/bypass rules can leak one user’s content to another.Cache contract, tests, invalidation, auth/session bypass.
Rate/connection limitsPer-IP values can punish NAT users and HTTP/2 concurrency.Dry-run logs and endpoint capacity.
HTTP/3Still experimental and requires UDP/TLS/module operational support.Compatibility, fallback, loss/latency, security patching.
TLS 0-RTTRequests can be replayed.Application-level replay protection and idempotency proof.

Obsolete or misleading tuning to remove

Verification contract

Every generated/example configuration should pass these checks before it is described as ready:

  1. Build identity: nginx -v is at least 1.30.5 and nginx -V shows the expected SSL library and modules (http_ssl, http_v2, and only optional modules actually selected).
  2. Configuration: nginx -t succeeds inside the same package/container and with the same mounted files used at runtime. nginx -T is inspected for inheritance and duplicate directives.
  3. TLS: TLS 1.2 and 1.3 handshakes succeed; TLS 1.0 and 1.1 fail. The expected certificate chain and SNI host are returned. Unknown SNI/Host does not serve an application.
  4. Protocols: HTTPS negotiates h2 and still serves HTTP/1.1. If HTTP/3 is selected, a real HTTP/3 client succeeds over UDP and TCP fallback still works.
  5. Static caching: hashed assets have the long immutable policy; HTML and service workers do not. Conditional requests return 304 where appropriate.
  6. Compression: eligible text over the size threshold returns Content-Encoding: gzip plus Vary: Accept-Encoding; images/archives and secret-bearing dynamic endpoints do not.
  7. PHP: an existing script executes; a nonexistent .php path returns 404 without reaching PHP-FPM; uploads at and above the declared limit behave as documented; a streaming route is tested if buffering is disabled.
  8. Limits and identity: forwarded addresses are accepted only from trusted proxies. Rate limits run in dry-run first and logs show expected keys.
  9. Reload: configuration reload is graceful. NGINX checks syntax and opens new logs/listeners before replacing workers; failed application leaves the old configuration running.
  10. Performance: compare before/after on the real content mix and full TLS path. Record throughput, error rate, p50/p95/p99 latency, CPU, memory, open descriptors, network bytes, and disk I/O. Warm and cold cache results must be separated. Keep a tuning change only when the relevant metric improves without unacceptable regression or errors.

Sources: command-line checks and graceful configuration reload behavior.

Implementation decisions for this repository