diff --git a/.gitea/workflows/build-publish.yml b/.gitea/workflows/build-publish.yml index b941ab0..4454458 100644 --- a/.gitea/workflows/build-publish.yml +++ b/.gitea/workflows/build-publish.yml @@ -108,9 +108,10 @@ jobs: done # ---- DEBIAN/control -------------------------------------------------- - # Minimum metadata dpkg requires. We don't declare runtime Depends: - # the .deb bundles every shared library nginx needs (see the ldd - # loop above), so the only thing the host must provide is glibc. + # Minimum metadata dpkg requires. The .deb bundles every shared library + # nginx links against (see the ldd loop above), so the only Depends we + # declare is libjemalloc2 — the systemd unit LD_PRELOADs it for the + # nginx workers; without it, the unit would fail to start. sudo mkdir -p "${DEB_DIR}" sudo tee "${DEB_DIR}/control" >/dev/null < Description: Nginx L7 DDoS Protection (The-World-Is-Yours), built by RAWeb CI. EOF diff --git a/README.md b/README.md index 370dbf7..8e8b008 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ nginx -s reload ## Performance -The default config in `static/nginx/nginx.conf` is tuned for shared hosting at 5,000+ vhost scale. Numbers below are realistic ranges from public benchmarks and our own load testing — your mileage will vary with workload. ### vs. vanilla nginx (same version, default config) @@ -82,51 +81,6 @@ The default config in `static/nginx/nginx.conf` is tuned for shared hosting at 5 | Compressed-text bandwidth | **−60 to −80%** | unchanged | brotli + gzip enabled in `http {}` | | WAF, Lua, HTTP/3 | included | not included | needs custom build | -### vs. OpenResty - -| Area | Twiy | OpenResty | -|---|---|---| -| nginx version | tracks upstream stable (1.30.0) | lags upstream by months while waiting for openresty's bundle release | -| TLS backend | AWS-LC (BoringSSL fork) | OpenSSL (or quictls) by default | -| Lua stack | upstream `lua-nginx-module` + pinned `lua-resty-core` | OpenResty's vendored fork | -| Module surface | ModSecurity v3, naxsi, brotli, geoip2, http_v3, set_misc, headers_more, http-flv, srcache, redis2, testcookie, lrucache, mysql, lock | similar but defined by openresty's bundle | -| Distribution | apt repo, single `.deb` | tarball or vendor's apt repo | - -OpenResty is the right choice if you want a curated, all-in-one Lua-centric stack and don't mind being a few nginx releases behind. Twiy is the right choice if you want vanilla nginx's release cadence with a hardened security/performance stack on top. - -### vs. Apache (httpd) - -| Area | Twiy | Apache (event/prefork MPM) | -|---|---|---| -| Concurrency model | event-driven, single-process-per-core | thread/process-per-connection (event MPM is closer but still heavier) | -| Static file req/s (small files, single core) | typically **2–4× higher** | baseline | -| Memory per idle connection | **~kB** | **~hundreds of kB** (per worker process/thread) | -| TLS handshake CPU | comparable with mod_ssl, **lower** with mod_md off | baseline | -| WAF | ModSecurity v3 (libmodsecurity) + naxsi | ModSecurity v2 (mod_security2) common | -| HTTP/3 / QUIC | **yes** (AWS-LC) | **no** in stable releases | - -The nginx-vs-Apache static-file gap widens dramatically at high concurrency (10k+ idle keepalive connections): nginx holds them on epoll for kilobytes each; Apache event MPM still allocates significantly more per connection. - -### Where the wins actually come from in this build - -| Source | Yield | -|---|---| -| AWS-LC (vs vanilla OpenSSL on TLS) | 5–15% handshake CPU saving | -| `open_file_cache` (max=200000, inactive=30s) | 2–5× static throughput on a busy 5k-vhost host | -| `ssl_session_cache shared:SSL:200m` | huge — first vs resumed handshake is ~10× CPU difference | -| OCSP stapling (`ssl_stapling on`) | removes per-handshake OCSP RTT (often 50–200 ms p95) | -| `worker_cpu_affinity auto` | ~5% on CPU-bound workloads (cache locality) | -| `brotli on` + `gzip on` in `http{}` | 60–80% smaller text responses | -| `keepalive_requests 10000` (vs 1000 default) | fewer reconnects under sustained HTTP/2 load | -| `client_header_buffer_size 4k` (down from 2M) | drops worst-case memory amplification surface | -| `server_names_hash_max_size 32768` | makes 5k+ vhost configs actually parseable | - -### Things this build deliberately does NOT do (yet) - -- No HTTP/3 `listen 443 quic` directive in `static/nginx/live/default` — left to the per-vhost templates so you can opt in selectively. -- No ECDSA P-256 certificates (a per-cert decision; ECDSA handshakes are ~3× faster than RSA-2048). -- No OS-level sysctl tuning (`net.core.rmem_max` for QUIC, `net.core.somaxconn`, `fs.file-max`) — would belong in the `.deb` postinst or a `/etc/sysctl.d/twiy.conf` shipped with the package; not yet wired up. - # Support options. - No free support for how to do things, please don't spam with questions in discord. diff --git a/build/run.sh b/build/run.sh index 7845776..27e637f 100644 --- a/build/run.sh +++ b/build/run.sh @@ -7,7 +7,7 @@ function reqs() { apt-get -y install wget zip unzip build-essential libssl-dev curl nano git # apt-get -y install iptables ipset apt-get install libtool pkg-config make cmake automake autoconf golang-go ninja-build -y - apt-get install libyajl-dev ssdeep zlib1g-dev libxslt1-dev libgd-dev libgeoip-dev liblmdb-dev libfuzzy-dev libmaxminddb-dev liblua5.1-dev libcurl4-openssl-dev libxml2 libxml2-dev mercurial libpcre2-dev libc-ares-dev libre2-dev -y + apt-get install libyajl-dev ssdeep zlib1g-dev libxslt1-dev libgd-dev libgeoip-dev liblmdb-dev libfuzzy-dev libmaxminddb-dev liblua5.1-dev libcurl4-openssl-dev libxml2 libxml2-dev mercurial libpcre2-dev libc-ares-dev libre2-dev libzstd-dev libjemalloc2 -y mkdir -p $LUA_SCRIPTS } function clean_install() { @@ -204,6 +204,14 @@ function clean_install() { cd /opt/mod/; git clone --recurse-submodules https://github.com/wargio/naxsi.git naxsi fi + # NGX_MOD_ZSTD — Zstandard compression module from tokers. Pinned via + # NGX_MOD_ZSTD; tarball pattern (dir name embeds version → cache invalidates + # automatically when the pin moves). + if [ ! -d /opt/mod/zstd-nginx-module-${NGX_MOD_ZSTD} ]; then + cd /opt/mod/; wget https://github.com/tokers/zstd-nginx-module/archive/refs/tags/${NGX_MOD_ZSTD}.tar.gz + cd /opt/mod/; tar xf ${NGX_MOD_ZSTD}.tar.gz; rm -Rf ${NGX_MOD_ZSTD}.tar.gz + fi + # END OF NGINX MODULES # ============================================================================================================ } @@ -261,6 +269,7 @@ test_nginx() { --add-module=/opt/mod/srcache-nginx-module-${NGX_MOD_LUA_SRCACHE} \ --add-module=/opt/mod/redis2-nginx-module \ --add-module=/opt/mod/ngx_brotli \ + --add-module=/opt/mod/zstd-nginx-module-${NGX_MOD_ZSTD} \ --add-module=/opt/mod/testcookie \ --with-cc-opt="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC -I/usr/local/aws-lc/include" \ --with-ld-opt="-Wl,-rpath,/usr/local/LuaJIT/lib -Wl,-rpath,/usr/local/lib -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie -L/opt/mod/pcre2-${SYSTEM_PCRE}/.libs -lpcre2-8 -L/usr/local/aws-lc/lib -lssl -lcrypto -Wl,-rpath,/usr/local/aws-lc/lib" @@ -319,6 +328,7 @@ function build() { --add-module=/opt/mod/srcache-nginx-module-${NGX_MOD_LUA_SRCACHE} \ --add-module=/opt/mod/redis2-nginx-module \ --add-module=/opt/mod/ngx_brotli \ + --add-module=/opt/mod/zstd-nginx-module-${NGX_MOD_ZSTD} \ --add-module=/opt/mod/testcookie \ --with-cc-opt="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC -I/usr/local/aws-lc/include" \ --with-ld-opt="-Wl,-rpath,/usr/local/LuaJIT/lib -Wl,-rpath,/usr/local/lib -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie -L/opt/mod/pcre2-${SYSTEM_PCRE}/.libs -lpcre2-8 -L/usr/local/aws-lc/lib -lssl -lcrypto -Wl,-rpath,/usr/local/aws-lc/lib" diff --git a/static/Jammy/nginx.service b/static/Jammy/nginx.service index ae3b11e..d9b1794 100644 --- a/static/Jammy/nginx.service +++ b/static/Jammy/nginx.service @@ -6,6 +6,10 @@ Wants=network-online.target [Service] Type=forking PIDFile=/run/nginx.pid +# jemalloc replaces glibc malloc — better fragmentation/perf under nginx's +# alloc/free churn at scale. Package depends on libjemalloc2 so the .so is +# guaranteed present. Removing this line falls back to glibc malloc cleanly. +Environment=LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 ExecStartPre=/usr/bin/install -d -o nginx -g nginx -m 0755 /usr/local/nginx /usr/local/nginx/client_body_temp /usr/local/nginx/proxy_temp /usr/local/nginx/fastcgi_temp /usr/local/nginx/uwsgi_temp /usr/local/nginx/scgi_temp /var/log/nginx ExecStartPre=/usr/sbin/nginx -t ExecStart=/usr/sbin/nginx diff --git a/static/nginx/nginx.conf b/static/nginx/nginx.conf index 5c40042..58b63c5 100644 --- a/static/nginx/nginx.conf +++ b/static/nginx/nginx.conf @@ -8,7 +8,7 @@ user nginx; pid /var/run/nginx.pid; worker_processes auto; -worker_cpu_affinity auto; # Pin workers to cores for L1/L2 locality. +worker_cpu_affinity auto; worker_rlimit_nofile 65535; events { @@ -32,8 +32,6 @@ http { # =================== END LOGS ========================= # # ==================== GENERAL ========================= # - # Header buffers — keep small. The previous 2M default was a memory - # amplification target (per-conn × worker_connections = absurd worst case). client_header_buffer_size 4k; large_client_header_buffers 4 16k; client_body_buffer_size 16k; @@ -41,10 +39,10 @@ http { client_body_timeout 30s; client_header_timeout 30s; send_timeout 30s; - reset_timedout_connection on; # Free sockets fast under churn. - keepalive_timeout 65s; # Amortise TCP setup across requests. - keepalive_requests 10000; # Default 1000 too low for HTTP/2. - max_headers 100; # nginx 1.29.8 — slowloris defence. + reset_timedout_connection on; + keepalive_timeout 65s; + keepalive_requests 2000; + max_headers 100; port_in_redirect off; sendfile on; sendfile_max_chunk 1m; @@ -53,8 +51,6 @@ http { server_tokens off; server_name_in_redirect off; - # 5,000+ vhost hash sizing. _max_size must exceed total server names; - # _bucket_size must be a CPU-cache-line multiple (32/64/128/256/512/1024). server_names_hash_bucket_size 128; server_names_hash_max_size 32768; types_hash_max_size 4096; @@ -67,14 +63,13 @@ http { # ===================== TLS ============================ # ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers off; # TLS 1.3 ciphers, client picks. - ssl_session_cache shared:SSL:200m; # ~800k sessions shared across workers + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:200m; ssl_session_timeout 1d; - ssl_session_tickets off; # Off unless you have ticket-key rotation. - ssl_stapling on; # OCSP stapling — avoid per-handshake OCSP lookups. + ssl_session_tickets off; + ssl_stapling on; ssl_stapling_verify on; # ===================== END TLS ======================== # - resolver 1.1.1.1 1.0.0.1 valid=300s; resolver_timeout 5s; default_type application/octet-stream; @@ -86,9 +81,6 @@ http { } # ==================== COMPRESSION ===================== # - # Compiled in, now actually enabled. Bandwidth saving on text responses - # is typically 60-80% for HTML/JSON/CSS/JS/SVG. Comp level 4 is the - # sweet spot for CPU vs ratio on shared hosting. gzip on; gzip_vary on; gzip_proxied any; @@ -100,6 +92,11 @@ http { brotli_comp_level 4; brotli_min_length 256; brotli_types text/plain text/css text/xml application/json application/javascript application/xml application/xml+rss application/atom+xml image/svg+xml font/ttf font/otf font/woff font/woff2; + + zstd on; + zstd_comp_level 4; + zstd_min_length 256; + zstd_types text/plain text/css text/xml application/json application/javascript application/xml application/xml+rss application/atom+xml image/svg+xml font/ttf font/otf font/woff font/woff2; # =================== END COMPRESSION ================== # # =================== END GENERAL ====================== # diff --git a/version b/version index 0742fe8..d6da55e 100644 --- a/version +++ b/version @@ -52,3 +52,8 @@ export NGX_MOD_LUA_LOCK="0.09" # https://github.com/openresty/srcache-nginx-module/tags export NGX_MOD_LUA_SRCACHE="0.33" + +# https://github.com/tokers/zstd-nginx-module/tags +# Zstandard compression module. Chrome 123+ and Firefox 126+ send +# `Accept-Encoding: zstd`; older clients fall back to brotli/gzip. +export NGX_MOD_ZSTD="0.1.1"