From c274397ce58e750c4d487084d56a4414df2c93f2 Mon Sep 17 00:00:00 2001 From: Patrik Wall Date: Thu, 30 Apr 2026 14:43:49 +0200 Subject: [PATCH 1/2] Events: support for SO_SNDBUF on outbound peer connections. ngx_event_connect_peer() honors SO_RCVBUF via pc->rcvbuf but had no equivalent for SO_SNDBUF. This adds pc->sndbuf and the matching setsockopt() call, mirroring the existing SO_RCVBUF path. Existing callers leaving sndbuf at 0 retain prior behavior, since setsockopt() is only invoked when the field is non-zero. The new field is consumed by the stream proxy module in a follow-up commit. --- src/event/ngx_event_connect.c | 10 ++++++++++ src/event/ngx_event_connect.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/event/ngx_event_connect.c b/src/event/ngx_event_connect.c index 668084a7b..a021f567b 100644 --- a/src/event/ngx_event_connect.c +++ b/src/event/ngx_event_connect.c @@ -73,6 +73,16 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc) } } + if (pc->sndbuf) { + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + (const void *) &pc->sndbuf, sizeof(int)) == -1) + { + ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, + "setsockopt(SO_SNDBUF) failed"); + goto failed; + } + } + if (pc->so_keepalive) { value = 1; diff --git a/src/event/ngx_event_connect.h b/src/event/ngx_event_connect.h index e428e7376..0604fef0f 100644 --- a/src/event/ngx_event_connect.h +++ b/src/event/ngx_event_connect.h @@ -57,6 +57,7 @@ struct ngx_peer_connection_s { int type; int rcvbuf; + int sndbuf; ngx_log_t *log; From 76c1e21e379aa34e11a00f0e32340e6e3da0038f Mon Sep 17 00:00:00 2001 From: Patrik Wall Date: Mon, 27 Apr 2026 13:22:16 +0200 Subject: [PATCH 2/2] Stream: proxy_socket_rcvbuf and proxy_socket_sndbuf directives. Until now, the only way to size socket buffers in nginx was the "sndbuf="/"rcvbuf=" parameters of the listen directive, which only affect listener-derived sockets. Outbound sockets created for proxy_pass therefore always used the OS default, which is small when TCP buffer autotuning is disabled. Both directives accept a size, the literal "max" (sets the buffer to INT_MAX so that the kernel clamps to its configured maximum, e.g. wmem_max on Linux or net.inet.tcp.{send,recv}buf_max on FreeBSD and macOS), or "off" (default; do not call setsockopt). The directives populate pc->rcvbuf and pc->sndbuf at upstream connect time; SO_SNDBUF use requires the support added to ngx_event_connect_peer() in the preceding commit. Closes: https://github.com/nginx/nginx/issues/1297 --- src/stream/ngx_stream_proxy_module.c | 68 ++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index c358c8647..c907bb72c 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -34,6 +34,8 @@ typedef struct { ngx_flag_t half_close; ngx_stream_upstream_local_t *local; ngx_flag_t socket_keepalive; + ngx_int_t socket_rcvbuf; + ngx_int_t socket_sndbuf; #if (NGX_STREAM_SSL) ngx_flag_t ssl_enable; @@ -92,6 +94,8 @@ static char *ngx_stream_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_proxy_socket_buf(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); #if (NGX_STREAM_SSL) @@ -166,6 +170,20 @@ static ngx_command_t ngx_stream_proxy_commands[] = { offsetof(ngx_stream_proxy_srv_conf_t, socket_keepalive), NULL }, + { ngx_string("proxy_socket_rcvbuf"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_proxy_socket_buf, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, socket_rcvbuf), + NULL }, + + { ngx_string("proxy_socket_sndbuf"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_proxy_socket_buf, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, socket_sndbuf), + NULL }, + { ngx_string("proxy_connect_timeout"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, @@ -457,6 +475,9 @@ ngx_stream_proxy_handler(ngx_stream_session_t *s) u->peer.so_keepalive = 1; } + u->peer.rcvbuf = (int) pscf->socket_rcvbuf; + u->peer.sndbuf = (int) pscf->socket_sndbuf; + u->peer.type = c->type; u->start_sec = ngx_time(); @@ -2367,6 +2388,8 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) conf->proxy_protocol = NGX_CONF_UNSET; conf->local = NGX_CONF_UNSET_PTR; conf->socket_keepalive = NGX_CONF_UNSET; + conf->socket_rcvbuf = NGX_CONF_UNSET; + conf->socket_sndbuf = NGX_CONF_UNSET; conf->half_close = NGX_CONF_UNSET; #if (NGX_STREAM_SSL) @@ -2428,6 +2451,10 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->socket_keepalive, prev->socket_keepalive, 0); + ngx_conf_merge_value(conf->socket_rcvbuf, prev->socket_rcvbuf, 0); + + ngx_conf_merge_value(conf->socket_sndbuf, prev->socket_sndbuf, 0); + ngx_conf_merge_value(conf->half_close, prev->half_close, 0); #if (NGX_STREAM_SSL) @@ -2833,3 +2860,44 @@ ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_OK; } + + +static char * +ngx_stream_proxy_socket_buf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + ngx_int_t *np; + ngx_str_t *value; + ssize_t size; + + np = (ngx_int_t *) (p + cmd->offset); + + if (*np != NGX_CONF_UNSET) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + *np = 0; + return NGX_CONF_OK; + } + + if (ngx_strcmp(value[1].data, "max") == 0) { + *np = NGX_MAX_INT32_VALUE; + return NGX_CONF_OK; + } + + size = ngx_parse_size(&value[1]); + + if (size == NGX_ERROR || size <= 0 || size > NGX_MAX_INT32_VALUE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + *np = (ngx_int_t) size; + + return NGX_CONF_OK; +}