Skip to content

Commit de93f1f

Browse files
committed
feat: Add support for HTTP keep-alive between the proxy and upstream
1 parent 8346b68 commit de93f1f

File tree

4 files changed

+60
-1
lines changed

4 files changed

+60
-1
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,13 @@ docker run -d -p 80:80 -p 443:443 \
368368

369369
You'll need apache2-utils on the machine where you plan to create the htpasswd file. Follow these [instructions](http://httpd.apache.org/docs/2.2/programs/htpasswd.html)
370370

371+
### Upstream (Backend) Server HTTP Keep-Alive Support
372+
373+
> **Warning**
374+
> This feature is experimental. The behavior may change (or the feature may be removed) without warning in a future release, even if the release is not a new major version. If you use this feature, please provide feedback so we know whether it is working properly and can be promoted to officially supported.
375+
376+
To enable HTTP keep-alive between `nginx-proxy` and a backend server, set the `com.github.nginx-proxy.nginx-proxy.keepalive` label on the server's container to the desired maximum number of idle connections. See the [nginx keepalive documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) and the [Docker label documentation](https://docs.docker.com/config/labels-custom-metadata/) for details.
377+
371378
### Headers
372379

373380
By default, `nginx-proxy` forwards all incoming request headers from the client to the backend server unmodified, with the following exceptions:

nginx.tmpl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
{{- end }}
7070

7171
{{- define "location" }}
72+
{{- $keepalive := first (keys (groupByLabel .Containers "com.github.nginx-proxy.nginx-proxy.keepalive")) }}
7273
location {{ .Path }} {
7374
{{- if eq .NetworkTag "internal" }}
7475
# Only allow traffic from internal clients
@@ -82,10 +83,16 @@
8283
root {{ trim .VhostRoot }};
8384
include fastcgi_params;
8485
fastcgi_pass {{ trim .Upstream }};
86+
{{- if $keepalive }}
87+
fastcgi_keep_conn on;
88+
{{- end }}
8589
{{- else if eq .Proto "grpc" }}
8690
grpc_pass {{ trim .Proto }}://{{ trim .Upstream }};
8791
{{- else }}
8892
proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}{{ trim .Dest }};
93+
{{- if $keepalive }}
94+
proxy_set_header Connection $proxy_connection_noclose;
95+
{{- end }}
8996
{{- end }}
9097

9198
{{- if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }}
@@ -161,6 +168,10 @@ upstream {{ .Upstream }} {
161168
# Fallback entry
162169
server 127.0.0.1 down;
163170
{{- end }}
171+
{{- $keepalive := first (keys (groupByLabel .Containers "com.github.nginx-proxy.nginx-proxy.keepalive")) }}
172+
{{- if $keepalive }}
173+
keepalive {{ $keepalive }};
174+
{{- end }}
164175
}
165176
{{- end }}
166177

@@ -193,6 +204,12 @@ map $http_upgrade $proxy_connection {
193204
default upgrade;
194205
'' close;
195206
}
207+
map $http_upgrade $proxy_connection_noclose {
208+
default upgrade;
209+
# By default, nginx sets "Connection: close". Cancel that to allow keepalive
210+
# connections between nginx and the upstream server.
211+
'' '';
212+
}
196213

197214
# Apply fix for very long server names
198215
server_names_hash_bucket_size 128;
@@ -450,7 +467,7 @@ server {
450467
{{- $upstream = printf "%s-%s" $upstream $sum }}
451468
{{- $dest = (or (first (groupByKeys $containers "Env.VIRTUAL_DEST")) "") }}
452469
{{- end }}
453-
{{- template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }}
470+
{{- template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag "Containers" $containers) }}
454471
{{- end }}
455472
{{- if and (not (contains $paths "/")) (ne $globals.default_root_response "none")}}
456473
location / {

test/test_keepalive.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import re
2+
3+
4+
def test_keepalive_disabled(docker_compose, nginxproxy):
5+
r = nginxproxy.get("http://keepalive-disabled.nginx-proxy.example/headers")
6+
assert r.status_code == 200
7+
assert re.search(fr'(?m)^(?i:Connection): close$', r.text)
8+
9+
def test_keepalive_enabled(docker_compose, nginxproxy):
10+
r = nginxproxy.get("http://keepalive-enabled.nginx-proxy.example/headers")
11+
assert r.status_code == 200
12+
assert not re.search(fr'(?m)^(?i:Connection):', r.text)

test/test_keepalive.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
keepalive-disabled:
2+
image: web
3+
expose:
4+
- "80"
5+
environment:
6+
WEB_PORTS: 80
7+
VIRTUAL_HOST: keepalive-disabled.nginx-proxy.example
8+
9+
keepalive-enabled:
10+
image: web
11+
expose:
12+
- "80"
13+
environment:
14+
WEB_PORTS: 80
15+
VIRTUAL_HOST: keepalive-enabled.nginx-proxy.example
16+
labels:
17+
com.github.nginx-proxy.nginx-proxy.keepalive: "64"
18+
19+
20+
sut:
21+
image: nginxproxy/nginx-proxy:test
22+
volumes:
23+
- /var/run/docker.sock:/tmp/docker.sock:ro

0 commit comments

Comments
 (0)