How to configure nginx X-Forwarded-Port to be the originally request port
Asked Answered
T

6

46

I am using nginx in a standard reverse proxy scenario, to pass all requests to /auth to another host, however I'm trying to use non-standard ports.

My end goal is to have the X-Forwarded-Port header set to the port that the request comes in on.

Here is my location block in nginx.conf:

location /auth/ {
    proxy_pass       http://otherhost:8090;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port <VAR>;
}

This nginx is running in a docker container, that is configured to forward requests from 8085 into 80 in the container, such that the nginx process is listening on 80:

0.0.0.0:8085->80/tcp

When I hit the URL:

http://localhost:8085/auth/

I am correctly redirected to http://otherhost:8090, but the X-Forwarded-Port header is missing or wrong.

Where I have <VAR> in the original block, I have tried the following:

  • $server_port - This is the port nginx is listening on (80), not the request port.

  • $pass_port - Seems to be null in my setup, so nginx drops the header.

  • $http_port - This is a random port per request.

  • $remote_port - This is a random port per request.

I can change my config at deploy time to hardcode to the known port of incoming requests, but ideally I would be able to change the front port without any change to the nginx config.

I've scoured the nginx variable list but can't find anything like $request_port. Is there any way for me to achieve my intent?

Toffic answered 10/3, 2020 at 11:19 Comment(3)
Did you try $remote_port? Apart from that, I have difficulties to understand how the port nginx is listening to ($server_port) would be different from the request's port.Anabantid
@Anabantid - $remote_port also seems to be random per request. Requests hit the docker host on port 8085, which forward to the container on port 80, so nginx listens on 80 but the port in the URL is 8085.Toffic
Just to note a fundamental problem here: Nginx is not privy to the original port - all it sees is the port that docker is forwarding to. To get the original port, you'd need a separate environment variable defined (set from docker) and set the x-forwarded-port to that.Hers
O
15

The only workaround I've found is to use a map rule to get the port from the http_host variable e.g.

    map $http_host $port {
      default 80;
      "~^[^\:]+:(?<p>\d+)$" $p;
    }
Orgel answered 11/8, 2020 at 20:44 Comment(3)
I would suggest to use default $scheme; instead. When running http and https on our nginx we had the problem, that https was suddenly using 80 in the X-Forwarded-Port. The location (keycloak) was able to handle the strings 'http' and 'https' as well, so no if/else was requiredDamning
@Philipp Hebing It's not because it works with Keycloak that it will work with another backendOstrich
Note that you will have to change 80 to 443 if using https. If you can use both (http and https), then see my answer for something more dynamic.Ostrich
T
6

This is a just rough idea to write Nginx conf, but I am sure this can help you in redirection

server {    
    listen 80;  
    server_name host.docker.internal;   

    # By default land on localhost:80 to root so in root we copied UI build to the ngnix html dir.
    # have a look to docker-compose uiapp service.
    location / {    
            root   /usr/share/nginx/html;   
            index  index.html index.htm;    
    }   

   # after location add filter, from which every endpoint starts with or comes in endpoint 
   # so that ngnix can capture the URL and reroute it.
   # like /backend/getUserInfo/<UserId> 
   # In above example /backend is that filter which will be captured by Ngnix and reroute the flow.
    location /backend { 
        proxy_set_header X-Forwarded-Host $host;    
        proxy_set_header X-Forwarded-Server $host;  
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        #proxy_pass http://<ContainerName>:<PortNumber>; 
        # In our case Container name is as we setup in docker-compose `beservice` and port 8080
        proxy_pass http://beservice:8080;   
    }   
}

For more details you can have a look at this project

https://github.com/dupinder/NgnixDockerizedDevEnv

Trollope answered 18/8, 2020 at 9:2 Comment(1)
I dont' see how it is related to the question which asks how to set the X-Forwarded-Port header.Ostrich
C
0

Inside from container you cannot access the outer config. And nginx doesn't support using environment variables inside most configuration blocks.

But if you use poco you can define your ports, paths, etc in a simple env file. These definition used by docker-compose configuration or env variable too. For example

yourconfig.env

PROJECT_HOME=..
MAGIC_DOCKER_PORT=8085

poco.yml file (main config to your project, define plans)

version: '3.0'
maintainer: "[email protected]"

environment:
  include: docker/conf/yourconfig.env

plan:
  live:
    description: "your plan to run live"
    docker-compose-file:
      - docker/dc-proxy.yml
      - docker/dc-other-service.yml
# you can define other plans to run local/dev env

and in docker/dc-proxy.yml compose file (relevant part):

version: '3'
services:
  proxy:
    image: nginx:alpine
    env_file: conf/yourconfig.env
    ports:
      - ${MAGIC_DOCKER_PORT}:80
    command: /bin/sh -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" 

and at last your config template relevant part from /etc/nginx/conf.d/mysite.template

listen ${MAGIC_DOCKER_PORT};
...
  location /auth/ {
    ...
    proxy_set_header X-Forwarded-Port ${MAGIC_DOCKER_PORT};
    ...
  }

I use the following folder structure to organize files. It is not mandatory, but easy to use for me because my every project structure are same.

/
  docker                <- all of docker related files here
    conf                <- env files, service configs, etc
    scripts             <- shell scripts which used by containers
    dc-*.yml            <- separate all services into single compose files
  poco.yml              <- main config, "start here" point
  project-related-files <- separated by each service

Explanation

Define a key-value pair in .env file. This is used by poco for create docker-compose files, and it used to create environment variable in nginx container. After that use envsubst to fill out nginx config template, and at last starts nginx service

Chuffy answered 12/8, 2020 at 9:2 Comment(0)
O
0

Here is a working solution when your nginx can be behind a reverse-proxy (works also if you are not behind a reverse proxy or if the reverse-proxy does not add X-Forwarded-* headers):

# Check if a X-Forwarded-Proto header (set by reverse-proxy) is already present. If not take the scheme used to call our nginx server.
map $http_x_forwarded_proto $x_forwarded_proto {
    default $http_x_forwarded_proto;
    ""      $scheme; # Note that if the reverse-proxy does not add a X-Forwarded-Proto header, it may be incorrect if the protocol used by the reverse proxy is not the same as the one on which your nginx server is listening. In this case you have no solution than harcode the correct value.
}

# Check if a X-Forwarded-Host header (set by reverse-proxy) is already present. If not take the value of the 'Host' header.
map $http_x_forwarded_host $x_forwarded_host {
    default $http_x_forwarded_host;
    ""      $http_host;
}

# Set the default port of each scheme/protocol (80 for http, 443 for https)
map $x_forwarded_proto $default_http_port {
    default 80;
    "https" 443;
}

# Extract the real port of the client request url (unfortunatly nginx has no variable to get this info)
map $http_host $request_port {
    default                 $default_http_port; # If port not explicitely defined in url take the default one associated to the calling scheme/protocol (80 for http, 443 for https)
    "~^[^\:]+:(?<p>\d+)$"   $p;
}

# Check if a X-Forwarded-Port header (set by reverse-proxy) is already present. If not take the port from the client request url
map $http_x_forwarded_port $x_forwarded_port {
    default $http_x_forwarded_port;
    ""      $request_port;
}

...


proxy_set_header X-Forwarded-Proto $x_forwarded_proto;
proxy_set_header X-Forwarded-Host $x_forwarded_host;
proxy_set_header X-Forwarded-Port $x_forwarded_port;
Ostrich answered 24/10, 2023 at 17:35 Comment(0)
T
-1

I used this:

map $http_x_forwarded_port $my_forwarded_port {
    default $remote_port;
    "~^(.*)$" $1;
}

All headers are set in variables in nginx, prefixed with http. I.e if you have origin = domain.com in header, you can use $http_origin to get "domain.com". So my code sets $my_forwarded_port to the forwarded port if it is set, otherwise to $remote_port

Tinsmith answered 9/9, 2020 at 12:55 Comment(1)
Don't use $remote_port for the default value. It is a random port and not the port of the original requestOstrich
O
-3

Strange that this hasn't been answered yet, but the answer is

proxy_set_header X-Forwarded-Port $server_port;
Ogham answered 7/6, 2020 at 14:50 Comment(3)
Thanks for the answer but I don't think this is the case. $server_port is the port that the nginx process is listening on, not necessarily the port that the request was made toToffic
The documentation for server_port seems to indicate it is the port the request was made on. nginx.org/en/docs/http/ngx_http_core_module.html $server_port port of the server which accepted a requestMitten
If there is a load balancer in front of nginx, which gets a request from a client on port 8000, and redirects it to 8100, and the nginx listens on 8100, than $server_port is 8100. But the author needs the port from the original request (8000)Tinsmith

© 2022 - 2024 — McMap. All rights reserved.