Use nginx upstreams module with a mix of SSL and non-SSL, with and without path - nginx

I want to create a reverse proxy which has 2 upstreams: a primary and a fallback one.
However the primary upstream talks http while the fallback upstream talks https.
Also, the fallback one has an api key as path component.
In theory I want something like this:
upstream backends {
server primary.mydomain.dev;
server fallback.notmydomain.com/v1/12345 ssl fallback;
}
server {
listen 80;
server_name proxy.example.com;
location / {
proxy_pass http://backends;
...
}
}
}
However my understanding is that I can't have URL paths in the upstreams module.
I could add the path to the proxy_pass directive, but then it would be applied regardless of the chosen upstream server.
Is what I want even possible with nginx?
If not, would it be possible with haproxy? How?

Related

nginx configuration file : server, server_name and upstream understanding

I have this nginx.conf configuration file inherited from a github project and i'd like some people to explain me what is doing what:
upstream hello_django {
server web:8000;
}
server {
listen 80;
server_name react-wagtail-api.accordbox.com;
location / {
proxy_pass http://hello_django;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
client_max_body_size 20M;
}
location /static/ {
alias /app/static/;
}
location /media/ {
alias /app/media/;
}
}
server {
listen 80;
server_name react-wagtail.accordbox.com;
location / {
root /usr/share/nginx/html/build;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
with
upstream hello_django {
server web:8000;
}
is web a service (elswhere there is a docker-compose container name which is web... is it a ref to that? ) ?
What does upstream define exactly?
with
server_name react-wagtail-api.accordbox.com;
what happens if i dont define a server_name for example in case i dont have yet a domain? is server_name the domain typed in the adress bar of the browser? can i define it as the local ip and let my domain name provider do the redirect? can i define it as the internet ip of the server and let my domain name provider do the redirect?
As there are two servers on the same port, can i define for example server_name my_internet_ip/app1 and server_name my_internet_ip/app2 to serve two servers on port 80?
Is web a service (elsewhere there is a docker-compose container name which is web... is it a ref to that?)
Generally, web here is an upstream domain name (can be also specified via IP address or UNIX socket path). However, when this is executed within the docker-compose context, at the nginx startup time it will be resolved to the web container internal IP using docker internal domain name resolving system.
What does upstream define exactly?
For this particular configuration, there will be no difference using either
upstream hello_django {
server web:8000;
}
server {
...
proxy_pass http://hello_django;
or specifying an upstream address directly in the proxy_pass directive:
server {
...
proxy_pass http://web:8000;
Really useful upstream use cases include failover (example) or load balancing (example). Read the ngx_http_upstream_module documentation to find out all the available features.
What happens if i don't define a server_name for example in case i don't have yet a domain?
To understand this part make sure you read the following two chapters from the official documentation:
How nginx processes a request
Server names
You can omit the server_name directive from the server block at all. For any HTTP request arriving at the TCP port where nginx is listening, one of the defined server blocks (usually the very first one appearing in the configuration, unless being specified explicitly using default_server parameter of listen directive) will act as the default server if more appropriate server block won't be found. The Host HTTP request header value is used to choose the most suitable server block here, being compared with the specified server_name for the server block, and it will be exactly what you typed at the browser address bar (assuming IP address/domain name being typed will actually point to the nginx server). That means there is no sense to specify the same server name for different server blocks listening on the same TCP ports - the first one will always be chosen to process such a request, and the nginx will complain with the
nginx: [warn] conflicting server name "..." on 0.0.0.0:80, ignored
warning message. For the developing purposes you can add the required domain names to the hosts system file, pointing them to your local machine, e.g.
127.0.0.1 react-wagtail.accordbox.com
127.0.0.1 react-wagtail-api.accordbox.com
This way you will be able to use those domains from your local browser, the generated HTTP requests will contain the proper Host header and will be processed with your local nginx server instance.
As there are two servers on the same port, can i define for example server_name my_internet_ip/app1 and server_name my_internet_ip/app2 to serve two servers on port 80?
No. Looks like you don't understand the internals of HTTP protocol. At the low level HTTP request will be something like
GET /app1 HTTP/1.1
Host: my_internet_ip
...
As you can see the host name and the request URL path are two completely different things. Usually this kind of tasks being solved using several location blocks:
server {
server_name example.com;
location /app1/ {
...
}
location /app2/ {
...
}
}
However it will require support from the underlying web apps. Two available options are
Referring assets using relative URIs, e.g.
<link rel="stylesheet" type="text/css" href="style.css">
or
<link rel="stylesheet" type="text/css" href="./style.css">
but not the using absolute URIs like
<link rel="stylesheet" type="text/css" href="/style.css">
Using the same URI prefix as specified in the location directive, e.g.
<link rel="stylesheet" type="text/css" href="/app1/style.css">
The reason should be obvious - every request for any first or second app assets should start with the /app1/ or /app2/ prefix for being handled with the proper location block.
As you can see, I'm using /app1/ and /app2/ suffixes here rather than /app1 and /app2. This is a very common mistake made by many people, to not understand the difference between those two. While it may seems to be an insignificant, in fact those two are drastically different in terms of browsing context which will be / in first case and /app1/ or /app2/ in second case. Unless you are proxying some kind of API endpoint but a whole web application, you probably want the second one. To make it more easy, a location directive special behavior provided by nginx:
If a location is defined by a prefix string that ends with the slash character, and requests are processed by one of proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed. In response to a request with URI equal to this string, but without the trailing slash, a permanent redirect with the code 301 will be returned to the requested URI with the slash appended. If this is not desired, an exact match of the URI and location could be defined like this:
location /user/ {
proxy_pass http://user.example.com;
}
location = /user {
proxy_pass http://login.example.com;
}
Although we can override the browsing context using the <base href="..."> HTML tag, I strongly recommend to not use this workaround and use a proper URI prefixes for your proxied web apps instead.
web service in your case resolves to some IP, same with server_name. You can read more about upstreams here, same for server_name directive.

Some sites can not be proxied? How ist this behaviour achieved?

I was having trouble configuring an nginx reverse proxy within my development environment when I stumbled on a behaviour that I do not quite get.
So nginx is listening on port 8080. When I make a request to my development-server, I can access my development server on
localhost:8080
With the following directives:
server {
listen 8080;
server_name site.com;
location / {
proxy_pass http://localhost:3000/;
proxy_redirect off;
}
But when I put a known website in the proxy pass_directive like google or apple the behaviour is different. I can not access e. g. apple.com as localhost:8080 with the following directives - I am immediately pushed to the real website and not the localhost:
server {
listen 8080;
server_name site.com;
location / {
proxy_pass http://apple.com/;
proxy_redirect off;
}
How is that behaviour called and how is it achieved? Can you guys put me in the right direction to understanding this? Thanks.
This is the correct behavior for the proxy service, you can find docs here https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
Some information regarding proxies here https://en.wikipedia.org/wiki/Proxy_server
Example: if you want to go to http://apple.com/apple-card/, you can point out to localhost:8080/apple-card and you will be redirected to /requested_path
I'm using proxies with docker containers just to route the requests to the correct application using different ports.

Is it possible in nginx while setting up reverse proxy to have server parameter as variable based on browser cookie in upstream module?

I am setting up nginx reverse proxy for routing/load distribution using upstream and server modules where same URL should proxy to 5 different servers running Apache, without changing the URL in the browser, depending on the value of the cookie. How do I accomplish server parameter in upstream to pass a variable based on cookie value?
I have tried mapping cookie value to server that I need to proxy to and then have used the server ip as a variable in "server_name" parameter. I am trying to use the same in "server" parameter in upstream, however nginx does not support that. I do not want to put all 5 servers in upstream as then nginx is trying to load balance across them, that is not the goal here. The goal is to distribute load to specific servers based on cookie value.
nginx.conf file
http {
include /etc/nginx/conf.d/*.conf;
upstream su.la.com {
server 100.0.8.2:443;
}
}
#The above works, making this $suhost, does not work.Any alternative
for this will be helpful.
suhosts.conf file under /etc/nginx/conf.d
map $cookie_su_ID $suhost {
default 100.0.8.2;
1 100.0.8.2;
3 100.0.8.6;
}
server {
set $bypass 0;
if ($remote_addr ~ "^(127.0.0.1)$") {
set $bypass $http_secret_header;
}
listen 80;
server_name su.la.com;
return 301 https://$host$request_uri;
}
server {
set $bypass 0;
if ($remote_addr ~ "^(127.0.0.1)$") {
set $bypass $http_secret_header;
}
listen 443 ssl;
server_name $suhost;
ssl_certificate /etc/nginx/certs/client.crt;
ssl_certificate_key /etc/nginx/certs/client.private.key;
location / {
proxy_pass https://su.la.com;
proxy_cache_bypass $bypass;
}
}
Expected results:
Based on map function described above, if su_id=1, the server parameter in upstream $suhost, should be picked up as 100.0.8.2 and https://su.la.com should route to 100.0.8.2, with no change in browser.
Actual Results:
nginx does not allow variable for server parameter, so need other alternatives or approach to make this happen. If server parameter is given a value for a single server, this works. We need to make it work for routing to multiple servers, single server at a time, based on cookie value.

Nginx Reverse Proxy upstream not working

I'm having trouble figuring out load balancing on Nginx. I'm using:
- Ubuntu 16.04 and
- Nginx 1.10.0.
In short, when I pass my ip address directly into "proxy_pass", the proxy works:
server {
location / {
proxy_pass http://01.02.03.04;
}
}
When I visit my proxy computer, I can see the content from the proxy ip...
but when I use an upstream directive, it doesn't:
upstream backend {
server 01.02.03.04;
}
server {
location / {
proxy_pass http://backend;
}
}
When I visit my proxy computer, I am greeted with the default Nginx server page and not the content from the upstream ip address.
Any further assistance would be appreciated. I've done a ton of research but can't figure out why "upstream" is not working. I don't get any errors. It just doesn't proxy.
Okay, looks like I found the answer...
two things about the backend servers, at least for the above scenario when using IP addressses:
a port must be specified
the port cannot be :80 (according to #karliwsn the port can be 80 it's just that the upstream servers cannot listen to the same port as the reverse proxy. I haven't tested it yet but it's good to note).
backend server block(s) should be configured as following:
server {
# for your reverse_proxy, *do not* listen to port 80
listen 8080;
listen [::]:8080;
server_name 01.02.03.04;
# your other statements below
...
}
and your reverse proxy server block should be configured like below:
upstream backend {
server 01.02.03.04:8080;
}
server {
location / {
proxy_pass http://backend;
}
}
It looks as if a backend server is listening to :80, the reverse proxy server doesn't render it's content. I guess that makes sense, since the server is in fact using default port 80 for the general public.
Thanks #karliwson for nudging me to reconsider the port.
The following example works:
Only thing to mention is that, if the server IP is used as the "server_name", then the IP should be used to access the site, means in the browser you need to type the URL as http://yyy.yyy.yyy.yyy or (http://yyy.yyy.yyy.yyy:80), if you use the domain name as the "server_name", then access the proxy server using the domain name (e.g. http://www.yourdomain.com)
upstream backend {
server xxx.xxx.xxx.xxx:8080;
}
server {
listen 80;
server_name yyy.yyy.yyy.yyy;
location / {
proxy_pass http://backend;
}
}

Error with IP and Nginx as reverse proxy

I configured my Nginx as simple reverse proxy.
I'm just using basic setting
location / {
proxy_pass foo.dnsalias.net;
proxy_pass_header Set-Cookie;
proxy_pass_header P3P;
}
The problem is that after some time (few days) the site behind nginx become unaccessible. Indead nginx try to call a bad ip (the site behind nginx is at my home behind my box and I'm a using a dyn-dns because my ip is not fixe). This dyn-dns is always valid (I can call my site directly) but for obscure reason Nginx get stuck with that..
So as said, nginx just give me 504 Gateway Time-out after some time. It looks like the error come when my ip change at home.
Here is a sample of error log:
[error] ... upstream timed out (110: Connection timed out) while connecting to upstream, client: my.current.ip, server: myreverse.server.com, request: "GET /favicon.ico HTTP/1.1", upstream: "http://my.old
.home.ip", host: "myreverse.server.com"
So do you know why nginx is using ip instead of the DN ?
If the proxy_pass value doesn't contain variables, nginx will resolve domain names to IPs while loading the configuration and cache them until you restart/reload it. This is quite understandable from a performance point of view.
But, in case of dynamic DNS record change, this may not be desired. So two options are available depending on the license you possess or not.
Commercial version (Nginx+)
In this case, use an upstream block and specify which domain name need to be resolved periodically using a specific resolver. Records TTL can be overriden using valid=time parameter. The resolve parameter of the server directive will force the DN to be resolved periodically.
http {
resolver X.X.X.X valid=5s;
upstream dynamic {
server foo.dnsalias.net resolve;
}
server {
server_name www.example.com;
location / {
proxy_pass http://dynamic;
...
}
}
}
This feature was added in Nginx+ 1.5.12.
Community version (Nginx)
In that case, you will also need a custom resolver as in the previous solution. But to workaround the unavailable upstream solution, you need to use a variable in your proxy_pass directive. That way nginx will use the resolver too, honoring the caching time specified with the valid parameter. For instance, you can use the domain name as a variable :
http {
resolver X.X.X.X valid=5s;
server {
server_name www.example.com;
set $dn "foo.dnsalias.net";
location / {
proxy_pass http://$dn;
...
}
}
}
Then, you will likely need to add a proxy_redirect directive to handle redirects.
Maybe check this out http://forum.nginx.org/read.php?2,215830,215832#msg-215832
resolver 127.0.0.1;
set $backend "foo.example.com";
proxy_pass http://$backend;
In such setup ip address of "foo.example.com" will be looked up
dynamically and result will be cached for 5 minutes.

Resources