This commit is contained in:
Dallas Lu 2026-06-09 14:41:15 +08:00
parent c50569d73e
commit a624163120
No known key found for this signature in database
27 changed files with 215 additions and 6 deletions

View file

@ -84,6 +84,16 @@ http {
Use `kit/http/websocket-map.conf` only when a `location {}` will include `kit/proxy_pass/websocket.conf`.
```nginx
http {
include kit/http/log-format-upstream.conf;
}
```
Use `kit/http/log-format-upstream.conf` when you want a reusable access log
format with upstream timing fields. It only defines `upstream_timing`; each
server still opts in with its own `access_log` directive.
### Reverse proxy
Plain HTTP reverse proxying only needs the `location {}`-level proxy snippets:
@ -100,6 +110,33 @@ server {
}
```
### Streaming reverse proxy
For SSE, token streaming, or other incremental responses, add the streaming and
long-timeout snippets to the proxied location:
```nginx
http {
include kit/http/log-format-upstream.conf;
server {
include kit/listen/http.conf;
access_log /var/log/nginx/app.access.log upstream_timing;
location /events/ {
include kit/proxy_pass/forwarded.conf;
include kit/proxy_pass/streaming.conf;
include kit/proxy_pass/timeout-300.conf;
proxy_pass http://app_backend;
}
}
}
```
Use `kit/proxy_pass/streaming.conf` only for locations that genuinely need
incremental flushing. It intentionally changes buffering behavior and forces
HTTP/1.1 for that location.
### Websocket reverse proxy
Websocket proxying adds one `http {}`-level dependency plus the websocket location snippet:
@ -150,6 +187,7 @@ server {
## Snippet reference
- `kit/http/gzip.conf`: gzip compression for common text-based responses. Must be included inside `http {}`.
- `kit/http/log-format-upstream.conf`: defines the `upstream_timing` access log format with upstream timing fields. Must be included inside `http {}`.
- `kit/http/websocket-map.conf`: defines `$connection_upgrade` for websocket proxying. Must be included inside `http {}`.
- `kit/listen/http.conf`: IPv4 and IPv6 HTTP listeners for `server {}`.
- `kit/listen/https.conf`: IPv4 and IPv6 HTTPS listeners for `server {}` without enabling HTTP/2.
@ -158,6 +196,7 @@ server {
- `kit/security.conf`: common low-risk security headers and host normalization. Intended for `server {}`.
- `kit/security-legacy.conf`: optional legacy compatibility headers such as `X-Download-Options` and `X-Permitted-Cross-Domain-Policies`.
- `kit/fastcgi/hide-powered-by.conf`: hides `X-Powered-By` from FastCGI upstream responses.
- `kit/fastcgi/timeout-300.conf`: longer FastCGI timeouts. Intended for `location {}`.
- `kit/ssl/security.conf`: TLS protocol and session resumption settings. Intended for `server {}`.
- `kit/ssl/hsts.conf`: HSTS header for HTTPS responses. Intended for `server {}`.
- `kit/ssl/hsts-preload.conf`: HSTS variant with `preload`. Use only if the whole domain tree is preload-safe.
@ -165,6 +204,8 @@ server {
- `kit/redirect/to-primary-domain.conf`: redirects aliases to the primary `server_name`. Intended for `server {}`.
- `kit/proxy_pass/forwarded.conf`: standard reverse proxy headers. Intended for `location {}`.
- `kit/proxy_pass/hide-powered-by.conf`: hides `X-Powered-By` from proxied upstream responses.
- `kit/proxy_pass/https-upstream.conf`: enables SNI for HTTPS upstreams. Intended for `location {}`.
- `kit/proxy_pass/streaming.conf`: disables proxy buffering for streaming responses and requests. Intended for `location {}`.
- `kit/proxy_pass/websocket.conf`: websocket upgrade headers. Requires `kit/http/websocket-map.conf`.
- `kit/proxy_pass/timeout-300.conf`: longer proxy timeouts. Intended for `location {}`.
@ -180,6 +221,8 @@ The script validates:
- [examples/example.com.conf](examples/example.com.conf:1) as a server-level snippet.
- [examples/reverse-proxy.nginx.conf](examples/reverse-proxy.nginx.conf:1) as a complete nginx config.
- The optional logging, streaming, HTTPS-upstream, and timeout snippets via
synthetic configs assembled in the validation script.
## Notes

View file

@ -1 +1,3 @@
# Mirror the proxy snippet so FastCGI-backed apps can drop framework branding
# without forcing the behavior into unrelated FastCGI locations.
fastcgi_hide_header X-Powered-By;

8
fastcgi/timeout-300.conf Normal file
View file

@ -0,0 +1,8 @@
# Match the long-running proxy timeout profile for FastCGI backends such as PHP
# workers or app servers behind fcgiwrap.
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
# Keep the downstream client socket aligned with the upstream timeout profile.
send_timeout 300;

View file

@ -1,6 +1,9 @@
# Enable gzip for common text-based responses.
gzip on;
gzip_vary on;
# Keep the default compression level moderate so the CPU cost stays predictable
# on small VPS instances.
gzip_comp_level 4;
gzip_min_length 256;
@ -27,3 +30,6 @@ gzip_types
text/plain
text/xml
text/vtt;
# Do not list text/html here. nginx already compresses it implicitly, and
# repeating it suggests callers need to keep the two lists in sync.

View file

@ -0,0 +1,23 @@
# Define a reusable access log format with upstream timing fields, but do not
# enable logging by default. Individual servers still choose their own log path
# and whether this format is worth the I/O cost.
#
# Example:
# access_log /var/log/nginx/access.log upstream_timing;
log_format upstream_timing escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"host":"$host",'
'"request":"$request",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_addr":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"upstream_connect_time":"$upstream_connect_time",'
'"upstream_header_time":"$upstream_header_time",'
'"upstream_response_time":"$upstream_response_time",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent"'
'}';

View file

@ -1,3 +1,5 @@
# Map Upgrade to a reusable Connection value so websocket locations can opt in
# without hard-coding "upgrade" for every request.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;

View file

@ -1,2 +1,5 @@
# Minimal dual-stack HTTP listener. Keep default_server and proxy_protocol out
# of the shared baseline so projects do not inherit mutually incompatible
# listener behavior by accident.
listen 80;
listen [::]:80;

View file

@ -1 +1,3 @@
# Keep HTTP/2 separate from https.conf so older nginx 1.24.x systems can keep
# using the compatibility snippet instead of failing on "http2 on;".
http2 on;

View file

@ -1,2 +1,4 @@
# Compatibility listener for nginx 1.24.x and distro packages that still
# expect HTTP/2 on the listen directive instead of a standalone "http2 on;".
listen 443 ssl http2;
listen [::]:443 ssl http2;

View file

@ -1,2 +1,4 @@
# HTTPS listener without HTTP/2. Use this together with http2.conf on nginx
# 1.25.1+ so newer installs avoid the "listen ... http2" deprecation warning.
listen 443 ssl;
listen [::]:443 ssl;

View file

@ -1,11 +1,24 @@
# Preserve the original Host header, including a non-default port, because many
# upstream frameworks use it when generating absolute URLs.
proxy_set_header Host $http_host;
# Keep the de-facto standard X-Forwarded-* headers and the older Scheme header
# together. Some upstreams still read Scheme while newer ones prefer
# X-Forwarded-Proto.
proxy_set_header Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# Preserve WebDAV and object-storage style Destination requests when proxying.
proxy_set_header Destination $http_destination;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Leave this legacy hint in place because some older applications and middleware
# still branch on it when they know they are behind nginx.
proxy_set_header X-NginX-Proxy true;
# Avoid rewriting Location headers implicitly. Callers can add explicit
# proxy_redirect rules locally if an upstream really needs them.
proxy_redirect off;

View file

@ -1 +1,4 @@
# Keep this separate from forwarded.conf so callers can decide whether hiding
# upstream branding is worth potentially masking framework details during
# debugging.
proxy_hide_header X-Powered-By;

View file

@ -0,0 +1,6 @@
# Enable SNI when proxy_pass targets an HTTPS origin by hostname. Without this,
# multi-tenant upstreams can return the wrong certificate or application.
proxy_ssl_server_name on;
# Do not force proxy_ssl_name or proxy_ssl_verify here. Those depend on whether
# the caller proxies to a hostname, an upstream block, or a private CA.

16
proxy_pass/streaming.conf Normal file
View file

@ -0,0 +1,16 @@
# Use HTTP/1.1 only in explicit streaming locations. Keeping this out of the
# default forwarded.conf avoids changing connection semantics for every proxy.
proxy_http_version 1.1;
# Disable buffering so SSE, token streams, and other incremental responses can
# flush chunks immediately instead of waiting for nginx to coalesce them.
proxy_buffering off;
# Disable request buffering as well for duplex APIs and streaming uploads. Put
# this behind an opt-in snippet because large upload endpoints may want the
# default buffered behavior instead.
proxy_request_buffering off;
# gzip can delay flushes by collecting more bytes before compression. Turn it
# off in explicit streaming locations even if gzip is enabled globally.
gzip off;

View file

@ -1,4 +1,9 @@
# Keep this as an opt-in long-request profile instead of raising timeouts in
# forwarded.conf for every proxy location.
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
# send_timeout covers the downstream client socket too, so long-lived responses
# do not inherit a shorter default than the upstream leg.
send_timeout 300;

View file

@ -1,3 +1,8 @@
# nginx defaults to proxying with HTTP/1.0. Websocket upgrade requires 1.1, so
# keep that here instead of in the generic forwarded.conf snippet.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
# Use the mapped value from http/websocket-map.conf so non-upgrade requests can
# still close cleanly instead of always advertising "Connection: upgrade".
proxy_set_header Connection $connection_upgrade;

View file

@ -1,9 +1,14 @@
set $root_domain "";
# Derive the apex domain from a www-prefixed primary server_name. This keeps the
# snippet simple, but it also means callers should not use it on multi-name
# server blocks whose first name is not the canonical www host.
if ($server_name ~* ^www\.(?<apex_domain>.+)$) {
set $root_domain $apex_domain;
}
# Use 307 so POST and other non-GET methods keep their method during
# canonicalization instead of being rewritten to GET as with many 301/302 flows.
if ($host = $root_domain) {
return 307 $scheme://$server_name$request_uri;
}

View file

@ -1,3 +1,5 @@
# Keep alias canonicalization method-preserving. This is safer than 301 when a
# non-idempotent request accidentally hits an alias host.
if ($host != $server_name) {
return 307 $scheme://$server_name$request_uri;
}

View file

@ -1,3 +1,5 @@
# This is the inverse of root-to-www.conf. It assumes the primary server_name
# is already the apex host and only strips a single leading www. label.
if ($host = www.$server_name) {
return 307 $scheme://$server_name$request_uri;
}

View file

@ -35,6 +35,7 @@ $optionalSnippetConfig = @(
""
" location /fastcgi {"
" include /etc/nginx/kit/fastcgi/hide-powered-by.conf;"
" include /etc/nginx/kit/fastcgi/timeout-300.conf;"
" }"
""
" location /proxy {"
@ -46,6 +47,38 @@ $optionalSnippetConfig = @(
$optionalSnippetConfigShell = $optionalSnippetConfig -replace "`n", "\\n"
$advancedProxyConfig = @(
"events {}"
""
"http {"
" include /etc/nginx/mime.types;"
" default_type application/octet-stream;"
""
" include /etc/nginx/kit/http/log-format-upstream.conf;"
""
" server {"
" include /etc/nginx/kit/listen/http.conf;"
" server_name streaming.example.com;"
" access_log /var/log/nginx/streaming.access.log upstream_timing;"
""
" location /events/ {"
" include /etc/nginx/kit/proxy_pass/forwarded.conf;"
" include /etc/nginx/kit/proxy_pass/streaming.conf;"
" include /etc/nginx/kit/proxy_pass/timeout-300.conf;"
" proxy_pass http://127.0.0.1:9000;"
" }"
""
" location /secure-upstream/ {"
" include /etc/nginx/kit/proxy_pass/forwarded.conf;"
" include /etc/nginx/kit/proxy_pass/https-upstream.conf;"
" proxy_pass https://example.com;"
" }"
" }"
"}"
) -join "\n"
$advancedProxyConfigShell = $advancedProxyConfig -replace "`n", "\\n"
$modernHttp2Config = @(
"events {}"
""
@ -79,6 +112,7 @@ $containerCommand = @(
"cp /etc/nginx/kit/examples/reverse-proxy.nginx.conf /tmp/nginx-kit/examples/reverse-proxy.nginx.conf"
"printf '%b' '$serverSnippetConfigShell' > /tmp/nginx-kit/server-snippet.nginx.conf"
"printf '%b' '$optionalSnippetConfigShell' > /tmp/nginx-kit/optional-snippets.nginx.conf"
"printf '%b' '$advancedProxyConfigShell' > /tmp/nginx-kit/advanced-proxy.nginx.conf"
"printf '%b' '$modernHttp2ConfigShell' > /tmp/nginx-kit/modern-http2.nginx.conf"
"echo 'Validating examples/example.com.conf'"
"nginx -t -c /tmp/nginx-kit/server-snippet.nginx.conf"
@ -86,6 +120,8 @@ $containerCommand = @(
"nginx -t -c /tmp/nginx-kit/examples/reverse-proxy.nginx.conf"
"echo 'Validating optional security and hide-powered-by snippets'"
"nginx -t -c /tmp/nginx-kit/optional-snippets.nginx.conf"
"echo 'Validating optional upstream logging, streaming, and HTTPS-upstream snippets'"
"nginx -t -c /tmp/nginx-kit/advanced-proxy.nginx.conf"
"echo 'Validating modern http2 on snippets'"
"nginx -t -c /tmp/nginx-kit/modern-http2.nginx.conf"
) -join "; "

View file

@ -1,2 +1,5 @@
# Keep legacy browser-era headers out of the default security baseline. They are
# still occasionally requested by enterprise scanners, but modern browsers
# rarely depend on them.
add_header X-Download-Options noopen always;
add_header X-Permitted-Cross-Domain-Policies none always;

View file

@ -4,9 +4,13 @@ server_tokens off;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
# Explicitly disable the legacy XSS Auditor. Modern browsers removed it, and
# some older implementations created security bugs of their own.
add_header X-XSS-Protection "0" always;
# Redirect `example.com.` to `example.com`
# Redirect `example.com.` to `example.com`. Use $host on the target so nginx
# emits the normalized host without the trailing dot.
if ($http_host ~ "\.$" ){
rewrite ^(.*) $scheme://$host$1 permanent;
}

View file

@ -1,3 +1,5 @@
# Preserve the request method during HTTP->HTTPS upgrades. 301 is more common,
# but 307 avoids surprising POST-to-GET rewrites on login and webhook paths.
if ($scheme = http) {
return 307 https://$http_host$request_uri;
}

View file

@ -1,5 +1,8 @@
set $hsts_header_value "";
# Keep the same HTTP/HTTPS guard as hsts.conf. The only difference is the
# preload token, which should be enabled only after the whole domain tree is
# known to be HTTPS-only.
if ($scheme = "https") {
set $hsts_header_value "max-age=31536000; includeSubDomains; preload";
}

View file

@ -1,5 +1,7 @@
set $hsts_header_value "";
# Only emit HSTS on HTTPS responses. This lets a single server block listen on
# both 80 and 443 without sending a meaningless STS header over plain HTTP.
if ($scheme = "https") {
set $hsts_header_value "max-age=31536000; includeSubDomains";
}

View file

@ -1,6 +1,13 @@
# TLSv1.2+ is the practical modern baseline. Older protocols create more
# compatibility burden than value in a shared default kit.
ssl_protocols TLSv1.2 TLSv1.3;
# Let nginx/OpenSSL pick the best named group set available on the host instead
# of freezing a list that will age badly across distro upgrades.
ssl_ecdh_curve auto;
# Keep a small shared cache because session resumption helps repeat visitors,
# but avoid huge caches that imply cross-host coordination.
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

View file

@ -1,2 +1,4 @@
# Copy this file into snippets/cert/<your-domain>.conf and replace the paths
# with the certificate material issued for that exact hostname set.
ssl_certificate /etc/ssl/certimate/example.com.crt;
ssl_certificate_key /etc/ssl/certimate/example.com.key;