proxy_pass doesn't seem to resolve to upstream - nginx

I use this configuration below but somehow it doesn't proxy to the upstream.
upstream backend {
ip_hash;
server s1.example.com;
server s2.example.com;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name localhost;
location / {
proxy_pass http://backend;
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;
}
}
However, if i replace to proxy_pass http://s1.example.com, it proxy successfully to the s1.example.com server.
How do I fix this?

The NGINX config says about proxy_pass:
Parameter value can contain variables. In this case, if an address is specified as a domain name, the name is searched among the described server groups, and, if not found, is determined using a resolver.
It does NOT state how the parameter handles a hostname when it's a constant/statically defined. According to this NGINX forum post:
domain names statically configured in config are only looked
up once on startup (or configuration reload).
Using an upstream block (as in your example) won't save you from this problem ("statically defined hostname is only looked up once after start/reload") unless you are using (the pay for) NGINX Plus and then you can add the resolve argument to the end of the nested server parameter.
The good news is that even in the free NGINX you can workaround the lookup problem by using variables like so:
upstream backend { ... }
server {
...
set $backend "backend";
proxy_pass http://$backend;
...
}
but this introduces its own caveats. See https://github.com/DmitryFillo/nginx-proxy-pitfalls for a thorough discussion of workarounds and pitfalls to the "NGINX proxy repeat DNS resolution" problem.

Related

Nginx not appending path when using proxy_path

I am trying to use Nginx as a reverse proxy for a few backends. It's probably not relevant, but the backends are all hosted within a single Docker network, and the network is being created using docker-compose.
The backends all have the same endpoints that need to be exposed, so to reduce duplicate configuration lines I am trying to use map.
map $http_host $backend {
host-a.example.com host-a;
host-b.example.com host-b;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name _;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
location / {
# there is no DNS resolution if we set proxy_pass targets dynamically,
# use the docker internal DNS server
resolver 127.0.0.11;
proxy_pass "http://$backend:8080/";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The resolver line has been added as Nginx will otherwise give the error: no resolver defined to resolve *hostname*
However with this configuration I am finding that requests are being sent to the wrong path, for example if I send a request to https://host-a.example.com/books or https://host-a.example.com/books/123/front-cover.jpg the page that is returned is always the root of the site (i.e. https://host-a.example.com/), it seems that for some reason proxy_pass is not including the path with the request to the backend server.
Is there a way that I can make this work with map or do I need to create a separate server block for each of the backends that I want to proxy requests to.
From the proxy_pass documentation:
When variables are used in proxy_pass
. . .
In this case, if URI is specified in the directive, it is passed to the server as is, replacing the original request URI.
You should either remove the trailing / (the documentation calls this the optional URI) :
proxy_pass http://$backend:8080;
Or provide the full URL of the request (conveniently available as a built-in variable) :
proxy_pass http://$backend:8080$request_uri;

nginx reverse proxy sends all traffic to first defined server

I have multiple servers running on the same host. I am trying to configure nginx to route traffic based on the server_name, but all traffic is sent to the first defined server.
I have two urls:
example.domain.net
domain.net
which I have configured nginx to proxy with configuration:
server {
listen 3978;
listen [::]:3978;
server_name example.domain.net:3978 example.domain.net:3978;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass https://127.0.0.1:8443;
}
}
server {
listen 3978;
listen [::]:3978;
server_name domain.net:3978 www.domain.net:3978;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass https://127.0.0.1:8020;
}
}
But all traffic to both example.domain.net:3978 and domain.net:3978 is being sent to whichever server is defined first in the file (in this case example.domain.net)
I've seen other examples where this worked like This post. Is this possible with one having a subdomain and another not?
I am using nginx version 1.18.0 with the default nginx.conf on Ubuntu 18.04
server_name should not have ports. Try removing :3978 from the server_name.
Because you have the ports, the hostname does not match any of the server_name. So, the entire traffic is sent to the first server which is considered as a default for no matches.

How can I ensure nginx resolves variable DNS in proxy_pass?

I pass some requests to this location (this is a simplified version of the configuration for clarity). ONBOARDING_URL is set in the environment and replaced when the server is run by a URL. This URL is resolved to an IP once. It needs to be resolved for each request when the TTL has expired. Initially, the configuration looked like this:
Initial Configuration Template
server {
listen 443 ssl;
server_name localhost;
resolver ${AWS_DNS};
location /onboarding {
proxy_cache onboarding;
proxy_set_header Host $proxy_host;
proxy_cache_valid 200 10m;
proxy_cache_key $cacheKey;
proxy_set_header Host $proxy_host;
proxy_pass ${ONBOARDING_URL}/api-users;
}
}
I discovered this was only resolved one time. Changes to the IP underlying the DNS resolution had no effect; requests were routed to the IP that was resolved initially.
So following guidance in the proxy_pass documentation, and from other questions / sources (below), we modified the configuration.
https://serverfault.com/a/593003/310436
https://stackoverflow.com/a/26957754/2081835
https://forum.nginx.org/read.php?2,215830,215832#msg-215832
from: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
Parameter value can contain variables. In this case, if an address is specified as a domain name, the name is searched among the described server groups, and, if not found, is determined using a resolver.
The new configuration was intended to replace the template variable, populated from the environment at runtime, with a configuration variable that will always be resolved. However, it doesn't seem to be. Once an IP is resolved, it is used for the life of the process. Any ideas how to get around this? This seems a bit complex to test, too, which further complicates the fix.
New Configuration Template
server {
listen 443 ssl;
server_name localhost;
resolver ${AWS_DNS};
set $onboardingUrl ${ONBOARDING_URL};
location ~ ^/onboarding/?(.*)$ {
proxy_cache onboarding;
proxy_set_header Host $proxy_host;
proxy_cache_valid 200 10m;
proxy_cache_key $cacheKey;
proxy_set_header Host $proxy_host;
proxy_pass $onboardingUrl/api-users/$1;
}
}
Yet still, for the life of the process only the initially resolved IP is used.

Routing meant for a subdomain is also being applied to the root domain

Consider two websites hosted on the same server: domain.com and foo.domain.com. I want to start up a monitoring panel for each site on port 5555. Each site has a separate monitoring panel so I need to use nginx to route domain.com:5555 and foo.domain.com:5555 to two different places.
Here is the configuration for foo.domain.com:
server {
listen 5555;
server_name foo.domain.com;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://localhost:5678;
}
}
While this works fine for foo.domain.com:5555, it is also routing domain.com:5555 to the monitoring panel. This is acting like I had defined server_name domain.com foo.domain.com, but clearly I only defined it for foo.domain.com.
The only other nginx configs on the server are for ports 80 and 443. Neither of those configs use any wildcards and explicitly use the full name.
nginx always has a default server - if you do not define a default server, it will use the first server block with a matching listen directive.
If you want to discourage this behaviour, you will need to define a catch-all server for port 5555.
For example:
server {
listen 5555 default_server;
return 444;
}
See this document for more.

Nginx Reverse proxy config

I'm having issues with getting a simple config to work with nginx. I have a server that host docker containers so nginx is in a container. So lets call the url foo.com. I would like for the url foo.com/service1 to actually just go to foo.com on another port, so it would actually be pulling foo.com:4321 and foo.com/service2 to be pulling foo.com:5432 and so on. Here is the config I have been having issues with.
http {
server {
listen 0.0.0.0:80;
location /service1/ {
proxy_pass http://192.168.0.2:4321/;
}
location /service2/ {
proxy_pass http://192.168.0.2:5432/;
}
}
}
So the services and nginx live at 192.168.0.2. What is the prefered way to be able to do this? Thank you in advance!
As A side note, this is running in a docker container. Thanks!
I think you should check whether your foo.com is pointing to the right ip address or not first by removing the reverse proxy config. E.g.
http {
server {
listen 80;
server_name foo.com;
location / {
}
}
}
Then, if your ip address already has a service running on port 80 you should specify the server_name for each service like in my example. Nginx can only distinguish which service to respond to which domain by server_name.
*My guess is that you forgot the server_name option.
http {
server {
listen 80;
server_name foo.com;
location /service1/ {
proxy_pass http://192.168.0.2:4321/;
}
location /service2/ {
proxy_pass http://192.168.0.2:5432/;
}
}
}
I have a guess your problem is not related to Nginx per se, but instead it is related to Docker networking. You provided insufficient information to make a detailed conclusion, but here it is a couple of suggestions:
run a simple Docker container at the same host where nginx container is running and try curl from inside that container (I've seen your answer that you are able to call curl from the server running Nginx, but it's not really the same)
for example, if the server running nginx container is OSX or Windows, it may use an intermediate Linux virtual machine with its own network stack, IP addreses, routing, etc.
This is my conf sending to inner glassfish. Check out the use of proxy_redirect off & proxy_set_header X-NginX-Proxy true;
#Glassfish
location /MyService/ {
index index.html;
add_header Access-Control-Allow-Origin $http_origin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:18000/MyService/;
}

Resources