NGHTTPX(1)

๐Ÿฆ‹ NGHTTPX(1) ๐Ÿฆ‹ โ€” nghttp2 manual, but make it make sense ๐Ÿ’…

๐ŸŒธ NAME ๐ŸŒธ

nghttpx โ€” HTTP/2 proxy ๐Ÿซถ

โœจ SYNOPSIS โœจ

nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>]

๐Ÿ’– DESCRIPTION ๐Ÿ’–

Okay so nghttpx is a reverse proxy for HTTP/3, HTTP/2, and HTTP/1. literally serving all the vibes. ๐ŸŒˆ

  • <PRIVATE_KEY> โ€” set path to server's private key. Required unless "no-tls" parameter is used in --frontend option. ๐Ÿ”‘
  • <CERT> โ€” set path to server's certificate. Required unless "no-tls" parameter is used in --frontend option. ๐Ÿ“œ

๐ŸŒˆ OPTIONS ๐ŸŒˆ

The options are categorized into several groups. yes bestie, categories. we love a little organization. โœจ

๐Ÿ”Œ Connections ๐Ÿ”Œ

  • -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][[;<PARAM>]...] โ€” set backend host and port. The multiple backend addresses are accepted by repeating this option. UNIX domain socket can be specified by prefixing path name with "unix:" (e.g., unix:/var/run/backend.sock). ๐Ÿ”ฅ
    • Optionally, if <PATTERN>s are given, the backend address is only used if request matches the pattern. The pattern matching is closely designed to ServeMux in net/http package of Go programming language. <PATTERN> consists of path, host + path or just host. The path must start with "/". If it ends with "/", it matches all request path in its subtree. To deal with the request to the directory without trailing slash, the path which ends with "/" also matches the request path which only lacks trailing '/' (e.g., path "/foo/" matches request path "/foo"). If it does not end with "/", it performs exact match against the request path. If host is given, it performs a match against the request host. For a request received on the frontend listener with "sni-fwd" parameter enabled, SNI host is used instead of a request host. If host alone is given, "/" is appended to it, so that it matches all request paths under the host (e.g., specifying "nghttp2.org" equals to "nghttp2.org/"). CONNECT method is treated specially. It does not have path, and we don't allow empty path. To workaround this, we assume that CONNECT method has "/" as path. ๐Ÿงš
    • Patterns with host take precedence over patterns with just path. Then, longer patterns take precedence over shorter ones. ๐Ÿ‘‘
    • Host can include "*" in the left most position to indicate wildcard match (only suffix match is done). The "*" must match at least one character. For example, host pattern "*.nghttp2.org" matches against "www.nghttp2.org" and "git.ngttp2.org", but does not match against "nghttp2.org". The exact hosts match takes precedence over the wildcard hosts match. ๐Ÿฆ‹
    • If path part ends with "*", it is treated as wildcard path. The wildcard path behaves differently from the normal path. For normal path, match is made around the boundary of path component separator, "/". On the other hand, the wildcard path does not take into account the path component separator. All paths which include the wildcard path without last "*" as prefix, and are strictly longer than wildcard path without last "*" are matched. "*" must match at least one character. For example, the pattern "/foo*" matches "/foo/" and "/foobar". But it does not match "/foo", or "/fo". ๐Ÿ’…
    • If <PATTERN> is omitted or empty string, "/" is used as pattern, which matches all request paths (catch-all pattern). The catch-all backend must be given. ๐Ÿšจ
    • When doing a match, nghttpx made some normalization to pattern, request host and path. For host part, they are converted to lower case. For path part, percent-encoded unreserved characters defined in RFC 3986 are decoded, and any dot-segments (".." and ".") are resolved and removed. โœจ
    • For example, -b'127.0.0.1,8080;nghttp2.org/httpbin/' matches the request host "nghttp2.org" and the request path "/httpbin/get", but does not match the request host "nghttp2.org" and the request path "/index.html". ๐ŸŽฏ
    • The multiple <PATTERN>s can be specified, delimiting them by ":". Specifying -b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the same effect to specify -b'127.0.0.1,8080;nghttp2.org' and -b'127.0.0.1,8080;www.nghttp2.org'. ๐Ÿ”
    • The backend addresses sharing same <PATTERN> are grouped together forming load balancing group. โš–๏ธ
    • Several parameters <PARAM> are accepted after <PATTERN>. The parameters are delimited by ";". The available parameters are: "proto=<PROTO>", "tls", "sni=<SNI_HOST>", "fall=<N>", "rise=<N>", "affinity=<METHOD>", "dns", "redirect-if-not-tls", "upgrade-scheme", "mruby=<PATH>", "read-timeout=<DURATION>", "write-timeout=<DURATION>", "group=<GROUP>", "group-weight=<N>", "weight=<N>", and "dnf". The parameter consists of keyword, and optionally followed by "=" and value. For example, the parameter "proto=h2" consists of the keyword "proto" and value "h2". The parameter "tls" consists of the keyword "tls" without value. Each parameter is described as follows. ๐Ÿ“
    • The backend application protocol can be specified using optional "proto" parameter, and in the form of "proto=<PROTO>". <PROTO> should be one of the following list without quotes: "h2", "http/1.1". The default value of <PROTO> is "http/1.1". Note that usually "h2" refers to HTTP/2 over TLS. But in this option, it may mean HTTP/2 over cleartext TCP unless "tls" keyword is used (see below). ๐ŸŒ
    • TLS can be enabled by specifying optional "tls" parameter. TLS is not enabled by default. ๐Ÿ”’
    • With "sni=<SNI_HOST>" parameter, it can override the TLS SNI field value with given <SNI_HOST>. This will default to the backend <HOST> name. ๐ŸŽ€
    • The feature to detect whether backend is online or offline can be enabled using optional "fall" and "rise" parameters. Using "fall=<N>" parameter, if nghttpx cannot connect to a this backend <N> times in a row, this backend is assumed to be offline, and it is excluded from load balancing. If <N> is 0, this backend never be excluded from load balancing whatever times nghttpx cannot connect to it, and this is the default. There is also "rise=<N>" parameter. After backend was excluded from load balancing group, nghttpx periodically attempts to make a connection to the failed backend, and if the connection is made successfully <N> times in a row, the backend is assumed to be online, and it is now eligible for load balancing target. If <N> is 0, a backend is permanently offline, once it goes in that state, and this is the default behaviour. ๐Ÿ“ˆ๐Ÿ“‰
    • The session affinity is enabled using "affinity=<METHOD>" parameter. If "ip" is given in <METHOD>, client IP based session affinity is enabled. If "cookie" is given in <METHOD>, cookie based session affinity is enabled. If "none" is given in <METHOD>, session affinity is disabled, and this is the default. The session affinity is enabled per <PATTERN>. If at least one backend has "affinity" parameter, and its <METHOD> is not "none", session affinity is enabled for all backend servers sharing the same <PATTERN>. It is advised to set "affinity" parameter to all backend explicitly if session affinity is desired. The session affinity may break if one of the backend gets unreachable, or backend settings are reloaded or replaced by API. ๐Ÿช
    • If "affinity=cookie" is used, the additional configuration is required. "affinity-cookie-name=<NAME>" must be used to specify a name of cookie to use. Optionally, "affinity-cookie-path=<PATH>" can be used to specify a path which cookie is applied. The optional "affinity-cookie-secure=<SECURE>" controls the Secure attribute of a cookie. The default value is "auto", and the Secure attribute is determined by a request scheme. If a request scheme is "https", then Secure attribute is set. Otherwise, it is not set. If <SECURE> is "yes", the Secure attribute is always set. If <SECURE> is "no", the Secure attribute is always omitted. "affinity-cookie-stickiness=<STICKINESS>" controls stickiness of this affinity. If <STICKINESS> is "loose", removing or adding a backend server might break the affinity and the request might be forwarded to a different backend server. If <STICKINESS> is "strict", removing the designated backend server breaks affinity, but adding new backend server does not cause breakage. If the designated backend server becomes unavailable, new backend server is chosen as if the request does not have an affinity cookie. <STICKINESS> defaults to "loose". ๐Ÿช๐Ÿ”
    • By default, name resolution of backend host name is done at start up, or reloading configuration. If "dns" parameter is given, name resolution takes place dynamically. This is useful if backend address changes frequently. If "dns" is given, name resolution of backend host name at start up, or reloading configuration is skipped. ๐ŸŒ
    • If "redirect-if-not-tls" parameter is used, the matched backend requires that frontend connection is TLS encrypted. If it isn't, nghttpx responds to the request with 308 status code, and https URI the client should use instead is included in Location header field. The port number in redirect URI is 443 by default, and can be changed using --redirect-https-port option. If at least one backend has "redirect-if-not-tls" parameter, this feature is enabled for all backend servers sharing the same <PATTERN>. It is advised to set "redirect-if-no-tls" parameter to all backends explicitly if this feature is desired. (and yes, the original says "redirect-if-no-tls" right there at the end, we're just keeping the typo, no cap ๐Ÿ’€)

If upgrade-scheme is used together with tls, the HTTP/2 :scheme pseudo header field gets flipped from http to https when the request gets forwarded to this specific backend. It's a lil workaround for a backend server that insists on the https :scheme pseudo header field on a TLS encrypted connection. iykyk ๐Ÿ’…

  • mruby=<PATH> โ€” points at the path of an mruby script file that gets invoked the moment this pattern matches. Heads up bestie: all backends sharing the same pattern must have the same mruby path. no exceptions, periodt.
  • read-timeout=<DURATION> and write-timeout=<DURATION> โ€” these set the read and write timeout of the backend connection when this here pattern matches. And yes, all backends sharing one pattern must share the same timeouts. If you straight up omit these timeouts for a pattern, then --backend-read-timeout and --backend-write-timeout are used instead. ๐Ÿซถ
  • group=<GROUP> โ€” names the group this backend address belongs to. By default it just chills in the unnamed default group. Group names are unique per pattern. Then group-weight=<N> sets that group's weight: the higher the weight, the more often the load balancing algorithm picks it. <N> must be in [1, 256] inclusive. Weight 8 is literally 4 times more weight than 2. <N> has to be identical for every address sharing the same <GROUP>. If group-weight is omitted on one address but another address in that same group specifies it, that weight gets used. And if literally no one specifies group-weight, the group's weight becomes 1. Oh and group and group-weight? Ignored entirely if session affinity is enabled. ๐Ÿ’€
  • weight=<N> โ€” the weight of the backend address inside the group it belongs to. Higher weight = picked more often by the load balancing algorithm. <N> must be [1, 256] inclusive. Weight 8 has 4 times more weight than weight 2. Omit it and weight becomes 1. And weight is ignored if session affinity is enabled. shrug ๐ŸŒธ
  • dnf โ€” short for "do not forward", and it does exactly what it says. An incoming request is not forwarded to a backend; it's just consumed along with the request body (a backend server literally never gets contacted). The expectation is that the HTTP response gets generated by an mruby script (see that mruby=<PATH> parameter up above ๐Ÿ‘€).

Since ; and : are used as delimiters, <PATTERN> must not contain those characters. To sneak a : into <PATTERN>, you have to write %3A (that's the percent-encoded form of : ) instead. And since ; has special meaning in the shell, the option value has to be quoted. fr fr.

Default: 127.0.0.1,80

  • -f, --frontend=(<HOST>,<PORT>|unix:<PATH>)[[;<PARAM>]...] โ€” set the frontend host and port, bestie. If <HOST> is '*', it assumes all addresses, both IPv4 and IPv6. You can specify a UNIX domain socket by prefixing the path name with unix: (e.g., unix:/var/run/nghttpx.sock). This option can be used multiple times to listen on multiple addresses. iconic. ๐Ÿ‘‘

This option takes 0 or more parameters, described below. Note that api and healthmon parameters are mutually exclusive โ€” can't have both, sorry. ๐Ÿ’…

Optionally, TLS can be disabled by specifying the no-tls parameter. TLS is enabled by default.

If the sni-fwd parameter is used, then when performing a match to select a backend server, the SNI host name received from the client is used instead of the request host. See --backend for how the pattern match works.

To make this frontend an API endpoint, specify the api parameter. This is disabled by default.

โš ๏ธ It is important to limit access to the API frontend. Otherwise someone may change the backend server and break your services, or expose confidential information to the outside world. not the vibe, bestie. ๐Ÿ™ˆ

To make this frontend a health monitor endpoint, specify the healthmon parameter. This is disabled by default. Any requests that come through this address are replied with 200 HTTP status, with no body at all.

To accept PROXY protocol version 1 and 2 on the frontend connection, specify the proxyproto parameter. This is disabled by default.

To receive HTTP/3 (QUIC) traffic, specify the quic parameter. It makes nghttpx listen on a UDP port rather than a TCP port. Note: UNIX domain socket, api, and healthmon parameters cannot be used together with the quic parameter. pick a struggle, honestly.

Default: *,3000

  • --backlog=<N> โ€” set the listen backlog size.

Default: 65536

  • --backend-address-family=(auto|IPv4|IPv6) โ€” specify the address family of backend connections. Give it auto and both IPv4 and IPv6 are considered. Give it IPv4 and only IPv4 addresses are considered. Give it IPv6 and only IPv6 addresses are considered.

Default: auto

  • --backend-http-proxy-uri=<URI> โ€” specify the proxy URI in the form <http:/>/[<USER>:<PASS>@]<PROXY>:<PORT>. If the proxy needs auth, throw in <USER> and <PASS>. Just know they have to be properly percent-encoded, okay? This proxy is used when the backend connection is HTTP/2. First, a CONNECT request is made to the proxy, and it connects to the backend on behalf of nghttpx. That forms a tunnel. After that, nghttpx performs the SSL/TLS handshake with the downstream through the tunnel. The timeouts when connecting and making that CONNECT request can be specified by --backend-read-timeout and --backend-write-timeout options. โœจ

โšก PERFORMANCE โšก (the glow-up section)

  • -n, --workers=<N> โ€” set the number of worker threads.

Default: 1

  • --single-thread โ€” run everything in one thread inside the worker process. This feature exists for a better debugging experience, or for platforms that just don't have thread support. If threading is disabled, this option is always enabled anyway. ๐Ÿงš
  • --read-rate=<SIZE> โ€” set the maximum average read rate on a frontend connection. Setting 0 here means read rate is unlimited.

Default: 0

  • --read-burst=<SIZE> โ€” set the maximum read burst size on a frontend connection. Setting 0 here means read burst size is unlimited.

Default: 0

  • --write-rate=<SIZE> โ€” set the maximum average write rate on a frontend connection. Setting 0 here means write rate is unlimited.

Default: 0

  • --write-burst=<SIZE> โ€” set the maximum write burst size on a frontend connection. Setting 0 here means write burst size is unlimited.

Default: 0

  • --worker-read-rate=<SIZE> โ€” set the maximum average read rate on a frontend connection per worker. Setting 0 here means read rate is unlimited. Not implemented yet.

Default: 0

  • --worker-read-burst=<SIZE> โ€” set the maximum read burst size on a frontend connection per worker. Setting 0 here means read burst size is unlimited. Not implemented yet.

Default: 0

  • --worker-write-rate=<SIZE> โ€” set the maximum average write rate on a frontend connection per worker. Setting 0 here means write rate is unlimited. Not implemented yet.

Default: 0

  • --worker-write-burst=<SIZE> โ€” set the maximum write burst size on a frontend connection per worker. Setting 0 here means write burst size is unlimited. Not implemented yet.

Default: 0

๐Ÿ’€ Not me hyping up four worker rate options that are literally not implemented yet. (they're coming, allegedly.)
  • --worker-frontend-connections=<N> โ€” set the maximum number of simultaneous connections the frontend accepts. Setting 0 means unlimited.

Default: 0

  • --backend-connections-per-host=<N> โ€” set the maximum number of backend concurrent connections (and/or streams, in the case of HTTP/2) per origin host. This option is meaningful when --http2-proxy option is used. The origin host is determined by the authority portion of the request URI (or the :authority header field for HTTP/2). To limit the number of connections per frontend for default mode, use --backend-connections-per-frontend instead. ๐Ÿฆ‹

Default: 8

  • --backend-connections-per-frontend=<N> โ€” set the maximum number of backend concurrent connections (and/or streams, in the case of HTTP/2) per frontend. This option is only used for default mode. 0 means unlimited. To limit the number of connections per host with the --http2-proxy option, use --backend-connections-per-host.

Default: 0

  • --rlimit-nofile=<N> โ€” set the maximum number of open files (RLIMIT_NOFILE) to <N>. If 0 is given, nghttpx does not set the limit.

Default: 0

  • --rlimit-memlock=<N> โ€” set the maximum number of bytes of memory that may be locked into RAM. If 0 is given, nghttpx does not set the limit.

Default: 0

  • --backend-request-buffer=<SIZE> โ€” set the buffer size used to store the backend request.

Default: 16K

  • --backend-response-buffer=<SIZE> โ€” set the buffer size used to store the backend response.

Default: 128K

  • --fastopen=<N> โ€” enables TCP Fast Open for the listening socket and limits the maximum length for the queue of connections that have not yet completed the three-way handshake. If the value is 0, then fast open is disabled.

Default: 0

  • --no-kqueue โ€” don't use kqueue. This option is only applicable on platforms that have kqueue. For other platforms, this option is simply ignored. lowkey a no-op for most of y'all. ๐Ÿคท

โฐ TIMEOUT โฐ

  • --frontend-http2-idle-timeout=<DURATION> โ€” specify the idle timeout for an HTTP/2 frontend connection. If no active streams exist for this duration, the connection gets closed. bye ๐Ÿ‘‹

Default: 3m

  • --frontend-http3-idle-timeout=<DURATION> โ€” how long an HTTP/3 frontend connection can just sit there with literally zero active streams before we're like "okay bestie, bye" and close it. if nothing's streaming for that long, it's gone. ๐Ÿ’…

Default: 3m

  • --frontend-write-timeout=<DURATION> โ€” write timeout for every single frontend connection. no cap.

Default: 30s

  • --frontend-keep-alive-timeout=<DURATION> โ€” the keep-alive timeout for frontend HTTP/1 connections. the way it just hangs out waiting for the next requestโ€ฆ ๐Ÿฆ‹

Default: 1m

  • --frontend-header-timeout=<DURATION> โ€” how long the server will wait for an HTTP request's header fields to arrive completely. timeout hits? for HTTP/1 and HTTP/2 the whole connection gets closed ๐Ÿ˜ญ. for HTTP/3 it's gentler โ€” the stream gets shut down, but the connection itself is left intact. she's fine, it's just the stream, periodt.

Default: 1m

  • --frontend-stream-read-timeout=<DURATION> โ€” read timeout for HTTP/2 and HTTP/3 streams on the frontend connection. 0 means no timeout at all โ€” no clock, no pressure, main character energy.

Default: 0

  • --frontend-stream-write-timeout=<DURATION> โ€” write timeout for HTTP/2 and HTTP/3 streams on the frontend connection. 0 means no timeout.

Default: 1m

  • --backend-stream-read-timeout=<DURATION> โ€” read timeout for HTTP/2 streams on the backend connection. 0 means no timeout.

Default: 0

  • --backend-stream-write-timeout=<DURATION> โ€” write timeout for HTTP/2 streams on the backend connection. 0 means no timeout.

Default: 1m

  • --backend-read-timeout=<DURATION> โ€” read timeout for the backend connection, full stop.

Default: 1m

  • --backend-write-timeout=<DURATION> โ€” write timeout for the backend connection.

Default: 30s

  • --backend-connect-timeout=<DURATION> โ€” how long we'll wait before we've established a TCP connection to the backend. after that? not our problem.

Default: 30s

  • --backend-keep-alive-timeout=<DURATION> โ€” keep-alive timeout for the backend HTTP/1 connection.

Default: 2s

  • --listener-disable-timeout=<DURATION> โ€” okay so if accepting a connection fails, the connection listener gets disabled for this amount of time. specifying 0 disables this feature entirely. ๐Ÿ™ˆ

Default: 30s

  • --frontend-http2-setting-timeout=<DURATION> โ€” how long we wait for a SETTINGS ACK to come back from the client before we're likeโ€ฆ hello?? ๐Ÿ“ฎ

Default: 10s

  • --backend-http2-settings-timeout=<DURATION> โ€” same vibe, different direction: how long we wait for SETTINGS ACK from the backend server.

Default: 10s

  • --backend-max-backoff=<DURATION> โ€” the maximum backoff interval. this gets used when doing a health check against an offline backend (peep the fail parameter in --backend option). it's also used to cap the max interval we temporarily disable a backend for when nghttpx failed to connect to it. these intervals are calculated with exponential backoff, and consecutive failed attempts increase the interval โ€” this option just caps its maximum value so it doesn't spiral into infinity. ๐Ÿ”ฅ

Default: 2m

  • --frontend-min-write-rate=<SIZE> โ€” sets the minimum rate of stream data the client has to actually read from the frontend connection. <SIZE> is the number of bytes per second. if the client can't read at least that rate, the connection gets closed. the implementation extends the timeout by the stream data to send. use --frontend-initial-write-rate-timeout to specify the initial timeout. use --frontend-max-write-rate-timeout to set the maximum timeout. setting 0 disables this feature.

Default: 0

  • --frontend-initial-write-rate-timeout=<DURATION> โ€” sets the initial timeout used by the --frontend-min-write-rate feature.

Default: 10s

  • --frontend-max-write-rate-timeout=<DURATION> โ€” sets the maximum timeout used by the --frontend-min-write-rate feature.

Default: 1m


๐Ÿ” SSL/TLS โœจ๐Ÿ’–

  • --ciphers=<SUITE> โ€” sets the allowed cipher list for frontend connections. the format of the string is described in OpenSSL ciphers(1). this option sets cipher suites for TLSv1.2 โ€” for TLSv1.3 use --tls13-ciphers instead, iykyk.

Default: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

  • --tls13-ciphers=<SUITE> โ€” sets the allowed cipher list for frontend connections too, but for the TLSv1.3 girlies. format's still in OpenSSL ciphers(1). this one is TLSv1.3 only โ€” for TLSv1.2 use --ciphers.

Default: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

  • --client-ciphers=<SUITE> โ€” sets the allowed cipher list for backend connections. format described in OpenSSL ciphers(1). this sets cipher suites for TLSv1.2 โ€” for TLSv1.3 use --tls13-client-ciphers.

Default: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

  • --tls13-client-ciphers=<SUITE> โ€” sets the allowed cipher list for backend connections, TLSv1.3 edition. format in OpenSSL ciphers(1). for TLSv1.2 use --client-ciphers.

Default: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

  • --groups=<LIST> โ€” sets the supported group list for frontend connections. <LIST> is a colon separated list of group NID or names, in preference order (most wanted first ๐Ÿ’…). which curves are actually supported depends on the linked OpenSSL library. heads up: this function requires OpenSSL >= 1.0.2.

Default: X25519:P-256:P-384:P-521

  • -k, --insecure โ€” don't verify the backend server's certificate if TLS is enabled for backend connections. living dangerously, no cap.
  • --cacert=<PATH> โ€” sets the path to the trusted CA certificate file. it's used in backend TLS connections to verify the peer's certificate. the file must be in PEM format and it can contain multiple certificates. if the linked OpenSSL is configured to load system wide certificates, those get loaded at startup regardless of this option.
  • --private-key-passwd-file=<PATH> โ€” path to the file that contains the password for the server's private key. if none is given and the private key is password protected, it'll be requested interactively. ๐Ÿ‘€
  • --subcert=<KEYPATH>:<CERTPATH>[[;<PARAM>]...] โ€” specify an additional certificate and private key file. nghttpx will choose certificates based on the hostname the client indicates using the TLS SNI extension. if nghttpx is built with OpenSSL >= 1.0.2, the signature algorithms (e.g., ECDSA+SHA256) presented by the client are also taken into consideration. this lets nghttpx send an ML-DSA or ECDSA certificate to modern clients, while sending an RSA based certificate to older clients. this option can be used multiple times. ๐ŸŽ€

An additional parameter can be specified in <PARAM>. the available <PARAM> is sct-dir=<DIR>.

sct-dir=<DIR> specifies the path to the directory which contains *.sct files for the TLS signed_certificate_timestamp extension (RFC 6962). this feature requires OpenSSL >= 1.0.2. see also the --tls-sct-dir option.

  • --dh-param-file=<PATH> โ€” path to the file that contains DH parameters in PEM format. without this option, DHE cipher suites are not available. fr fr.
  • --alpn-list=<LIST> โ€” a comma delimited list of ALPN protocol identifiers, sorted in order of preference โ€” meaning the most desirable protocol comes first. the parameter must be delimited by a single comma only, and any white spaces are treated as part of the protocol string. so no sneaky spaces, bestie.

Default: h2,http/1.1

  • --verify-client โ€” require and verify the client certificate. no certificate, no entry. ๐Ÿ‘‘
  • --verify-client-cacert=<PATH> โ€” path to the file that contains CA certificates used to verify the client certificate. the file must be in PEM format and it can contain multiple certificates.
  • --verify-client-tolerate-expired โ€” accept an expired client certificate. โš ๏ธ the operator should handle the expired client certificate by some means (e.g., mruby script). otherwise, this option might cause a security risk. so like, be aware.
  • --client-private-key-file=<PATH> โ€” path to the file that contains the client private key used in backend client authentication.
  • --client-cert-file=<PATH> โ€” path to the file that contains the client certificate used in backend client authentication.
  • --tls-min-proto-version=<VER> โ€” specify the minimum SSL/TLS protocol. name matching is done in a case-insensitive manner. the versions between --tls-min-proto-version and --tls-max-proto-version are enabled. if the protocol list advertised by the client does not overlap this range, you'll get the error message "unknown protocol". ๐Ÿ˜ญ the available versions are: TLSv1.3 and TLSv1.2.

Default: TLSv1.2

  • --tls-max-proto-version=<VER> โ€” specify the maximum SSL/TLS protocol. also case-insensitive matching. the versions between --tls-min-proto-version and --tls-max-proto-version are enabled. if the protocol list advertised by the client doesn't overlap this range, you'll receive the error message "unknown protocol". the available versions are: TLSv1.3 and TLSv1.2.

Default: TLSv1.3

  • --tls-ticket-key-file=<PATH> โ€” ok so this is the path to a file full of random data that we use to build TLS session ticket parameters ๐ŸŽŸ๏ธ If aes-128-cbc is what you gave to --tls-ticket-key-cipher, the file has to contain exactly 48 bytes. If it's aes-256-cbc over there, the file has to contain exactly 80 bytes. You can pass this option over and over to specify a whole little collection of ticket parameters ๐Ÿ’… And here's the tea: when several files are given, only the first key is used to encrypt TLS session tickets. The other keys get accepted, but the server will issue new session tickets with the first key. That's literally the whole point โ€” it lets you do session key rotation ๐Ÿ”„ Please note that key rotation does not occur automatically. You, the human, have to rearrange files or change the option values and then restart nghttpx gracefully. If opening or reading a given file fails, all loaded keys are discarded and it's treated as if you never passed this option at all ๐Ÿ’€ And if you don't give this option, or an error happened while opening/reading a file, a key is generated internally every 1 hour and those keys are valid for 12 hours. That's the recommended path if you don't need ticket key sharing between nghttpx instances โœจ
  • --tls-ticket-key-memcached=<HOST>,<PORT>[;tls] โ€” this says where the memcached server lives so we can grab TLS ticket keys for session resumption ๐Ÿงบ It turns on shared TLS ticket keys across multiple nghttpx instances. Heads up though: nghttpx does not set TLS ticket keys to memcached โ€” you need an external ticket key generator for that. nghttpx only gets TLS ticket keys from memcached and uses them, possibly replacing the whole current set of keys ๐Ÿ‘‘ It's up to that external generator to rotate keys frequently. Peep the "TLS SESSION TICKET RESUMPTION" section in the manual page to learn the data format used in the memcached entry ๐Ÿ“– Optionally, you can encrypt the memcached connection with TLS by adding the "tls" parameter ๐Ÿ”
  • --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6) โ€” picks the address family for the memcached connections we use to fetch TLS ticket keys. "auto" means both IPv4 and IPv6 get considered ๐ŸŒ "IPv4" means only IPv4 gets considered. "IPv6" means only IPv6 gets considered.

Default: auto

  • --tls-ticket-key-memcached-interval=<DURATION> โ€” sets how often we go get TLS ticket keys from memcached โฐ

Default: 10m

  • --tls-ticket-key-memcached-max-retry=<N> โ€” the max number of consecutive retries before we abandon a TLS ticket key retrieval ๐Ÿ˜ค If this number gets reached, the attempt counts as a failure, and the "failure" count gets incremented by 1, which feeds into the value controlled by the --tls-ticket-key-memcached-max-fail option.

Default: 3

  • --tls-ticket-key-memcached-max-fail=<N> โ€” the max number of consecutive failures before TLS ticket is disabled until the next scheduled key retrieval ๐Ÿšซ

Default: 2

  • --tls-ticket-key-cipher=<CIPHER> โ€” the cipher used to encrypt the TLS session ticket ๐Ÿ”’ Give it either aes-128-cbc or aes-256-cbc. By default, aes-128-cbc is the one โœจ
  • --tls-ticket-key-memcached-cert-file=<PATH> โ€” path to the client certificate for memcached connections that fetch TLS ticket keys ๐Ÿชช
  • --tls-ticket-key-memcached-private-key-file=<PATH> โ€” path to the client private key for memcached connections that fetch TLS ticket keys ๐Ÿ”‘
  • --tls-dyn-rec-warmup-threshold=<SIZE> โ€” the threshold size for TLS dynamic record size behaviour ๐Ÿ“ During a TLS session, once that threshold number of bytes has been written, the TLS record size gets bumped up to the maximum allowed (16K). That max record size keeps being used for the active TLS session. Then, after --tls-dyn-rec-idle-timeout has elapsed, the record size drops back down to 1300 bytes. Set it to 0 to always use the maximum record size, no matter how long the idle period was ๐Ÿ‘‘ This behaviour applies to all TLS based frontends, and TLS HTTP/2 backends.

Default: 1M

  • --tls-dyn-rec-idle-timeout=<DURATION> โ€” the timeout for the TLS dynamic record size behaviour. Go read --tls-dyn-rec-warmup-threshold for the full picture ๐Ÿ” This behaviour applies to all TLS based frontends, and TLS HTTP/2 backends.

Default: 1s

  • --no-http2-cipher-block-list โ€” allows a block listed cipher suite on the frontend HTTP/2 connection ๐Ÿ™ˆ The complete HTTP/2 cipher suites block list lives here: https://tools.ietf.org/html/rfc7540#appendix-A
  • --client-no-http2-cipher-block-list โ€” allows a block listed cipher suite on the backend HTTP/2 connection ๐Ÿ™‰ The complete HTTP/2 cipher suites block list lives here: https://tools.ietf.org/html/rfc7540#appendix-A
  • --tls-sct-dir=<DIR> โ€” tells nghttpx which directory has the *.sct files ๐Ÿ“‚ Every *.sct file in <DIR> gets read and sent as extension_data of TLS signed_certificate_timestamp (RFC 6962) to the client ๐ŸŽ€ These *.sct files belong to the certificate specified in the positional command-line argument <CERT>, or the certificate option in the configuration file. For additional certificates, use the --subcert option. Heads up: this option requires OpenSSL >= 1.0.2 โš ๏ธ
  • --psk-secrets=<PATH> โ€” reads a list of PSK identity and secrets from <PATH>. This one is for the frontend connection ๐Ÿซถ Each line of the input file is formatted as <identity>:<hex-secret>, where <identity> is the PSK identity and <hex-secret> is the secret in hex. An empty line, and any line that starts with '#', gets skipped โœจ The default enabled cipher list might not contain any PSK cipher suite at all. In that case, the desired PSK cipher suites have to be enabled using the --ciphers option. And the desired PSK cipher suite may be block listed by HTTP/2 ๐Ÿ˜ญ To use those cipher suites with HTTP/2, consider using the --no-http2-cipher-block-list option. But be aware of its implications.
  • --client-psk-secrets=<PATH> โ€” reads PSK identity and secrets from <PATH>. This one is for the backend connection ๐Ÿ’ซ Each line of the input file is formatted as <identity>:<hex-secret>, where <identity> is the PSK identity and <hex-secret> is the secret in hex. An empty line, and any line that starts with '#', gets skipped. Only the first identity and secret pair encountered is used ๐Ÿ‘† The default enabled cipher list might not contain any PSK cipher suite. In that case, the desired PSK cipher suites have to be enabled using the --client-ciphers option. And the desired PSK cipher suite may be block listed by HTTP/2 ๐Ÿ˜ฉ To use those cipher suites with HTTP/2, consider using the --client-no-http2-cipher-block-list option. But be aware of its implications.
  • --tls-no-postpone-early-data โ€” by default, except for QUIC connections, nghttpx postpones forwarding HTTP requests that were sent in early data โ€” including the ones sent partially in it โ€” until the TLS handshake finishes โณ If all backend servers recognize the "Early-Data" header field, using this option makes nghttpx not postpone forwarding the request and lets you get the full potential of 0-RTT data ๐Ÿ”ฅ
  • --tls-max-early-data=<SIZE> โ€” sets the maximum amount of 0-RTT data that the server will accept ๐Ÿ“ฅ

Default: 16K

  • --tls-ktls โ€” enable ktls ๐Ÿ’…
  • --ech-config-file=<PATH> โ€” read the Encrypted Client Hello (ECH) server configuration from <PATH> ๐Ÿ•ต๏ธ See --ech-retry-config-file for the details.
  • --ech-retry-config-file=<PATH> โ€” this option and the --ech-config-file option both read Encrypted Client Hello (ECH) server configuration from <PATH> ๐Ÿ” If --ech-retry-config-file is used, the configurations are included in the retry configurations. The file format must be the PEM ECH file described in RFC 9934 ๐Ÿ“œ These options can be used repeatedly to read multiple files. And --ech-retry-config-file must be used at least once when enabling ECH ๐Ÿ‘‘

๐ŸŒˆ HTTP/2 ๐ŸŒˆ

  • -c, --frontend-http2-max-concurrent-streams=<N> โ€” sets the maximum number of concurrent streams in one frontend HTTP/2 session ๐ŸŒŠ

Default: 100

  • --backend-http2-max-concurrent-streams=<N> โ€” sets the maximum number of concurrent streams in one backend HTTP/2 session ๐ŸŒŠ This sets the maximum number of concurrently opened pushed streams. The maximum number of concurrent requests is set by the remote server, so that part's not on you, bestie.

Default: 100

  • --frontend-http2-window-size=<SIZE> โ€” sets the per-stream initial window size of the HTTP/2 frontend connection ๐ŸชŸ

Default: 65535

  • --frontend-http2-connection-window-size=<SIZE> โ€” sets the per-connection window size of the HTTP/2 frontend connection ๐ŸชŸ

Default: 65535

  • --backend-http2-window-size=<SIZE> โ€” sets the initial window size of the HTTP/2 backend connection ๐ŸชŸ

Default: 65535

  • --backend-http2-connection-window-size=<SIZE> โ€” sets the per-connection window size of the HTTP/2 backend connection ๐ŸชŸ

Default: 2147483647

  • --http2-no-cookie-crumbling โ€” don't crumble the cookie header field ๐Ÿช
  • --padding=<N> โ€” add at most <N> bytes to a HTTP/2 frame payload as padding ๐Ÿงธ Specify 0 to disable padding. This option is meant for debugging purpose and is not intended to enhance protocol security โš ๏ธ
  • --no-server-push โ€” disable HTTP/2 server push ๐Ÿšซ๐Ÿ“ค Server push is supported by default mode and by HTTP/2 frontend via the Link header field. It's also supported if both frontend and backend are HTTP/2 in default mode. In that case, server push from the backend session is relayed to the frontend, and server push via the Link header field is also supported โœจ
  • --frontend-http2-optimize-write-buffer-size โ€” (experimental, bestie, we're in the lab ๐Ÿงช) turns on write buffer size optimization for the frontend HTTP/2 TLS connection. The whole point of this glow-up is shrinking the write buffer so it only holds bytes that can literally be sent right now. Because of that, the server is more responsive to prioritized HTTP/2 streams โ€” the buffering of those lower-priority streams gets cut down. Heads up tho: this one only does anything on recent Linux platforms. ๐Ÿ’…
  • --frontend-http2-optimize-window-size โ€” (experimental again ๐Ÿงช) auto-tunes the connection-level window size for the frontend HTTP/2 TLS connection. Flip this on and the connection window size starts at the default, which is 65535 bytes, and then nghttpx adjusts it on its own based on the TCP receiving window size. The max window size gets capped by whatever you set with --frontend-http2-connection-window-size. And because a stream is subject to the stream-level window size, you should also tune things with --frontend-http2-window-size. Same deal as the one above: recent Linux platforms only. ๐Ÿง
  • --frontend-http2-encoder-dynamic-table-size=<SIZE> โ€” sets the max dynamic table size of the HPACK encoder on the frontend HTTP/2 connection. The decoder โ€” that's the client โ€” says what max dynamic table size it'll accept, and then the negotiated dynamic table size is the minimum of this option's value and whatever the client specified. Teamwork, lowkey. ๐Ÿซถ

Default: 4K

  • --frontend-http2-decoder-dynamic-table-size=<SIZE> โ€” sets the max dynamic table size of the HPACK decoder on the frontend HTTP/2 connection. ๐Ÿ’–

Default: 4K

  • --backend-http2-encoder-dynamic-table-size=<SIZE> โ€” sets the max dynamic table size of the HPACK encoder on the backend HTTP/2 connection. The decoder โ€” the backend this time โ€” reports the max dynamic table size it accepts, and the negotiated dynamic table size is the minimum of this option's value and the value the backend specified. โœจ

Default: 4K

  • --backend-http2-decoder-dynamic-table-size=<SIZE> โ€” sets the max dynamic table size of the HPACK decoder on the backend HTTP/2 connection. ๐Ÿฆ‹

Default: 4K


๐ŸŒˆ Mode ๐ŸŒˆ

  • (default mode) โ€” this is the no-flag-needed vibe. It accepts HTTP/2 and HTTP/1.1 over SSL/TLS. If you pass the "no-tls" parameter to the --frontend option, it accepts HTTP/2 and HTTP/1.1 over cleartext TCP instead. And the incoming HTTP/1.1 connection can be upgraded to HTTP/2 through HTTP Upgrade. glow up in real time, fr fr โœจ
  • -s, --http2-proxy โ€” same energy as default mode, but with forward proxy enabled. This is the so-called HTTP/2 proxy mode. ๐Ÿ‘‘

๐Ÿ’œ Logging ๐Ÿ’œ

  • -L, --log-level=<LEVEL> โ€” sets the severity level of log output. <LEVEL> has to be one of INFO, NOTICE, WARN, ERROR, or FATAL. No improvising, bestie, it's a closed set. ๐ŸŽ€

Default: NOTICE

  • --accesslog-file=<PATH> โ€” sets the path to write the access log to. Wanna reopen the file? Send a USR1 signal to nghttpx. ๐Ÿ’Œ
  • --accesslog-syslog โ€” sends the access log to syslog. If you use this, the --accesslog-file option is ignored. One or the other, babe. ๐Ÿซถ
  • --accesslog-format=<FORMAT> โ€” sets the format string for the access log. The default format is combined format. Here are all the variables you can use, and yes, we're listing every single one because they all slay:
    • $remote_addr: client IP address.
    • $time_local: local time in Common Log format.
    • $time_iso8601: local time in ISO 8601 format.
    • $request: HTTP request line.
    • $status: HTTP response status code.
    • $body_bytes_sent: the number of bytes sent to client as response body.
    • $http_<VAR>: value of HTTP request header <VAR> where '_' in <VAR> is replaced with '-'.
    • $remote_port: client port.
    • $server_port: server port.
    • $request_time: request processing time in seconds with milliseconds resolution.
    • $pid: PID of the running process.
    • $alpn: ALPN identifier of the protocol which generates the response. For HTTP/1, ALPN is always http/1.1, regardless of minor version.
    • $tls_cipher: cipher used for SSL/TLS connection.
    • $tls_client_fingerprint_sha256: SHA-256 fingerprint of client certificate.
    • $tls_client_fingerprint_sha1: SHA-1 fingerprint of client certificate.
    • $tls_client_subject_name: subject name in client certificate.
    • $tls_client_issuer_name: issuer name in client certificate.
    • $tls_client_serial: serial number in client certificate.
    • $tls_protocol: protocol for SSL/TLS connection.
    • $tls_session_id: session ID for SSL/TLS connection.
    • $tls_session_reused: "r" if SSL/TLS session was reused. Otherwise, "."
    • $tls_sni: SNI server name for SSL/TLS connection.
    • $tls_ech_accepted: "e" if ECH was accepted in SSL/TLS session. Otherwise, "."
    • $backend_host: backend host used to fulfill the request. "-" if backend host is not available.
    • $backend_port: backend port used to fulfill the request. "-" if backend host is not available.
    • $method: HTTP method
    • $path: Request path including query. For CONNECT request, authority is recorded.
    • $path_without_query: $path up to the first '?' character. For CONNECT request, authority is recorded.
    • $protocol_version: HTTP version (e.g., HTTP/1.1, HTTP/2)

And a little pro tip for disambiguation: you can wrap any variable in "{" and "}", like ${remote_addr}. iykyk ๐Ÿ’…

Default: $remote_addr - - [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"

  • --accesslog-write-early โ€” writes the access log when the response header fields come in from the backend, instead of waiting for the whole request transaction to finish. Early bird energy. ๐Ÿฆ
  • --errorlog-file=<PATH> โ€” sets the path to write the error log to. To reopen the file, send a USR1 signal to nghttpx. Also, stderr gets redirected to the error log file unless --errorlog-syslog is used. ๐Ÿ“

Default: /dev/stderr

  • --errorlog-syslog โ€” sends the error log to syslog. If you use this, the --errorlog-file option is ignored. ๐Ÿ˜ญ
  • --syslog-facility=<FACILITY> โ€” sets the syslog facility to <FACILITY>.

Default: daemon


๐ŸŒธ HTTP ๐ŸŒธ

  • --add-x-forwarded-for โ€” appends the X-Forwarded-For header field to the downstream request. ๐Ÿ’Œ
  • --strip-incoming-x-forwarded-for โ€” strips the X-Forwarded-For header field from inbound client requests. bye bye โœ‚๏ธ
  • --no-add-x-forwarded-proto โ€” don't append the additional X-Forwarded-Proto header field to the backend request. And if the inbound client sets X-Forwarded-Proto, and the --no-strip-incoming-x-forwarded-proto option is used, those get passed through to the backend. ๐Ÿซถ
  • --no-strip-incoming-x-forwarded-proto โ€” don't strip the X-Forwarded-Proto header field from inbound client requests. Let it live its truth. โœจ
  • --add-forwarded=<LIST> โ€” appends an RFC 7239 Forwarded header field with the parameters you give in the comma delimited list <LIST>. The supported parameters are "by", "for", "host", and "proto". By default, the values of the "by" and "for" parameters are obfuscated strings โ€” check --forwarded-by and --forwarded-for respectively for that. And important note: nghttpx does not translate non-standard X-Forwarded-* header fields into the Forwarded header field, and vice versa. No crossover episodes, bestie. ๐Ÿ™ˆ
  • --strip-incoming-forwarded โ€” strips the Forwarded header field from inbound client requests. ๐Ÿงน
  • --forwarded-by=(obfuscated|ip|<VALUE>) โ€” sets the parameter value sent out with the "by" parameter of the Forwarded header field. If "obfuscated" is given, the string is randomly generated at startup. If "ip" is given, the interface address of the connection โ€” port number included โ€” is sent with the "by" parameter. In the case of a UNIX domain socket, "localhost" is used instead of the address and port. You can also specify a static obfuscated string, but the limitation is that it must start with "_", and it can only consist of the character set [A-Za-z0-9._-], as described in RFC 7239. Rules are rules. ๐ŸŽ€

Default: obfuscated

  • --forwarded-for=(obfuscated|ip) โ€” sets the parameter value sent out with the "for" parameter of the Forwarded header field. If "obfuscated" is given, the string is randomly generated for each client connection. If "ip" is given, the remote client address of the connection โ€” port number not included this time โ€” is sent with the "for" parameter. In the case of a UNIX domain socket, "localhost" is used instead of the address. ๐Ÿ’œ

Default: obfuscated

  • --no-via โ€” don't append to the Via header field. And if a Via header field is received, it's left unaltered. Touch nothing, break nothing. ๐Ÿ’…
  • --no-strip-incoming-early-data โ€” don't strip the Early-Data header field from inbound client requests. โญ
  • --no-location-rewrite โ€” don't rewrite the location header field in default mode. When --http2-proxy is used, the location header field won't be altered regardless of this option โ€” it's just not that girl in proxy mode. ๐Ÿฆ‹
  • --host-rewrite โ€” rewrites the host and :authority header fields in default mode. When --http2-proxy is used, these headers won't be altered regardless of this option. ๐ŸŽ€
  • --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]> โ€” specifies the protocol ID, port, host and origin of an alternative service. <HOST>, <ORIGIN> and <PARAMS> are optional. Empty <HOST> and <ORIGIN> are allowed, and they're treated as if nothing was specified. They get advertised in the alt-svc header field only in the HTTP/1.1 frontend. You can use this option multiple times to specify multiple alternative services. Example: --altsvc="h2,443,,,ma=3600; persist=1" โœจ
  • --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]> โ€” just like the --altsvc option, but this altsvc is only sent in the HTTP/2 frontend. Same slay, different protocol. ๐Ÿ”ฅ
  • --add-request-header=<HEADER> โ€” bestie, this adds one more header field to the request header set. The field name must be lowercase (no shouting, we're not that kind of girl). This only appends โ€” it will NOT overwrite anything already sitting there. Use it as many times as you want to stack multiple header fields. Example: --add-request-header="foo: bar"
  • --add-response-header=<HEADER> โ€” same energy, but for the response header set. Field name must be lowercase. It appends only and won't replace anything already set. Repeat it as many times as you need for multiple header fields. Example: --add-response-header="foo: bar"
  • --request-header-field-buffer=<SIZE> โ€” the max buffer size for the incoming HTTP request header field list. Heads up: this counts the header name plus the value in bytes. And if trailer fields exist? They count towards this number too. ๐Ÿ™ˆ

Default: 64K

  • --max-request-header-fields=<N> โ€” caps how many incoming HTTP request header fields you'll accept. Trailer fields, if they exist, are counted here too.

Default: 100

  • --response-header-field-buffer=<SIZE> โ€” the max buffer size for the incoming HTTP response header field list. Sum of header name and value in bytes, and yes, trailer fields count towards it as well.

Default: 64K

  • --max-response-header-fields=<N> โ€” the maximum number of incoming HTTP response header fields. Trailer fields existing? Counted here too, no exceptions.

Default: 500

  • --error-page=(<CODE>|*)=<PATH> โ€” sets the file path to a custom error page that gets served when nghttpx originally generates an HTTP error status code of <CODE>. That <CODE> has to be greater than or equal to 400, and at most 599. Slap in * instead of <CODE> and it matches all HTTP status codes. ๐ŸŽ€ One big caveat tho: if the error status code is coming from a backend server, these custom error pages are not used. Periodt.
  • --server-name=<NAME> โ€” changes the server response header field value to <NAME>.

Default: nghttpx

  • --no-server-rewrite โ€” tells it to NOT rewrite the server header field in default mode. And when --http2-proxy is in play, those headers won't be altered regardless of this option, so don't even try it. ๐Ÿ’…
  • --redirect-https-port=<PORT> โ€” picks the port number that shows up in the Location header field when a redirect to an HTTPS URI happens because of the "redirect-if-not-tls" parameter in the --backend option.

Default: 443

  • --require-http-scheme โ€” always requires http or https scheme in the HTTP request. It also requires that https scheme must be used for an encrypted connection, and otherwise http scheme must be used. This option is recommended for a server deployment that directly faces clients and whose services only require the http or https scheme. ๐Ÿ”’

โœจ API โœจ

  • --api-max-request-body=<SIZE> โ€” sets the maximum size of the request body for API requests.

Default: 32M

๐ŸŒˆ DNS ๐ŸŒˆ

  • --dns-cache-timeout=<DURATION> โ€” how long cached DNS results stay valid. Lowkey important: nghttpx caches the unsuccessful results too. ๐Ÿ˜ญ

Default: 10s

  • --dns-lookup-timeout=<DURATION> โ€” the timeout the DNS server gets to respond to the initial DNS query. For the 2nd and later queries, the server gets time based on this timeout, and it's scaled linearly. It's giving escalator energy.

Default: 250ms

  • --dns-max-try=<N> โ€” how many DNS queries happen before nghttpx gives up on the name lookup. ๐Ÿซถ

Default: 3

  • --frontend-max-requests=<N> โ€” the number of requests that a single frontend connection can process. For HTTP/2, this is the number of streams in one HTTP/2 connection. For HTTP/1, it's the number of keep alive requests. Heads up, this is just a hint to nghttpx, so it may allow a few extra requests. ๐ŸŒธ

The default value is unlimited.

๐Ÿ› Debug ๐Ÿ›

  • --frontend-http2-dump-request-header=<PATH> โ€” dumps request headers received by the HTTP/2 frontend to the file at <PATH>. The output is in HTTP/1 header field format and each header block is followed by an empty line. โš ๏ธ This option is not thread safe and MUST NOT be used with option -n<N>, where <N> >= 2.
  • --frontend-http2-dump-response-header=<PATH> โ€” dumps response headers sent from the HTTP/2 frontend to the file at <PATH>. Output's in HTTP/1 header field format, each header block followed by an empty line. โš ๏ธ Also not thread safe and MUST NOT be used with option -n<N>, where <N> >= 2.
  • -o, --frontend-frame-debug โ€” prints HTTP/2 frames in the frontend to stderr. โš ๏ธ Not thread safe and MUST NOT be used with option -n=N, where N >= 2.

๐Ÿ’… Process ๐Ÿ’…

  • -D, --daemon โ€” runs in the background. And if you use -D, the current working directory gets changed to /. Iconic detached queen behaviour. ๐Ÿ‘‘
  • --pid-file=<PATH> โ€” sets the path to save the PID of this program.
  • --user=<USER> โ€” runs this program as <USER>. This option is intended to be used to drop root privileges.
  • --single-process โ€” runs this program in a single process mode for debugging purposes. Without this option, nghttpx creates at least 2 processes: main and worker processes. With this option, main and worker are unified into a single process. nghttpx still spawns an additional process if neverbleed is used. Also just so you know: in single process mode, the signal handling feature is disabled. ๐Ÿ’€
  • --max-worker-processes=<N> โ€” the maximum number of worker processes. nghttpx spawns a new worker process when it reloads its configuration. The previous worker process enters a graceful termination period and will terminate when it finishes handling the existing connections. BUT โ€” if reloading configurations happens very frequently, the worker processes might get piled up if they take a bit long to finish the existing connections. With this option, if the number of worker processes exceeds the given value, the oldest worker process is terminated immediately. Specifying 0 means no limit, and that's the default behaviour. ๐Ÿซถ
  • --worker-process-grace-shutdown-period=<DURATION> โ€” the maximum period for a worker process to terminate gracefully. When a worker process enters a graceful shutdown period (e.g., when nghttpx reloads its configuration) and it doesn't finish handling the existing connections in the given period of time, it gets terminated immediately. Specifying 0 means no limit, and that's the default behaviour.

๐Ÿงš Scripting ๐Ÿงš

  • --mruby-file=<PATH> โ€” sets the mruby script file.
  • --ignore-per-pattern-mruby-error โ€” ignores mruby compile errors for a per-pattern mruby script file. If an error occurred, it's treated as if no mruby file were specified for the pattern. No drama, we just move on. โœจ

๐Ÿ”ฅ HTTP/3 and QUIC ๐Ÿ”ฅ

  • --frontend-quic-idle-timeout=<DURATION> โ€” specifies an idle timeout for a QUIC connection.

Default: 30s

  • --frontend-quic-debug-log โ€” outputs QUIC debug log to /dev/stderr. ๐Ÿ›
  • --quic-bpf-program-file=<PATH> โ€” specifies a path to an eBPF program file reuseport_kern.o to direct an incoming QUIC UDP datagram to the correct socket.

Default: /usr/local/lib/nghttp2/reuseport_kern.o

  • --frontend-quic-early-data โ€” enables early data on frontend QUIC connections. nghttpx sends the "Early-Data" header field to a backend server if a request is received in early data and the handshake has not finished. โš ๏ธ All backend servers should deal with possibly replayed requests.
  • --frontend-quic-qlog-dir=<DIR> โ€” specifies a directory where a qlog file is written for frontend QUIC connections. A qlog file is created per each QUIC connection. The file name is ISO8601 basic format, followed by "-", the server Source Connection ID, and ".sqlog". ๐Ÿ“
  • --frontend-quic-require-token โ€” requires an address validation token for a frontend QUIC connection. The server sends a token in a Retry packet or a NEW_TOKEN frame in the previous connection.
  • --frontend-quic-congestion-controller=<CC> โ€” specifies a congestion controller algorithm for a frontend QUIC connection. <CC> should be either "cubic" or "bbr".

Default: cubic

  • --frontend-quic-secret-file=<PATH> โ€” path to a file that contains secure random data used as QUIC keying materials. It's used to derive keys for encrypting tokens and Connection IDs. It is not used to encrypt QUIC packets. Each line of this file must contain exactly 136 bytes of hex-encoded string (which, when decoded, is a byte string 68 bytes long). The first 3 bits of the decoded byte string are used to identify the keying material. An empty line, or a line that starts '#', is ignored. The file can contain more than one keying material. Because the identifier is 3 bits, at most 8 keying materials are read and the remaining data is discarded. The first keying material in the file is primarily used for encryption and decryption for a new connection. The other ones are used to decrypt data for the existing connections. Specifying multiple keying materials enables key rotation. ๐ŸŽ€ Please note that key rotation does not occur automatically. The user should update files or change option values and restart nghttpx gracefully. If opening or reading the given file fails, all loaded keying materials are discarded and it's treated as if none of this option was given. If this option is not given, or an error occurred while opening or reading a file, a keying material is generated internally on startup and reload.
  • --quic-server-id=<HEXSTRING> โ€” specifies the server ID encoded in the Connection ID to identify this particular server instance. The Connection ID is encrypted and this part is not visible in public. It must be 4 bytes long and must be encoded in a hex string (which is 8 bytes long). If this option is omitted, a random server ID is generated on startup and configuration reload. ๐Ÿฆ‹
  • --frontend-quic-initial-rtt=<DURATION> โ€” sets the initial RTT of the frontend QUIC connection, bestie โœจ

Default: 333ms

  • --no-quic-bpf โ€” turns eBPF off. That's it, that's the whole job ๐Ÿ’…
  • --frontend-http3-window-size=<SIZE> โ€” sets the per-stream initial window size for the HTTP/3 frontend connection ๐Ÿฆ‹

Default: 256K

  • --frontend-http3-connection-window-size=<SIZE> โ€” sets the per-connection window size for the HTTP/3 frontend connection ๐ŸŒธ

Default: 1M

  • --frontend-http3-max-window-size=<SIZE> โ€” sets the maximum per-stream window size for the HTTP/3 frontend connection. The window size gets adjusted based on the receiving rate of stream data. The initial value is whatever you gave to --frontend-http3-window-size, and the window grows up to <SIZE> bytes ๐Ÿ“ˆ

Default: 6M

  • --frontend-http3-max-connection-window-size=<SIZE> โ€” sets the maximum per-connection window size for the HTTP/3 frontend connection. Same deal: the window size adjusts based on the receiving rate of stream data, it starts at the value from --frontend-http3-connection-window-size, and it grows up to <SIZE> bytes ๐ŸŒˆ

Default: 8M

  • --frontend-http3-max-concurrent-streams=<N> โ€” sets the max number of concurrent streams allowed in one frontend HTTP/3 connection ๐Ÿ‘‘

Default: 100

๐ŸŒŸ Misc ๐ŸŒŸ

  • --conf=<PATH> โ€” load the configuration from <PATH>. Quick heads up though: if you don't give --conf, nghttpx always tries to read the default configuration file anyway ๐Ÿ™ˆ

Default: /etc/nghttpx/nghttpx.conf

  • --include=<PATH> โ€” load extra configs from <PATH>. The file <PATH> gets read right when the configuration parser stumbles onto this option. You can use this one multiple times, or even recursively, no cap ๐ŸŒ€
  • -v, --version โ€” print the version and dip. bye bestie ๐Ÿ‘‹
  • -h, --help โ€” print this help and exit ๐Ÿ’–

The <SIZE> argument is an integer plus an optional unit (so like, 10K means 10 * 1024). The units are K, M and G (all powers of 1024, slay).

The <DURATION> argument is an integer plus an optional unit (so 1s is 1 second, 500ms is 500 milliseconds). The units are h, m, s or ms (hours, minutes, seconds and milliseconds, respectively). If you skip the unit, a second is used as the unit โฑ๏ธ


๐Ÿ“ FILES ๐Ÿ“

  • /etc/nghttpx/nghttpx.conf โ€” the default config file path nghttpx searches at startup. You can change the config file path with the --conf option.

Any lines starting with # are treated as comments ๐Ÿ’ฌ

The option name in the config file is the long command-line option name with the leading -- stripped off (so like, frontend). Put = between the option name and the value. And do NOT add extra leading or trailing spaces, okay? ๐Ÿซถ

When you're specifying arguments that contain characters a shell finds special, we usually use quotes so the shell doesn't interpret them. When you're writing this config file though, those quotes must not be used. For example, to specify an additional request header field, you do this:

add-request-header=foo: bar

instead of:

add-request-header="foo: bar"

The options that don't take an argument on the command line do take an argument in the config file. Specify yes as the argument (e.g., http2-proxy=yes). If you give any other string, it just gets ignored ๐Ÿ’€

To specify the private key and certificate file โ€” the ones given as positional arguments on the command line โ€” use private-key-file and certificate-file.

The --conf option cannot be used in the config file, and it will be ignored if you specify it there. periodt ๐Ÿšซ

Error log

The error log is written to stderr by default. You can configure it with --errorlog-file. The format of a log message is like this:

<datetime> <main-pid> <current-pid> <thread-id> <level> (<filename>:<line>) <msg>
  • <datetime> โ€” a combination of the date and time when the log was written. It's in ISO 8601 format ๐Ÿ•
  • <main-pid> โ€” the main process ID.
  • <current-pid> โ€” the process ID that wrote this log.
  • <thread-id> โ€” the thread ID that wrote this log. It would be unique within <current-pid>.
  • <filename> and <line> โ€” the source file name and line number that produced this log.
  • <msg> โ€” the log message body.

๐Ÿšจ SIGNALS ๐Ÿšจ

  • SIGQUIT โ€” shut down gracefully. First it accepts pending connections and stops accepting new ones. Once all connections are handled, nghttpx exits ๐ŸŒ™
  • SIGHUP โ€” reload the configuration file given in --conf ๐Ÿ”„
  • SIGUSR1 โ€” reopen the log files ๐Ÿ“‚
  • SIGUSR2 โ€” fork and execute nghttpx. It'll execute the binary at the same path with the same command-line arguments and environment variables. As of nghttpx version 1.20.0, the new main process sends SIGQUIT to the original main process when it's ready to serve requests. For earlier versions of nghttpx, you have to send SIGQUIT to the original main process yourself ๐Ÿ˜ญ

The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that the former is usually used to execute a new binary, and the main process is newly spawned. The latter, on the other hand, just reloads the configuration file, and the same main process keeps existing โœจ

Note: nghttpx consists of multiple processes: one process for processing these signals, and another one for processing requests. The former spawns the latter. The former is called the main process, and the latter is called the worker process. If neverbleed is enabled, the worker process spawns a neverbleed daemon process that does the RSA key processing. The above signals must be sent to the main process. If any of the other processes receive one of them, it's ignored. This behaviour of these processes may change in a future release. In other words, in a future release, processes other than the main process may terminate upon receiving these signals. So these signals should not be sent to processes other than the main process ๐Ÿ’€

๐Ÿš€ SERVER PUSH ๐Ÿš€

nghttpx supports HTTP/2 server push in default mode, via the Link header field. nghttpx looks for the Link header field (RFC 5988 <http://tools.ietf.org/html/rfc5988>) in response headers from the backend server, extracts the URI-reference with the parameter rel=preload (see preload <http://w3c.github.io/preload/#interoperability-with-http-link-header>), and pushes those URIs to the frontend client. Here's a sample Link header field to get a server push going:

Link: </fonts/font.woff>; rel=preload
Link: </css/theme.css>; rel=preload

Right now, the following restriction applies to server push:

  1. The associated stream must have method "GET" or "POST". The associated stream's status code must be 200.

This limitation may be loosened in a future release ๐Ÿคž

nghttpx also supports server push if both frontend and backend are HTTP/2 in default mode. In that case, on top of server push via the Link header field, server push from the backend gets forwarded to the frontend HTTP/2 session ๐Ÿ”ฅ

HTTP/2 server push gets disabled if --http2-proxy is used ๐Ÿšซ


๐Ÿ”Œ UNIX DOMAIN SOCKET ๐Ÿ”Œ

nghttpx supports UNIX domain socket with a filename for both frontend and backend connections, iconic ๐Ÿ’…

Please note that the current nghttpx implementation does not delete a socket with a filename. And on startup, if nghttpx detects that the specified socket already exists in the filesystem, nghttpx first deletes it. However, if SIGUSR2 is used to execute a new binary and both the old and new configurations use the same filename, the new binary does not delete the socket and continues to use it. iykyk ๐Ÿซถ

๐Ÿ” TLS SESSION RESUMPTION ๐Ÿ”

nghttpx supports TLS session resumption through both session ID and session ticket โœจ

SESSION ID RESUMPTION

By default, the session ID is shared by all worker threads ๐Ÿงต

TLS SESSION TICKET RESUMPTION

By default, the session ticket is shared by all worker threads. Automatic key rotation is also enabled by default. Every hour, a new encryption key is generated, and the previous encryption key becomes a decryption-only key. We set the session timeout to 12 hours, so we keep at most 12 keys ๐Ÿ”‘

If --tls-ticket-key-memcached is given, encryption keys are retrieved from memcached. nghttpx just reads keys from memcached; you have to deploy a key generator program to update the keys frequently (like, every 1 hour). The example key generator tlsticketupdate.go is available under the contrib directory in the nghttp2 archive. The memcached entry key is nghttpx:tls-ticket-key. The data format stored in memcached is the binary format described below:

+--------------+-------+----------------+
| VERSION (4)  |LEN (2)|KEY(48 or 80) ...
+--------------+-------+----------------+
               ^                        |
               |                        |
               +------------------------+
               (LEN, KEY) pair can be repeated

All numbers in the above figure are in bytes. All integer fields are in network byte order ๐ŸŒ

The first 4-byte integer is the VERSION field, which must be 1. The 2-byte integer LEN field gives the length of the following KEY field, which contains the key. If --tls-ticket-key-cipher=aes-128-cbc is used, LEN must be 48. If --tls-ticket-key-cipher=aes-256-cbc is used, LEN must be 80. The LEN and KEY pair can be repeated multiple times to store multiple keys. The key that appears first is used as the encryption key. All the remaining keys are used as decryption only ๐Ÿ”

By default, connections to the memcached server are not encrypted. To enable encryption, use the tls keyword in the --tls-ticket-key-memcached option ๐Ÿ›ก๏ธ

If --tls-ticket-key-file is given, the encryption key is read from the given file. In this case, nghttpx does not rotate the key automatically. To rotate the key, you have to restart nghttpx (see SIGNALS) ๐Ÿ’ซ

okay so nghttpx is literally obsessed with the TLS signed_certificate_timestamp extension (that's RFC 6962 <https://tools.ietf.org/html/rfc6962>) ๐Ÿ’–

the โœจrelevantโœจ options here are --tls-sct-dir and the sct-dir parameter that lives inside --subcert. both of them take a directory, and nghttpx will read every single file whose extension is .sct under that directory. periodt.

those *.sct files are encoded as the SignedCertificateTimestamp struct described in section 3.2 of RFC 69662 <https://tools.ietf.org/html/rfc6962#section-3.2>. and bestie, this is exactly the same format that nginx-ct <https://github.com/grahamedgecombe/nginx-ct> and mod_ssl_ct <https://httpd.apache.org/docs/trunk/mod/mod_ssl_ct.html> use ๐Ÿซถ

and if you wanna actually submit certificates to log servers and pull back the SignedCertificateTimestamp struct that nghttpx can eat? that's literally what ct-submit <https://github.com/grahamedgecombe/ct-submit> is for ๐Ÿ’…


๐Ÿ”ฅ MRUBY SCRIPTING ๐Ÿ”ฅ

โš ๏ธ Warning: the mruby extension API right now is experimental and not frozen. it is subject to change in a future release. so like, don't come for me ๐Ÿ™ˆ
โš ๏ธ Warning: okay this one matters โ€” almost every string value you get back from a method or an attribute is a fresh new mruby string, which means memory allocation and copies, every time ๐Ÿ˜ญ so it's strongly recommended that you stash that return value in a local variable and use that, instead of calling the method or touching the attribute over and over. iykyk.

nghttpx lets you level up its whole capability set with mruby scripts ๐Ÿ’œ there are 2 hook points where your mruby script gets to run: the request phase and the response phase.

  • the request phase hook fires after all request header fields have been received from the client.
  • the response phase hook fires after all response header fields have been received from the backend server.

these hooks let you modify header fields, mess with common HTTP variables like authority or request path, and even return a custom response without ever forwarding the request to the backend servers ๐Ÿ‘‘

there are 2 levels of mruby script invocation: global and per-pattern.

  • the global mruby script is set by the --mruby-file option, and it's called for all requests.
  • the per-pattern mruby script is set by the "mruby" parameter in the -b option, and it's invoked for a request that matches that particular pattern.

the order of hook invocation goes like this, no cap:

  1. global request phase hook
  2. per-pattern request phase hook
  3. per-pattern response phase hook
  4. finally, global response phase hook

if a hook returns a response, any later hooks are not invoked. and heads up: the global request hook runs before pattern matching happens, so changing the request path can totally affect the pattern matching ๐Ÿฆ‹

one more thing โ€” please note that the request and response hooks of a per-pattern mruby script for a single request might not come from the same script. this can happen when a request hook is executed, the backend fails for some reason, and at the same time the backend configuration gets replaced by an API request, and then the request uses the new configuration on retry. in that case, the response hook from the new configuration, if one is specified, will be invoked. it's giving chaos but okay โœจ

all mruby scripts get evaluated once per thread on startup, and each one must instantiate an object and evaluate it as the return value (like App.new). that object is called the app object ๐Ÿ’–

  • if the app object defines an on_req method, it gets called with an Nghttpx::Env object on the request hook.
  • if the app object defines an on_resp method, it gets called with an Nghttpx::Env object on the response hook.

for each method invocation, you can access the Nghttpx::Request and Nghttpx::Response objects via Nghttpx::Env#req and Nghttpx::Env#resp respectively ๐ŸŒธ


โญ Nghttpx::REQUEST_PHASE

constant that represents the request phase. iconic, self-explanatory.

โญ Nghttpx::RESPONSE_PHASE

constant that represents the response phase. also iconic.


๐Ÿ’– class Nghttpx::Env

object that represents the current request-specific context.

attributes

  • attribute [R] req โ€” returns the Request object.
  • attribute [R] resp โ€” returns the Response object.
  • attribute [R] ctx โ€” returns a Ruby hash object. it persists until the request finishes, so any values you set in the request phase hook can be retrieved in the response phase hook ๐ŸŽ€
  • attribute [R] phase โ€” returns the current phase.
  • attribute [R] remote_addr โ€” returns the IP address of the remote client. if the connection is made via a UNIX domain socket, this returns the string "localhost".
  • attribute [R] server_addr โ€” returns the address of the server that accepted the connection. this is a string, the one you specified in --frontend, excluding the port number โ€” and not a resolved IP address. for a UNIX domain socket, this is the path to the UNIX domain socket.
  • attribute [R] server_port โ€” returns the port number of the server frontend which accepted the connection from the client.
  • attribute [R] tls_used โ€” returns true if TLS is used on the connection.
  • attribute [R] tls_sni โ€” returns the TLS SNI value that the client sent on this connection.
  • attribute [R] tls_client_fingerprint_sha256 โ€” returns the SHA-256 fingerprint of a client certificate.
  • attribute [R] tls_client_fingerprint_sha1 โ€” returns the SHA-1 fingerprint of a client certificate.
  • attribute [R] tls_client_issuer_name โ€” returns the issuer name of a client certificate.
  • attribute [R] tls_client_subject_name โ€” returns the subject name of a client certificate.
  • attribute [R] tls_client_serial โ€” returns the serial number of a client certificate.
  • attribute [R] tls_client_not_before โ€” returns the start date of a client certificate, in seconds since the epoch.
  • attribute [R] tls_client_not_after โ€” returns the end date of a client certificate, in seconds since the epoch.
  • attribute [R] tls_cipher โ€” returns the TLS cipher negotiated on this connection.
  • attribute [R] tls_protocol โ€” returns the TLS protocol version negotiated on this connection.
  • attribute [R] tls_session_id โ€” returns the session ID for this connection, as a hex string.
  • attribute [R] tls_session_reused โ€” returns true if, and only if, an SSL/TLS session is reused.
  • attribute [R] alpn โ€” returns the ALPN identifier negotiated on this connection.
  • attribute [R] tls_handshake_finished โ€” returns true if the SSL/TLS handshake has finished. if it returns false in the request phase hook, the request was received in TLSv1.3 early data (0-RTT) and might be vulnerable to the replay attack ๐Ÿ’€ nghttpx will send an Early-Data header field to backend servers to flag this.

๐Ÿ’– class Nghttpx::Request

object that represents a request from the client. heads up: you're only allowed to modify the Request object in the request phase hook.

attributes

  • attribute [R] http_version_major โ€” returns the HTTP major version.
  • attribute [R] http_version_minor โ€” returns the HTTP minor version.
  • attribute [R/W] method โ€” the HTTP method. on assignment, a copy of the given value is assigned. we don't accept arbitrary method names โ€” those will get documented later, but well-known methods like GET, PUT and POST are all supported ๐Ÿ‘‘
  • attribute [R/W] authority โ€” the authority (i.e., example.org), including the optional port component. on assignment, a copy of the given value is assigned.
  • attribute [R/W] scheme โ€” the scheme (i.e., http, https). on assignment, a copy of the given value is assigned.
  • attribute [R/W] path โ€” the request path, including the query component (i.e., /index.html). on assignment, a copy of the given value is assigned. the path does not include the authority component of the URI, but it may include the query component. nghttpx does some normalization on the path: it decodes percent-encoding for unreserved characters (see <https://tools.ietf.org/html/rfc3986#section-2.3>), and it resolves ".." and ".". but it may leave characters that should be percent-encoded as-is. so be careful when comparing a path against a desired string ๐Ÿซถ
  • attribute [R] headers โ€” returns a Ruby hash containing a copy of the request header fields. changing values in the returned hash does not change the request header fields actually used in request processing ๐Ÿ˜ญ use Nghttpx::Request#add_header or Nghttpx::Request#set_header to actually change request header fields.

methods

  • add_header(key, value) โ€” add a header entry associated with key. value can be a single string or an array of strings. it does not replace any existing values associated with key.
  • set_header(key, value) โ€” set the header entry associated with key. value can be a single string or an array of strings. it replaces any existing values associated with key.
  • clear_headers() โ€” clear all existing request header fields. bye ๐Ÿ‘‹
  • push(uri) โ€” initiate a push of the resource identified by uri. only HTTP/2 supports this feature โ€” for the other protocols, this method is a noop. uri can be an absolute URI, an absolute path, or a relative path to the current request. for absolute or relative paths, the scheme and authority are inherited from the current request. currently, the method is always GET. nghttpx will issue a request to the backend servers to fulfill this request, and the request and response phase hooks will be called for the pushed resource as well โœจ

๐Ÿ’– class Nghttpx::Response

object that represents the response from the backend server.

attributes

  • attribute [R] http_version_major โ€” returns the HTTP major version.
  • attribute [R] http_version_minor โ€” returns the HTTP minor version.
  • attribute [R/W] status โ€” the HTTP status code. it must be in the range [200, 999], inclusive. also, just so you know, the non-final status code is not supported in mruby scripting at the moment ๐Ÿ™ˆ
  • attribute [R] headers โ€” returns a Ruby hash that is a copy of your response header fields. Lowkey important: mutating the values in that returned hash does not change the response header fields that actually get used in response processing, bestie. If you wanna actually change them use Nghttpx::Response#add_header or Nghttpx::Response#set_header. Periodt. ๐Ÿ’…
  • add_header(key, value) โ€” adds a header entry under key. value</p> can be a single string or an array of strings. It does not replace any existing values already chilling under that key โ€” it just stacks another one on top. ๐ŸŽ€
  • set_header(key, value) โ€” sets a header entry under key. value</p> can be a single string or an array of strings. This one replaces any existing values associated with key. Bye bye old values ๐Ÿ‘‹
  • clear_headers() โ€” clears all existing response header fields. Total clean slate energy โœจ
  • return(body) โ€” returns a custom response body to a client. Real talk, this one changes how stuff flows:
    • Call it in the request phase hook โ†’ the request is not forwarded to the backend, and the response phase hook for this request will not be invoked.
    • Call it in the response phase hook โ†’ the response from the backend server is canceled and discarded.

Set your status code and response header fields before using this method. Status code? Use Nghttpx::Response#status. If you don't set a status code, 200 is used. Header fields? Use Nghttpx::Response#add_header and Nghttpx::Response#set_header. When you invoke this in the response phase hook, the response headers are filled with the ones received from the backend server โ€” so to send completely custom header fields, first call Nghttpx::Response#clear_headers to erase all existing header fields, then add what you need. ๐Ÿงš

Calling this method twice for a given request is an error. Once is the vibe, twice is not. ๐Ÿ™ˆ
  • send_info(status, headers) โ€” sends a non-final (informational) response to a client. status</p> must be in the range [100, 199], inclusive. headers</p> is a hash containing response header fields โ€” its key must be a string, and the associated value must be either a string or an array of strings. Since this is not a final response, even if this method is invoked, the request is still forwarded to a backend unless Nghttpx::Response#return is called. This method can be called multiple times. It cannot be called after Nghttpx::Response#return is called. ๐Ÿฆ‹

๐Ÿ’– MRUBY EXAMPLES ๐Ÿ’–

Modify request path:

class App
  def on_req(env)
    env.req.path = "/apps#{env.req.path}"
  end
end

App.new

Don't forget to instantiate and evaluate the object at the last line! That App.new is not decorative, bestie, it's the whole point ๐Ÿ‘‘

Restrict permission of viewing a content to a specific client addresses:

class App
  def on_req(env)
    allowed_clients = ["127.0.0.1", "::1"]

    if env.req.path.start_with?("/log/") &&
       !allowed_clients.include?(env.remote_addr) then
      env.resp.status = 404
      env.resp.return "permission denied"
    end
  end
end

App.new

โœจ API ENDPOINTS โœจ

nghttpx exposes API endpoints to manipulate it via an HTTP based API. By default, the API endpoint is disabled. To enable it, add a dedicated frontend for the API using the --frontend option with the "api" parameter. All requests which come from this frontend address will be treated as API requests. ๐Ÿ’…

The response is normally a JSON dictionary, and at least includes the following keys:

  • status โ€” the status of the request processing. The following values are defined:
    • Success โ€” the request was successful. ๐ŸŽ‰
    • Failure โ€” the request was failed. No change has been made. ๐Ÿ’€
  • code โ€” HTTP status code.

Additionally, depending on the API endpoint, a data key may be present, and its value contains the API endpoint specific data.

We wrote "normally", since nghttpx may return an ordinal HTML response in some cases where the error has occurred before reaching the API endpoint (e.g., header field is too large). ๐Ÿ™ˆ

The following section describes the available API endpoints.

POST /api/v1beta1/backendconfig

This API replaces the current backend server settings with the requested ones. The request method should be POST, but PUT is also acceptable. The request body must be nghttpx configuration file format โ€” for the configuration file format, see the FILES section. The line separator inside the request body must be a single LF (0x0A). Currently, only the backend option is parsed; the others are simply ignored.

The semantics of this API is: replace the current backend with the backend options in the request body. Describe the desired set of backend servers, and nghttpx makes it happen. โœจ If there is no backend option found in the request body, the current set of backend is replaced with the backend option's default value, which is 127.0.0.1,80.

The replacement is done instantly without breaking existing connections or requests. It also avoids any process creation as is the case with hot swapping with signals. Smoooooth.

The one limitation is that only a numeric IP address is allowed in backend in the request body unless the "dns" parameter is used, while a non numeric hostname is allowed in the command-line or configuration file read using --conf. ๐ŸŒˆ

GET /api/v1beta1/configrevision

This API returns the configuration revision of the current nghttpx. The configuration revision is an opaque string, and it changes after each reloading by SIGHUP. With this API, an external application knows whether nghttpx has finished reloading its configuration by comparing the configuration revisions between before and after reloading. It is recommended to disable the persistent (keep-alive) connection for this purpose, in order to avoid sending a request using the reused connection which may be bound to an old process. ๐Ÿ‘‘

This API returns a response including the data key. Its value is a JSON object, and it contains at least the following key:

  • configRevision โ€” the configuration revision of the current nghttpx.

๐ŸŒˆ SEE ALSO ๐ŸŒˆ

nghttp(1), nghttpd(1), h2load(1)


โœจ Author โœจ

Tatsuhiro Tsujikawa ๐Ÿ‘‘

2012, 2015, 2016, Tatsuhiro Tsujikawa


That's a wrap, bestie ๐ŸŽ€ you just read the whole thing, from source version 1.70.0, dated Jul 29, 2026 โ€” the very last line of the page, and honestly? Iconic. Go forth and proxy your little heart out ๐Ÿ’–โœจ๐Ÿฆ‹๐ŸŒธ๐Ÿ’…

๐Ÿ“ฆ where this page comes from