17

I'm trying to run multiple docker containers all running nginx listening on port 80, but with different host ports mapping to the containers port 80.

For the most part this works, except for when nginx does a redirect due to missing a trailing slash.

server {
    listen 80;
    root /var/www;
    index index.html;
    location /docs {}
}

Given the above nginx config and a docker container running it with host port 8080 mapped to container port 80 I can get localhost:8080/docs/ via curl ok:

> GET /docs/ HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
* Server nginx/1.9.5 is not blacklisted
< Server: nginx/1.9.5
< Date: Sat, 28 Nov 2015 17:27:05 GMT
< Content-Type: text/html
< Content-Length: 6431
< Last-Modified: Sat, 28 Nov 2015 17:17:06 GMT
< Connection: keep-alive
< ETag: "5659e192-191f"
< Accept-Ranges: bytes
<
... html page ...

but if I request localhost:8080/docs I get a redirect to localhost/docs/

> GET /docs HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
* Server nginx/1.9.5 is not blacklisted
< Server: nginx/1.9.5
< Date: Sat, 28 Nov 2015 17:29:40 GMT
< Content-Type: text/html
< Content-Length: 184
< Location: http://localhost/docs/
< Connection: keep-alive
<
... html redirect page ...

How can I get nginx to preserve the original port when doing the redirect? I've tried looking at port_in_redirect and server_name_in_redirect but they didn't help.


EDIT

Based on https://forum.nginx.org/read.php?2,261216,261216#msg-261216 this doesn't look possible right now.

3
  • Look into the nginx-proxy container, so you don't have to do any of this insane port rewriting junk. Nov 28, 2015 at 18:27
  • I don't really want anything balancing in front of these containers. I've got a docker-compose file that sets the external port based on an env var, and most of the time I just "docker-compose up -d" this file once. However for testing reasons and to allow me to work on other things I want to be able to do "PORT=8080 docker-compose -p test up -d" to spin up a whole new set of containers (due to the new project name) that are mapped to a different host port.
    – Ibasa
    Nov 29, 2015 at 11:41
  • Ack, just ran into this problem as well. I guess I'll have to go vanilla nginx or move the stuff on 8080 to something else.
    – Ken
    May 10, 2016 at 23:32

5 Answers 5

5

The simplest solution is to remove the index directive and not rely on explicit or implicit $uri/ redirects. For example:

server {
  listen 80;
  root /var/www;
  location /docs {
    try_files $uri $uri/index.html =404;
  }
}

This isn't identical behaviour as it avoids the redirect altogether. If you wanted a trailing slash redirect like the index module gives, then a more complex solution is required. For example:

server {
  listen 80;
  root /var/www;
  location /docs {
    try_files $uri @redirect;
  }
  location @redirect {
    if ($uri ~* ^(.+)/$) { rewrite ^ $uri/index.html last; }
    if (-d $document_root$uri) { return $scheme://$host:8080$uri/; }
    return 404;
  }
}
3
  • As I said in the question I have tried this, I added port_in_redirect off; to the http block, same result of a redirect to localhost/docs/, tried adding server_name_in_redirect off; as well. Still redirects to localhost/docs/
    – Ibasa
    Nov 29, 2015 at 11:39
  • @Ibasa Yeah, sorry about that. Read twice - write once. Must try to remember that. Nov 29, 2015 at 13:15
  • I like this one is not requiring container to run in the same port as nginx uses inside container. Jul 16, 2021 at 15:05
9

The HTTP clients will put the port in the Host header. If you use the original value of the host header when doing the redirect, it should work as expected. I tested the following code and looks to be doing exactly what you requested:

location ~ ^.*[^/]$ {
    try_files $uri @rewrite;
}
location @rewrite {
    return 302 $scheme://$http_host$uri/;
}

> GET /bla HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 302 Moved Temporarily
< Server: nginx/1.9.7
< Date: Sun, 29 Nov 2015 06:23:35 GMT
< Content-Type: text/html
< Content-Length: 160
< Connection: keep-alive
< Location: http://localhost:8080/bla/
1
  • This is technically the correct answer. It works with IPs as well, e.g. a request to 127.0.0.1:8000 will have 127.0.0.1:8000 as http_host. That is because according to: tools.ietf.org/html/rfc2616#section-14.23 http_host needs to cater for ambiguousity, to which a port will add. If ports are left out, then the defaults are implied (e.g. either 80 or 443). So this solution should be the cleanest to work with... Feb 10, 2018 at 11:41
0

Just follow this simple fix

location /app {
    alias /usr/share/nginx/html/folder;
    if (-d $request_filename) {
        rewrite [^/]$ $scheme://$http_host$uri/ permanent;
    }
}
0

Interesting... I encountered precisely this issue and was able to fix it much as Richard Smith's answer suggests:

root /var/www;
location = /docs {
    try_files $uri $uri/ =404;
}

The only difference is that I don't specify index.html?

Specify the error code to avoid a redirect loop.

Still waiting on feedback from nginx's support.

0

I think it happens since your Docker is kind of NAT/Proxy to your nginx container. For the routing between different port addition information is required.

For me this was solved add the following to the nginx.conf

http {
    ...
    proxy_set_header Host $http_host; # required for docker client's sake
    proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    ...
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .