Nginx reverse proxy configuration to handle absolute paths - nginx

I am running a web application on http://localhost:9000
that I would like to reverse proxy to http://localhost/myapplication.
The problem is that the application is using absolute paths link which is translated to http://localhost/some-directory/some-file.html with my current configuration.
How do I configure nginx to handle these paths so that they point to http://localhost/myapplication/some-directory/some-file.html instead?
Right now the proxy pass configuration looks like this:
location /myapplication/ {
proxy_pass http://localhost:9000/;
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;
}

Similar question to https://serverfault.com/questions/714322/nginx-proxy-pass-and-absolute-paths. So here is my answer from over there (slightly adapted for your example):
The only way I found to make this work is to use the HttpSubModule and adding sub_filter directives. So given your example it could look like this:
sub_filter 'href="/' 'href="/myapplication/';
Obviously the more specific your matching is the more variants you'll have to add. If you go less specific, like just match "/ or '/ then you need less rules but run into the danger of substituting the wrong thing.
You probably also need to add:
sub_filter_types *;
So it doesn't just match text/html (which is the default) but also javascript and css files. Obviously * is the lazy approach which might break things and using specific mimetypes should be preferred.
Ultimately the correct way is to fix the web application. Most web frameworks support something like a base url/root url/url prefix (there doesn't seem to be standard name for this) which you can set to avoid exactly this problem.

Related

Nginx reverse proxy prevent changing host for other locations

I’m trying to get this done for a couple of days and I can’t. I’m having two web apps, one production and one testing, let’s say https://production.app and https://testing.app. I need to make a reverse proxy under https://production.app/location that points to https://testing.app/location (at the moment I only need one functionality from the testing env in prod). The configuration I created indeed proxies this exact location, but that functionality also loads resources from /static directory resulting in request to https://production.app/static/xyz.js instead of https://testing.app/static/xyz.js, and /static can’t be proxied. Is there a way to change headers only in this proxied traffic so that it’s https://testing.app/static (and of course any other locations)?
Below is my current config (only directives regarding proxy):
server {
listen 443 ssl;
server_name production.app;
root /var/www/production.app;
location /location/ {
proxy_pass https://testing.app/location/;
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;
}
}
Have a good day :)
Your question title isn't well written. It has nothing to do about "changing headers", and nginx location, while being essentially tied to the request processing, isn't the right term there too. It should be something like "how to serve the assets requested by proxied page in a different way than the others".
Ok, here we go. First of all, do you really understand what exactly does the following config?
location /location/ {
proxy_pass https://testing.app/location/;
}
The URI prefix /location/ specified after the https://testing.app upstream name has the following effect:
The /location/ prefix specified in the location directive truncated from the processed URI.
Processed UIR is being prepended with the /location/ prefix specified in the proxy_pass directive before being passed to the upstream.
That is, if these prefixes are equal, the following configuration will do the same eliminating those steps (therefore processing the request slightly more efficiently):
location /location/ {
proxy_pass https://testing.app;
}
Understanding this is a key concept to the trick we're going to use.
Since all the requests for static assets made from the /location/ page will have the HTTP Referer header equal to http://production.app/location/ (well, unless you specify no-referer as you page referrer policy, and if you are, the whole trick won't work at all unless you change it), we can rewrite a request URI that matched some conditions (say start with the /static/ or /img/ prefix) redirecting it to some special internal location using the following if block (should be placed at the server configuration level):
if ($http_referer = https://production.app/location/) {
rewrite ^/static/ /internal$uri;
rewrite ^/img/ /internal$uri;
...
}
We can use any prefix, the /internal one used here is only as example, however used prefix should not interfere with your existing routes. Next, we will define that special location using the internal keyword:
location /internal/ {
internal;
proxy_pass https://testing.app/;
}
Trailing slash after the http://testing.app upstream name here is the essential part. It will make the proxied URI returning to its original state, removing the /internal/ prefix added by the rewrite directive earlier and replacing it with a single slash.
You can use this trick for more than one page using regex pattern to match the Referer header value, e.g.
if ($http_referer ~ ^https?://production\.app/(page1|page2|page3)/) {
...
}
You should no try this for anything else but the static assets or it can break the app routing mechanism. This is also only a workaround that should be used for testing purposes only, not something that I can recommend for the production use on a long term basis.
BTW, are you sure you really need that
proxy_set_header Host $host;
line in your location? Do you really understand what does it mean or you are using it just copy-pasting from some other configuration? Check this answer to not be surprised if something goes wrong.

Multiple "relays" in NGINX - is it possible?

Is it possible to configure NGINX to something like multiple reverse-proxy? So, instead of one proxy_pass:
location /some/path/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://localhost:8000;
}
to have multiple proxy_passs (relays)? I need something like load balancer but to send not to one of them, but TO ALL (see the scheme below).
TO BE ACCURATE: IT'S NOT REVERSE PROXY AND IT IS NOT LOAD BALANCING AS WELL.
The response may be retrieving from any of them, maybe the last one or even to be "hardcoded" with some configuration directives - it does not matter (it's enough to be HTTP 200 and will be ignored)... So, the scheme should look like:
.----> server 1
/
<---> NGINX <-----> server 2 (response from 2nd server, but it maybe any of them!)
\
`----> server 3
Maybe some extension for NGINX? Is it possible at all and how to do it if it is?
Yes, it is possible.
What you are searching for is called mirroring. And nginx implements it since version 1.13.4, see the directive mirror for more info.
Example:
location = /mirror1 {
internal;
proxy_pass http://mirror1.backend$request_uri;
}
location = /mirror2 {
internal;
proxy_pass http://mirror2.backend$request_uri;
}
...
location /some/path/ {
mirror /mirror1;
mirror /mirror2;
proxy_pass http://primary.backend;
}
(you can also specify it for whole server (or even http) and disable for locations where you don't need it).
Alternatively you could try post_action (but this is undocumented feature and if I remember correctly is deprecated).

nginx `proxy_redirect` in traefik (redirect without changing url)

basically how this will look like in traefik if it is possible:
location /blog/ {
proxy_pass https://blog.example.com/;
proxy_redirect https://blog.example.com/ https://www.example.com/blog/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host blog.example.com;
}
I tried, this config in my docker-compose:
- 'traefik.frontend.redirect.regex=^https://example.com/blog/(.*)$$'
- 'traefik.frontend.redirect.replacement=https://blog.example.com/$$1'
this does work, but it just redirects, to https://blog.example.com, I want to keep original url https://example.com/blog/ and show content from https://blog.example.com.
is it possible to achieve with the traefik?
In Traefik, that redirect label is for interaction with client (sending your browser to another location)
I am still trying to figure out what is the exact replacement for proxy_redirect. As per Nginx docs, this directive:
Sets the text that should be changed in the “Location” and “Refresh” header fields of a proxied server response.
So in docker-compose.yaml those two header fields could be placed like this:
labels:
- "traefik.frontend.headers.customResponseHeaders=Location:value1||Refresh:value1"
But I'm not sure how to replace them with regex. Maybe there is a more "traefikal" approach out there.
I know this isn't a real answer, but I can't post a comment due to my low points.
I found your question while looking for a solution for Jenkins behind Traefik SSL.

phpMyAdmin inside docker container via nginx reverse proxy

I installed phpMyAdmin docker image and run it with
# docker run --name phpmyadmin -d --link mariadb:db -p 8081:80 -e PMA_ABSOLUTE_URI=http://servm3/pma --restart unless-stopped phpmyadmin/phpmyadmin
Accessing http://servm3:8081 works fine. The variable PMA_ABSOLUTE_URI is for reverse proxies as seen on the docker page.
Then I set up nginx (locally installed, not inside docker) to act as a reverse proxy (working for several other apps like guacamole).
Inside my nginx.conf I have:
location /pma/ {
proxy_pass http://localhost:8081/;
proxy_buffering off;
}
Accessing http://servm3/pma shows the favicon on the browser tab but instead of the login page only a blank page is shown. Removing the preceding "/" and restarting nginx only gives a 404.
# docker logs phpmyadmin
shows nothing except from the php and nginx service start info, nothing related to phpmyadmin.
Local nginx access log shows several 304 and 404 codes and some 200, error log is not present. Detailled log can be found here on pastebin.
I hope somebody will be able to tell me how to make nginx work as a reverse proxy for the phpMyAdmin docker container.
If some important information is missing please let me know.
Be sure to include the rewrite:
location ~ \/pma {
rewrite ^/pma(/.*)$ $1 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://localhost:8081;
}
You'll also want to set the PMA_ABSOLUTE_URI environment variable in your docker-compose.yml:
PMA_ABSOLUTE_URI: https://yourdomain.com/pma/
Provided you're running 4.6.5 or later of the docker phpmyadmin you should be set. To update you can docker pull to pull down the latest. i.e.
docker pull phpmyadmin/phpmyadmin
Just remove the ending backslash of /pma/:
location /pma {
proxy_pass http://localhost:8081/;
proxy_buffering off;
}
With it the browser treats it as a directory and request for assets accordingly, which is unexpected for PMA.
Don't need rewrite.
nginx.conf:
location ^~ /pma/ {
proxy_pass http://pma-container/;
absolute_redirect off;
}
docker-compose.yml:
PMA_ABSOLUTE_URI: https://yourdomain.com/pma/
Notice: keep trailing slash on location, proxy_pass, PMA_ABSOLUTE_URI
There's not a lot that can be done. Problem is that phpmyadmin is serving its pages from localhost, and even if nginx translates accesses from http://servm3/pma to http://localhost, all links in HTML content ignore the lattest and, even if they're relative, they don't take into account the /pma part. So, all those 404 errors you're seeing are from resources that, inside HTML, are referenced as relative links like styles.css, that when referenced/clicked become http://servm3/styles.css, that doesn't exist in the server.
Unfortunately reverse proxies work at the header level so, even if they are able to change headers like Location on the fly, they leave HTML content untouched, and hence the problems. There're workarounds involving changing HTML code on the fly, but they're not easy, are unreliable at most and they hinder performance considerably, so the only practical solution is that websites explicitly support some kind of base path setting. In this case, the solution would be that phpmyadmin Docker image allowed setting the base path using an environment variable in docker-compose.yaml, instead of defaulting to root.
Another workaround in the mean time would be not using a relative path, but a subdomain. If you're in control of DNS settings for servm3, you could use something like phpmyadmin.servm3, and proxy_pass without problems.
If you have a docker setup that will have many different domains but one database(like a multistore magento shop, or a multi domain wordpress) it might be undesireable to forward all the traffic to one single domain. Or even having to hardcode it in the ENV variable.
It's easier to portforward based on subdomain.
In your nginx config define a map, listing your port numbers
map $subdomain $port_number {
default "013";
phpmyadmin "014";
mailhog "017";
}
This will make it so that when the variable $subdomain matches any entry in the map below, the variable $port_number will be set to the corresponding value. In my example is default "013" if nothing is matched.
Then add the following directive to your server directive.
server_name server_name ~^(?:(?<subdomain>[^.]+)\.|)(?<base_domain>.+\.localhost)$;
These regexes set 2 variables $subdomain and base_domain
If the request is example.com.localhost
$subdomain will have example
$base_domain will have com.localhost
$port_number will have 013
If the request is example.com.localhost
$subdomain will have phpmyadmin
$base_domain will have example.com.localhost
$port_number will have 014
[Optional] If you need the actual domain in a variable you could use an if statement like this:
set $lookup_domain $host;
if ($port_number != "013") {
set $lookup_domain $base_domain;
}
and $lookup_domain will contain the actual domain you're interested in.
You can then use a proxy pass like this:
location / {
proxy_pass http://docker-host:${backend}${port_number};
proxy_pass_header Content-Type;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # allow websockets
proxy_pass_header Connection;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
}
Where the $backend variable contains in my case the base port range for my webapp set.
So the proxy url becomes someting like http://docker-host:20013 depending on the app. Feel free to skip that and set the ports to actual ports or whatever, or to modify it to follow any other logic.
don't know if you're still looking for an answer.
just follow this answer from Joshua Ostrom.
The problem is I dont know why it doesn't behave like it should but, you need to add index.php at the end like https://yourdomain.com/pma/index.php.
I had the same issue

Multiple TYPO3s behind nginx issue

I want to host several TYPO3s behind an nginx reverse proxy using the following structure:
Internal server (apache):
http://some.internal.ip/site1
http://some.internal.ip/site2
http://some.internal.ip/site3
External server (nginx, reverse proxy):
http://site1.mydomain.com
http://site2.mydomain.com
http://site3.mydomain.com
So here're the relevant parts of my nginx.conf
http {
...
server {
listen 80;
server_name site1.mydomain.com;
location / {
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 http://some.internal.ip/site1$request_uri;
}
}
...
server {
(like above with site2 and so on)
...
}
(I quickly rewrote the configuration from https to http in order not to make things more complicated then necessary.)
Now, the nginx-Part of this seems to work - it's the Typo3 that seems to cause the trouble as all elements on the site are referred to as
http://some.internal.ip/site1/example.php
instead of
http://site1.mydomain.com/example.php
I'm aware of
[SYS][reverseProxyIP]
and
[SYS][reverseProxyHeaderMultiValue]
which I set accordingly, but that doesn't seem to work - which in turn is what should be expected based on my understanding.
Is there another way to approach this issue or am I simply trying something that simply can't be done this way?
Are you using config.baseUrl = http://some.internal.ip/site1/ on your website?
If you are, I recommend you to use config.absRefPrefix = / instead.
See also Søren Mallings article from 2013 which is still correct.

Resources