Search code examples
dockernginxdocker-composenginx-reverse-proxygogs

Nginx Reverse Proxy not redirecting?


Update

The details in this question are getting long, but I think it narrows down to this:

For some reason the host name matters to Nginx when it's trying to figure out whether to proxy the request. If the host name is set to git.example.com the request does not seem to go through, but if it's set to 203.0.113.2 then it goes through. Why does the host name matter?

Filed an issue with Nginx here And docker compose

Start of original question

When I type in the IP address of the reverse proxy directly into my browser bar, it does perform the redirect.

When using a URL that is resolved via the /etc/hosts entry 203.0.113.2 git.example.com the "Welcome to Ngnix page" is shown. Any ideas? This is the configuration:

server {
    listen 203.0.113.2:80 default_server;
    server_name 203.0.113.2 git.example.com;

    proxy_set_header X-Real-IP  $remote_addr; # pass on real client IP

    location / {
        proxy_pass http://203.0.113.1:3000;
    }
}

This is the docker-compose.yml file that is used to launch the whole thing:

version: '3'
services:
  gogs-nginx:
    build: ./proxy
    ports:
      - "80:80"
    networks:
      mk1net:
        ipv4_address: 203.0.113.2
  gogs:
    image: gogs/gogs
    ports:
      - "3000:3000"
    volumes: 
      - gogs-data:/data
    networks: 
      mk1net:
        ipv4_address: 203.0.113.3
volumes:
  gogs-data:
    external: true
networks:
  mk1net:
    ipam:
      config:
        - subnet: 203.0.113.0/24

One interesting thing is that I can navigate to for example:

http://203.0.113.2/issues

The log for the above URL is:

gogs-nginx_1 | 203.0.113.1 - - [07/Oct/2018:11:28:06 +0000] "GET / HTTP/1.1" 200 38825 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" "-"

If I then change 203.0.113.2 with git.example.com (So that the url ends up being git.example.com I get Nginxs "404 not found" page, and the log says:

gogs-nginx_1 | 2018/10/07 11:31:34 [error] 8#8: *10 open() "/usr/share/nginx/html/issues" failed (2: No such file or directory), client: 203.0.113.1, server: localhost, request: "GET /issues HTTP/1.1", host: "git.example.com"

If I only use http://git.example.com as the URL I get the NGINX welcome page, and the following log:

gogs-nginx_1 | 203.0.113.1 - - [07/Oct/2018:11:34:39 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" "-"

It looks like Nginx understands that the request is for the proxy because it logs the IP of the proxy, but it does not redirect to the proxy and returns a 304 ...

Using Curl to perform requests

Using curl with a host name parameter that targets the proxy like this:

curl -H 'Host: git.example.com' -si http://203.0.113.2

Results in the Nginx welcome page:

    ole@mki:~/Gogs/.gogs/docker$ curl -H 'Host: git.example.com' -si http://203.0.113.2
    HTTP/1.1 200 OK
    Server: nginx/1.15.1
    Date: Sun, 07 Oct 2018 17:09:11 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 03 Jul 2018 13:27:08 GMT
    Connection: keep-alive
    ETag: "5b3b79ac-264"
    Accept-Ranges: bytes

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
        body {
            width: 35em;
            margin: 0 auto;
            font-family: Tahoma, Verdana, Arial, sans-serif;
        }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>

    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>

    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>

But if I change the host name to the ip address like this:

Using curl with a host name parameter that targets the proxy like this:

curl -H 'Host: 203.0.113.2' -si http://203.0.113.2

Then the proxy works as it should:

    ole@mki:~/Gogs/.gogs/docker$ curl -H 'Host: 203.0.113.2' -si http://203.0.113.2
    HTTP/1.1 302 Found
    Server: nginx/1.15.1
    Date: Sun, 07 Oct 2018 17:14:46 GMT
    Content-Type: text/html; charset=utf-8
    Content-Length: 34
    Connection: keep-alive
    Location: /user/login
    Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
    Set-Cookie: i_like_gogits=845bb09d69587b81; Path=/; HttpOnly
    Set-Cookie: _csrf=neGgBfG4LdOcdrdeA0snHjVGz4s6MTUzODkzMjQ4NjE5MzEzNzI3OQ%3D%3D; Path=/; Expires=Mon, 08 Oct 2018 17:14:46 GMT; HttpOnly
    Set-Cookie: redirect_to=%252F; Path=/

    <a href="/user/login">Found</a>.

Solution

  • I am sorry, I failed to realize what's happening on your side because the information is sometimes confusing and sometimes incomplete. But Stackoverflow provides a great explanation on what is considered a good question: How to create a Minimal, Complete, and Verifiable example and so I have just tried to implement a minimal example of a system you are likely going to build.

    Below I am providing all the files and will show you a test run as well.

    File #1: docker-compose.yml

    gogs:
      image: gogs/gogs
    
    web:
      build: .
      ports:
      - 8000:80
      links:
      - gogs
    

    I have outdated Docker at my computer and I do not want to bother with Docker networking, so I have just linked both containers using Docker links. This is the most important part and the link will ensure that (1) our web container depends on gogs; (2) we are able to reference gogs IP from inside web as just gogs. Docker will resolve the name to an IP assigned to the container.

    Since I want a minimal example, I've skipped everything else as irrelevant. For example, volume.

    File #2: Dockerfile

    Newer Compose versions support config options specified right in docker-compose.yml, but I need a custom Dockerfile instead. It's trivial:

    FROM nginx:stable-alpine
    COPY gogs.conf /etc/nginx/conf.d
    

    File #3: gogs.conf

    And finally we need Nginx configuration for proxy:

    server {
      listen 80 default_server;
    
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    
      location / {
        proxy_pass http://gogs:3000;
      }
    }
    

    You may notice here we are referring another container simply by name gogs and we need to know what port number it is exposes. We know: 3000.

    Running

    $ docker-compose build
    $ docker-compose up
    

    It's up and running:

    $ docker ps
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
    1f74293df630        g_web               "nginx -g 'daemon off"   2 minutes ago       Up 26 seconds       0.0.0.0:8000->80/tcp   g_web_1
    dfa2dbaa6074        gogs/gogs           "/app/gogs/docker/sta"   2 minutes ago       Up 26 seconds       22/tcp, 3000/tcp       g_gogs_1
    

    web container is exposed to the world at port number 8000.

    Tests

    by IP

    Let's request it by IP:

    $ curl -si http://192.168.99.100:8000/
    HTTP/1.1 302 Found
    Server: nginx/1.14.0
    Date: Sun, 07 Oct 2018 15:13:55 GMT
    Content-Type: text/html; charset=utf-8
    Content-Length: 31
    Connection: keep-alive
    Location: /install
    Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
    Set-Cookie: i_like_gogits=50411f542e2ae8f8; Path=/; HttpOnly
    Set-Cookie: _csrf=ZJxRPqnqayIbpAYgZ22zrPIOaSo6MTUzODkyNTIzNTQ2NTg5MDE1NA%3D%3D; Path=/; Expires=Mon, 08 Oct 2018 15:13:55 GMT; HttpOnly
    
    <a href="/install">Found</a>.
    

    Corresponding log file:

    web_1   | 192.168.99.1 - - [07/Oct/2018:15:14:24 +0000] "GET / HTTP/1.1" 302 31 "-" "curl/7.61.1" "-"
    gogs_1  | [Macaron] 2018-10-07 15:14:24: Started GET / for 192.168.99.1
    gogs_1  | [Macaron] 2018-10-07 15:14:24: Completed GET / 302 Found in 199.519µs
    gogs_1  | 2018/10/07 15:14:24 [TRACE] Session ID: 38d06d393a9e9d21
    gogs_1  | 2018/10/07 15:14:24 [TRACE] CSRF Token: Xth986dFWhhj8w8vBdIqRZu4SbI6MTUzODkyNTI2NDYxMDYzNzAyNA==
    

    I can see from the log that (1) both containers work and they were used to process the request; (2) 192.168.99.1 is my host's IP address, which means "gogs" successfully gets a real request IP via X-Forwarded-For.

    by domain name

    OK, let's request using a domain name:

    $ curl -H 'Host: g.example.com' -si http://192.168.99.100:8000/
    

    Trust me, this is just sufficient. Host is an HTTP protocol header to pass domain name. And any browser will do the same under the hood.

    and the corresponding log file is --

    gogs_1  | [Macaron] 2018-10-07 15:32:49: Started GET / for 192.168.99.1
    gogs_1  | [Macaron] 2018-10-07 15:32:49: Completed GET / 302 Found in 618.701µs
    gogs_1  | 2018/10/07 15:32:49 [TRACE] Session ID: 81f64d97e9c3dd1e
    gogs_1  | 2018/10/07 15:32:49 [TRACE] CSRF Token: X5QyHM4LMIfn8OSJD1gwSSEyXV46MTUzODkyNjM2OTgyODQyMjExMA==
    web_1   | 192.168.99.1 - - [07/Oct/2018:15:32:49 +0000] "GET / HTTP/1.1" 302 31 "-" "curl/7.61.1" "-"
    

    No changes, everything works as expected.