Nginx proxy_pass from route without trailing slash - nginx

I would like to configure Nginx to have one route on my domain proxy to another URL. More specifically, I'd like my.domain.com/special_route to proxy to another.domain.com and for the URL to remain unchanged in the address bar. For example, I'd like my.domain.com/special_route/some_path to proxy to another.domain.com/some_path, while the URL remains unchanged.
This is the configuration I have added so far:
set $another_url https://another.domain.com;
location ~ /special_route(/?.*)$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header 'Access-Control-Allow-Origin' '*';
proxy_pass $another_url$1;
}
It appears to work mostly as intended with one notable exception. my.domain.com/special_route/some_path works, my.domain.com/special_route/ also works. However, my.domain.com/special_route (without the trailing slash) does not work. It appears to proxy to another.domain.com/special_route.
What do I need to change or add to my config to get the base route working without the trailing slash?

When the $1 is empty, it becomes just proxy_pass http://upstream which means the url is passed in full to the backend (/special_route).
The variable need to be updated to / in such cases. This is one possible method:
# tip: it should start with ^/ unless you do mean
# to allow accessing /thing/special_route in the same fashion
location ~ ^/special_route(/?.*)$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# another tip: unless you know what you're doing,
# don't use $proxy_add_x_forwarded_for. Just use $remote_addr
# otherwise people may be able to fake their ip in some cases
proxy_set_header X-Forwarded-For $remote_addr;
add_header 'Access-Control-Allow-Origin' '*';
set $proxy_url $1;
if ($proxy_url = '') {
set $proxy_url /;
}
proxy_pass $another_url$proxy_url;
}

Related

Nginx location to FLASK route

I have 2 docker containers running. 1 is FLASK and 2 is Nginx.
This is the configuration of the Nginx location
location /search/?(.*) {
proxy_pass http://backend:8080/search/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_pass_request_headers on;
}
The FLASK is running on port 8080 and the route #app.route('/search', methods=['GET']) and running with app.config['SERVER_NAME'] = 'mydomain.com'
When I try to send a GET request to "mydomain.com" (going through the NGINX) from external I get
Cannot GET /search
When I remove app.config['SERVER_NAME'] = 'mydomain.com' from the FLASK I can send GET to EXTERNAL_IP:8080 (Directly to the backend not though the NGINX).
But when I leave the app.config['SERVER_NAME'] = 'mydomain.com' and try to GET mydomain.com:8080 I get 404 Not Found
What am I missing here?
I want all requests to go though the NGINX and I want to use mydomain.com
I think your main mistake is that you trying to match the whole request URI including the query part while location directive (as well as the rewrite one) works with the normalized URI which doesn't include the query part at all (check the location directive documentation to find out what URI normalization is). Looks like you are also trying to use a regex while regex location should be declared using a ~ (or ~*) modifier. Nevertheless you don't need any regex locations at all for your particular case. To proxy a single API endpoint preservig the Host HTTP header value, you can try this one:
location = /search {
proxy_pass http://backend:8080;
proxy_set_header Host $http_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;
}
You don't need to specify an URI with the proxy_pass directive since you are not changing your request URI. If your API endpoint is /search/ rather than /search, change the location accordingly. If it is an URI path prefix rather than a single API endpoint, use a prefix location /search/ { ... } instead of exact match location.

More accurate nginx location matching

I have the following nginx rule
location /api {
proxy_pass http://flask:5000/api;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
It will match the following path, which is what I'm expecting
http://localhost/api
http://localhost/api/
http://localhost/api/a
http://localhost/api?
http://localhost/api?name=value
However, it will also match the following, which I'm not expecting
http://localhost/apii
http://localhost/apiX
May I know how can I avoid from matching unwanted http://localhost/apii and http://localhost/apiX and http://localhost/apiXX and ...
This requires two location blocks - one matching specifically /api and one for everything in the path /api/. In this way urls like /apio will not be captured.
Example:
location = /api {
proxy_pass http://flask:5000/api;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#Querystrings and /api, matches the first, any /api/* matches this one
location /api/ {
proxy_pass http://flask:5000/api;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Reference http://nginx.org/en/docs/http/ngx_http_core_module.html#location
One possibility is to use a regular expression. As the proxy_pass is not actually transforming the URI, the uri element can be safely removed.
location ~ ^/api($|/) {
proxy_pass http://flask:5000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Note that regular expression location blocks are evaluated in order. See this document for details.
It's actually really simple.
Just change location /api { to location /api/ {
Nginx will add a trailing slash to any request ending /api so there is no need to add a separate block for them. This block will behave as you expected your original one to.

Nginx - proxy_pass to google storage bucket does not use the index directive

I am trying to set my root(/) location to be passed to a google bucket.
Here is my configuration:
listen 80;
location / {
rewrite /(.*) /$1 break;
proxy_pass https://storage.googleapis.com/my-google-bucket-name/$1$is_args$args;
proxy_redirect off;
index my_main.html;
proxy_set_header Host "storage.googleapis.com";
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 Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
This seems to work but the index directive does not set the default page (to my_main.html)
e.g. when i go to http://my_enginx_url/ instead of reaching "my_main.html" i reach the google bucket root page that shows an XML file with all the files in that bucket.
P.S
Both
http://my_enginx_url/another_page.html,
http://my_enginx_url/yet_another_page.html
are working fine.
Any ideas?
Because the index directive tells Nginx the names of files to look for within your file system which are appropriate for serving a request ending with a /
You are proxying the request to another server, so it's not applicable here.
To achieve your desired result create another location directive above your current one and use = to tell Nginx this is to handle only requests for an exact match with http://my_enginx_url/
Something like this:
location = / {
proxy_pass https://storage.googleapis.com/my-google-bucket-name/my_main.html;
......
}

nginx - add_header gets overwritten

I'm having a very hard time understanding exactly when nginx overwrites my add_header directives.
I have the following:
include /etc/nginx/vhost.d/ihc.example.com;
location = /auth {
proxy_pass http://sso.example.com/auth/login;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Host $http_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;
}
location / {
auth_request /auth;
auth_request_set $saved_set_cookie $upstream_http_set_cookie;
add_header Set-Cookie $saved_set_cookie; #I don't see this header in the response
proxy_pass http://ihc.example.com;
include /etc/nginx/vhost.d/ihc.example.com_location;
}
The problem I'm having is that the auth_request_set $saved_set_cookie $upstream_http_set_cookie; cookie doesn't appear in the response.
Now if I remove the include /etc/nginx/vhost.d/ihc.example.com_location; line, it will appear, but this file contains a shared CORS fix that I would really, really like to keep:
if ($cors = 'trueGET') {
add_header 'Access-Control-Allow-Origin' $http_origin; #I see this header in the response
[...]
How can I achieve both without having to repeat the CORS headers in every location? And what is causing this to become overwritten? As I read the documentation, location should overwrite server, but in this case, what is causing this new "scope"?
I finally found the root cause of my issue:
Ifs are evil
Apparently, using if statements in location blocks leads to unspecified behaviours. Unfortunately for me, this means rethinking my entire setup, and potentially switching to OpenResty instead.

forward matched specific location and proxy redirect it in nginx

I am trying to forward specific uri if matched to backend in nginx. For example
forward https://www.hostname.com/*/a/b/c to https://int.hostname.com/*/a/b/c (Where * is a variable auto populated from regex)
Current configuration looks like below and have no idea how to proceed on above
location /a/b/c/ {
proxy_set_header Host $proxy_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 180m;
proxy_pass http://int.hostname.com/v2/e/t/a/b/c;
proxy_redirect default;
}
If you want your location to match any URI that ends with /a/b/c/, you will need to use regular expression syntax. See this document for details.
The URI suffix of the proxy_pass statement is optional. In the absence of a URI suffix, the original URI will be passed unmodified. See this document for details.
For example:
location ~ /a/b/c/$ {
...
proxy_pass http://int.hostname.com;
...
}

Resources