'Nginx multiple locations for multiple Node JS Express Apps

I have the following configuration:

location / {
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-NginX-Proxy true;
  proxy_pass http://localhost:3000; # this is where our node js app runs at
  proxy_set_header Host $http_host;
  proxy_cache_bypass $http_upgrade;
  proxy_redirect off;
}

Which proxies [SERVER_IP]/ to localhost:3000 so that it basically routes everything to the node js app.

I then went ahead and wrote another nodejs app, which runs on port 5000, instead of 3000:

location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-NginX-Proxy true;
      proxy_pass http://localhost:3000; # this is where our node js app runs at
      proxy_set_header Host $http_host;
      proxy_cache_bypass $http_upgrade;
      proxy_redirect off;
    }

location /testing {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-NginX-Proxy true;
      proxy_pass http://localhost:5000; # this is where our node js app runs at
      proxy_set_header Host $http_host;
      proxy_cache_bypass $http_upgrade;
      proxy_redirect off;
    }

However if I go to [SERVER_IP]/testing it routes it as /testing to my first app which then generates the Express JS message: "Cannot GET /testing".

I changed the order, restarted nginx without problems. Any idea why it would not work? I assume nginx is the first instance before it gets routed to Node JS. If I change the port of the location / { ... } I can get the second app, but I would want to run them parallel to each other.

Thanks



Solution 1:[1]

You'll probably find it's actually your second app which is generating the Express JS message: "Cannot GET /testing".

Nginx proxy_pass directives behave differently based upon what can appear to be very minor differences in how you define them. If you specify a location block and proxy_pass it to a server with no path defined then the entire client request uri will be passed to the upstream server.

http://localhost/testing will proxy to http://localhost:5000/testing

However, if you specify a proxy_pass directive with anything appended, Nginx will replace the part of the client request which matches the location block with the path you appended to your proxy_pass directive.

So this, with just an extra slash at the end:

location /testing/ {
    proxy_pass http://localhost:5000/;

Now results in Nginx doing this:

http://localhost/testing -> http://localhost:5000/

And this:

location /testing/ {
    proxy_pass http://localhost:5000/foo/;

Would do this:

http://localhost/testing/bar/ -> http://localhost:5000/foo/bar/

In summary, proxy_pass to a naked server:ip to pass entire client request uri, add a single slash to remove part of the uri, or add something else to substitute.

The other answer suggesting it's caused by the order of your location blocks is incorrect, with all due respect to the person who answered I would recommend you read the information from the link they posted but not follow their advice, as they appear slightly confused about the way Nginx selects location blocks themselves.

Solution 2:[2]

Thanks to @lakshman.pasala, I found a solution for multi location block of a server like below

server {
 listen 80;
 listen [::]:80;

 server_name _;

 # Localhost:80/api1/ -> localhost:5000
 location /api1/ {
    proxy_pass http://localhost:5000/;
    proxy_http_version 1.1;
 }

 # Localhost:80/api2/ -> localhost:6000
 location /api2/ {
    proxy_pass http://localhost:6000/; 
    proxy_http_version 1.1;
 }
}  

When implementing, just pay attention to this 2 points:

  • Location Match MUST have slash ( / ) at the end. E.g: /api1/
  • The proxy_pass destination MUST have slash ( / ) at the end. E.g: http://localhost:5000/ instead of http://localhost:5000

Solution 3:[3]

Since server_ip/testing does match the / location block, you will always go inside that location block. At this point NGINX doesn't go to the next block as it has already found the block that matches the request.

You should move the second location block to the top and use = instead, like this

location = /testing { }

to forward the request to nodeApp2:5000

Read the NGINX documentation here to understand how NGINX location blocks work along with regex rules.

Solution 4:[4]

In your default file, only change the port number in your proxy_pass, and leave location as /

Example of how your default would look:

server {
  listen 80 default_server;
  listen  [::]:80  default_server;
  root /root/port-two/build;
  server_name www.myfirstsite.com myfirstsite.com;
  location / {
    proxy_pass http://127.0.0.1:8001;
    proxy_redirect off;
    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_max_temp_file_size 0;
    client_max_body_size 10m;
    client_body_buffer_size 128k;
    proxy_connect_timeout 90;
    proxy_send_timeout 90;
    proxy_read_timeout 90;
    proxy_buffer_size 4k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
  }
}
server {
  listen 80;
  listen [::]:80;
  server_name www.mysecondsite.com mysecondsite.com;
  location / {
    proxy_pass http://127.0.0.1:8002;
    proxy_redirect off;
    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_max_temp_file_size 0;
    client_max_body_size 10m;
    client_body_buffer_size 128k;
    proxy_connect_timeout 90;
    proxy_send_timeout 90;
    proxy_read_timeout 90;
    proxy_buffer_size 4k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
  }
}

And make sure the .env file of each app has a PORT variable corresponding to the number you gave it on the default file.

.env for myfirstsite.com

PORT=8001

.env for mysecondsite.com

PORT=8002

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 miknik
Solution 2 Hoang Thinh
Solution 3 lakshman.pasala
Solution 4 maurojflores