Proper handling of request_uri in double nginx reverse proxy? - php

So, essentially I am running Joomla in a Docker php7-fpm container, then I have an nginx container where a joomla.conf file is defined as follows:
#https://docs.joomla.org/nginx
server {
listen 8081;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
server_name php-docker.local;
root /usr/src/joomla;
index index.php index.html index.htm default.html default.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
# deny running scripts inside writable directories
location ~* /(images|cache|media|logs|tmp)/.*\.(php|pl|py|jsp|asp|sh|cgi)$ {
return 403;
error_page 403 /403_error.html;
}
location ~ \.php$ {
fastcgi_pass joomla:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
#include /etc/nginx/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
And this works as expected... going to http://:8081 loads everything correctly.
Now, 8081 is just temporarily exposed in the nginx container, what I essentially want to do is setup a reverse proxy such that http:///joomla will be the final endpoint.
For this, I am struggling with the following conf file:
server{
listen 80;
server_name _;
location /joomla/ {
proxy_pass http://localhost:8081/;
proxy_set_header Referer $http_referer;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
}
}
What happens is that the HTML is served correctly, however, none of the assets are. This is because the URLs in Joomla are generated by a JURI class, which seems to rely on $request_uri, which by when it appears to arrive to Joomla is already lost.
https://github.com/joomla/joomla-cms/blob/6ab2a6e9010e7e04c260b9eba17dc76e866dd3e6/libraries/joomla/uri/uri.php#L87
So every link or reference to a file, script, or css renders like this:
http://localhost/login
http://localhost/images/headers/maple.jpg
Instead of:
http://localhost/joomla/login
http://localhost/joomla/images/headers/maple.jpg
However, when I access the second set of URL, I can access the link/asset without a problem... but of course once again, no images, templates, js or links being rendered correctly.
I prefer not to touch joomla.conf unless something is wrong, as for site.conf I would only like to translate URI segments to map requests to other applications, e.g.:
/joomla -> localhost:8081
/phpbb -> localhost:8082
/someapp -> localhost:8083

The cleanest solution would be to modify your upstreams to provide unique paths for all the resources.
It is generally a much easier task to remove parts of a URL from the upstream (with a unique prefix) than to add extra parts to a non-unique one. This is because you can always catch the longer URL and know exactly what it refers to, subsequently returning a 301 or 302 redirect to a shorter and more concise version.
On the other hand, faced with a short request URL, like /, it would be difficult to know for sure which app it may refer to (unless you look into the $http_referer variable, too, and then conditionally issue the redirect based on where the URL request comes from), or unless you implement some sort of spaghetti rules to detect which individual URLs refer to which applications (if you go this route, the map directive may come in handy).
Additionally, consider that, security-wise and when cookies get involved, it is not the best practice to run multiple independent applications on a single domain — a compromise in one application can easily lead to security violations in all the other ones.

As Rinos said, you need to configurate the $live_site var in configuration.php, as this:
public $live_site = '/joomla/';
I've made a complete example in Github that works well after editing that file, and it uses your configs.
 Becomes:

You could try use sub_filter how it mentioned in this answer.
So in your case your nginx config should looks like this one:
server{
listen 80;
server_name _;
location /joomla/ {
proxy_pass http://localhost:8081/;
proxy_set_header Referer $http_referer;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
sub_filter "http://your_server/" "http://your_server/joomla/";
sub_filter_once off;
}
}

I know this is old but here's my answer. You shouldn't change the configuration.php file, basically because you should keep your services (web and proxy) completely separated (A problem generated by a service implementation should be solved in that service), that will make your service architecture more flexible.
You have to filter the assets urls and some Location headers changing the following proxy settings.
server {
#... other settings
location /joomla/ {
# Main redirect
proxy_pass http://joomla_service/;
# Set the Host Header
proxy_set_header Host $host;
# When Joomla redirects a Location Header
# For example when you log in, Joomla retrieves you a response with a Location Header = localhost
# we don't want this.
proxy_redirect http://localhost/ http://localhost/joomla/;
proxy_redirect / /joomla/;
# Some content rewriting
sub_filter_types text/javascript application/javascript;
# Html redirection
sub_filter '="/' '="/joomla/';
sub_filter '="http://localhost/' '="/joomla/';
sub_filter '\'http://localhost/administrator/' '\'/joomla/administrator/';
sub_filter 'url(\'/media' 'url(\'/joomla/media';
# Some json data
sub_filter '"root":""' '"root":"\/joomla"';
sub_filter '"uri":"\/index.php' '"uri":"\/joomla\/index.php';
sub_filter '"jdragdrop":"\/media' '"jdragdrop":"\/joomla\/media';
sub_filter '"plg_quickicon_privacycheck_url":"http:\/\/localhost' '"plg_quickicon_privacycheck_url":"\/joomla';
sub_filter '"plg_quickicon_privacycheck_ajax_url":"http:\/\/localhost' '"plg_quickicon_privacycheck_ajax_url":"\/joomla';
sub_filter '"content_css":"\/templates' '"content_css":"\/joomla\/templates';
sub_filter_once off;
}
}
For some reason Joomla is not doing a good support if we talk about implement a reverse proxy. I created an example with more details, you can see it here.

Related

Getting a user's IP address in Laravel through Docker containers and Nginx webserver

Good day,
My problem is that I am ONLY getting my Docker network IP address when using Laravel's $request->ip() or $request->getClientIp () or $request->server->get('REMOTE_ADDR') or $_SERVER['REMOTE_ADDR'] which essentially all do the same I assume.
So far the problem occurs on my local test pc (without Nginx Reverse Proxy) as well as on my live servers
I hope I've supplied enough relevant info below, if not please let me know :) Thanks in advance!
So my setup is the following (simplified), both Nginx configs are pasted below as well:
Server -> Nginx Reverse Proxy -> Docker Containers & Network (db, php, nginx, redis) -> Nginx Webserver <-> PHP-FPM
Some results related to _SERVER:
[SERVER_ADDR] => 172.20.0.3 (nginx webserver)
[REMOTE_PORT] => 55378
[REMOTE_ADDR] => 172.20.0.1 (docker network / gateway)
[SERVER_SOFTWARE] => nginx/1.19.6
I have read multiple forums and solutions for hours and hours while trying and changing configs etc. but none of it seems to be working.
What I have tried:
Get User IP address in laravel with similar method to HTTP_X_FORWARDED_FOR
https://serverfault.com/questions/618456/getting-the-client-ip-when-passing-through-a-reverse-proxy
So I have also tried to set my "Trusted Proxy" in Laravel's /Http/Middleware/TrustProxies.php by allowing all: protected $proxies = '*';
I also don't need these additional Laravel packages as of >5.5 I believe this is a built in feature of Laravel (TrustProxies).
Please find below my Nginx webserver config:
server {
listen 80;
server_name domain.com;
return 301 https://www.example.com;
}
server {
listen 80 default_server;
index index.html index.htm index.php;
server_name www.example.com;
root /var/www/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-fpm:9013;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/example.com/public/$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
# Support Clean (aka Search Engine Friendly) URLs & enable Gzip compression:
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
# Override the load balancer IP with real IP.
fastcgi_param REMOTE_ADDR $http_x_real_ip;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
And my server's Nginx Reverse Proxy config (IF relevant):
server {
listen 80;
listen [::]:80;
server_name example.com.au www.example.com.au;
location / {
proxy_pass http://localhost:8013;
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-Host $server_name;
}
}
Ok figured it out! :D
I was using Cloudflare as a proxy, my DNS is with CloudFlare and I have enabled the "Proxy through CloudFlare" option due to speed / caching improvements.
This resulted in CloudFlare "injecting" a HTTP_CF_CONNECTING_IP into my header.
To retrieve this header value I have used $_SERVER['HTTP_CF_CONNECTING_IP'] in my code.
Another solution was to use the HTTP_X_FORWARDED_FOR value from the header by using _SERVER['HTTP_X_FORWARDED_FOR'], this I believe would, in my situation only work after doing the following (posted in my question):
TrustProxies.php
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
I hope this helps others as well :D

how to correctly use url paths point to laravel apps using nginx?

I have the following nginx config
server {
listen 80;
client_max_body_size 2M;
server_name some.app;
root /var/virtual/a-cakephp-app/webroot;
access_log /var/log/nginx/a-cakephp-app-access.log;
include common.conf;
include cakephp.conf;
location /billing/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:89;
proxy_redirect off;
rewrite ^/billing/(.*)$ /$1 break;
}
And my webapps are:
/var/virtual/a-cakephp-app ==> virtual path that leads to the cakephp folder (definitely working)
/var/virtual/a-laravel-app ==> virtual path that leads to the laravel folder (not too sure how to test it)
What I want to achieve
I have a cakephp 2 app that is running at http://some.app. What I want is to start another app running Laravel at http://some.app/billing
My Laravel .env
APP_ENV=local
APP_DEBUG=true
APP_KEY=base64:somekey
APP_URL=http://some.app/billing
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
What did I get
I got a bad gateway error
What did I expect
I was hoping that the laravel app can work without compromising the cakephp app
It is generally a bad idea to mix and match independent apps on a single domain, which may lead to security issues w.r.t. cookies.
You're trying to use a proxy_pass for Laravel, a PHP framework. Note that this directive is meant to be used when the request is meant to be passed, in HTTP over TCP form, to a subsequent HTTP server, running on the specified port (e.g., Apache Tomcat, Jetty etc).
PHP (and hence Laravel) can be run by nginx itself, and doesn't require a separate server, so, your proxy_pass setup is likely a mistake, and should have been an appropriate fastcgi_pass set of directives instead (the normal way php is executed from within nginx).
We have no idea what is in the files that you include, however, it would be a good guesstimate that they do contain a location ~ \.php$ directive (for handling the php files of your existing php app).
Note that as per http://nginx.org/r/location, a location with a regular expression like location ~ \.php$ will take precedence over a prefix-string location like location /billing/, when a file like /billing/index.php is accessed.
To modify such behaviour, use the ^~ specifier for the prefix string location, e.g., location ^~ /billing/.
In summary, I'd use a separate domain. Else, use location ^~ /billing/, and fit all the proper fastcgi within.
You need to tell nginx that the root is different for /billing/ paths:
...
location /billing/ {
...
root /var/virtual/a-laravel-app/public;
# this replaces the rewrite
# rewrite will alter the url in nginx and a new lookup will be made
# the entry point for Laravel is the public/index.php file
try_files /index.php =404;
....
}
...
I kept the original config as such:
server {
listen 80;
client_max_body_size 2M;
server_name some.app;
root /var/virtual/a-cakephp-app/webroot;
access_log /var/log/nginx/a-cakephp-app-access.log;
include common.conf;
include cakephp.conf;
location /billing/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:89;
proxy_redirect off;
rewrite ^/billing/(.*)$ /$1 break;
}
Then I created this config after that:
server {
listen 89;
client_max_body_size 2M;
server_name 127.0.0.1;
root /var/virtual/a-cakephp-app/another-laravel-app/public;
include common.conf;
include cakephp.conf;
}
Note the port 89 does not matter so long as the server is not using it for some other app
Ideally you should not mix all the configurations under one file. nginx.conf should only only contain universal configurations like gzip being on, not to give out server tokens, etc. The individual file should be under sites-enabled folder
On why the bad gateway error is coming is maybe because you should have one root and then multiple location blocks to handle the same.
Also, once inside the billing block, are you trying to rewrite it to remove the billing folder? Why?

Nginx is throwing 'no input file specified' when accessing siteb.com/blog

Ive looked at this for hours, but am still not getting where I need to get to despite doing a lot of hours googling.Nginx is throwing no input file specified
my config in /etc/nginx/sites-enabled/siteb looks like:
upstream site {
#flask app
server 127.0.0.1:8001;
}
upstream siteb-blog {
#wordpress php
server unix:/var/run/php5-fpm.sock;
}
server {
server_name siteb.com;
listen 80;
root /home/www/flask-deploy/siteb;
location ~* ^blog/ {
try_files $uri $uri/ /blog/index.php?$query_string;
root /home/www/flask-deploy/siteb-blog;
location ~ \.php$ {
fastcgi_pass siteb-blog;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
}
}
location / {
try_files #proxy #proxy;
}
location #proxy {
internal;
proxy_pass http://siteb;
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;
}
location /static {
alias /home/www/flask-deploy/siteb/static/;
}
}
However, when i try to access mysite.com/blog or mysite.com/blog/info.php i get a 404 error.
ive checked error.log and it doesnt show any errors. access.log, just shows:
[16/Dec/2016:16:11:56 -0500] "GET /blog/index.php HTTP/1.1" 404 209 "-" "Mozilla/5.0 (X11; Ubuntu; Linux armv7l; rv:50.0) Gecko/20100101 Firefox/50.0"
My siteb.com works fine. siteb.com/blog does not.
im specifically trying to get siteb-blog working at siteb.com/blog.
Thank you in advance!
You have WordPress installed under a directory called siteb-blog and would like to access it with a URI prefix of /blog/. One of your problems is that the $request_filename is constructed from the root and URI, so that the /blog segment becomes part of the pathname. See this document for details.
The simple solution with static files is to use the alias directive, but I try to avoid using it with try_files because of a long standing bug.
My preferred solution is to silently (using internal rewrite) map the /blog URI to /siteb-blog, then continue to use root as normal.
For example:
server {
server_name siteb.com;
listen 80;
location /blog {
rewrite ^/blog(.*)$ /siteb-blog$1 last;
}
location /siteb-blog {
root /home/www/flask-deploy;
try_files $uri $uri/ /blog/index.php?$query_string;
location ~ \.php$ {
try_files $uri /blog/index.php;
fastcgi_pass siteb-blog;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
}
}
location / {
proxy_pass http://siteb;
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;
}
location /static {
root /home/www/flask-deploy/siteb;
}
}
You can make the location /siteb-blog internal, by adding the internal directive. This will make it inaccessible directly.
A root statement in both the server block and the location /static block is unnecessary - it only needs to appear once.
The alias was unnecessary - root is preferred. See this document for details.
The named location was unnecessary - merged with location /
Finally, WordPress needs to know that it is hosted under /blog, otherwise it cannot find its resource files (css & js). You will need to set the values for WP_SITEURL and WP_HOME either using wp-config.php or using the WordPress ⇒ DashBoard ⇒ Settings ⇒ General page.

Redirect to wordpress blog through nginx

I am new to both, the nginx and the wordpress.
I have configured my project to be served through the nginx server. Now we need to add the wordpress blog support to our project.
My nginx is running over port 80. And the Apache is running over 8181 where my wordpress is installed.
Now any url going to blog is redirected to the apache over 8181 through my nginx server.
So below is my nginx redirect for the blog urls.
location /blog {
proxy_pass http://127.0.0.1:8181/blog/;
proxy_redirect https://192.168.1.54 http://127.0.0.1:8181/blog;
proxy_read_timeout 3500;
proxy_connect_timeout 3250;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header SSL_PROTOCOL $ssl_protocol;
proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;
proxy_set_header SSL_CLIENT_VERIFY $ssl_client_verify;
proxy_set_header SSL_SERVER_S_DN $ssl_client_s_dn;
}
For the Google page speed insight, I did below config.
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff)$ {
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
}
Now, the problem I am facing here is that, when some css or js request comes from the wordpress eg. http://192.168.1.54/blog/wp-content/themes/twentysixteen/style.css?ver=4.6 is getting caught inside the location config written for css ie. location ~* .(?:ico|css|js|gif|jpe?g|png|woff)$ Hence wordpress is not able to get the css or js.
So, I need your help where the any URL requested for blog on the nginx should be redirected to Apache server on 8181 port. Including the js and css.
Where as other resource related url like https://192.168.1.54/js/somejs.js should also work.
I did some config change in wordpress file wp-config.php
define('WP_HOME', 'https://192.168.1.54/blog');
define('WP_SITEURL', 'https://192.168.1.54/blog');
You should use the ^~ modifier on the location /blog block to prevent conflict with regular expression location blocks at the same level. See this document for details.
location ^~ /blog {
proxy_pass http://127.0.0.1:8181;
...
}
Also, you should probably remove the URL element from the proxy_pass directive, because it looks like you are mapping /blog to /blog anyway.
And the proxy_redirect statement is back to front. But you probably do not need it as you have set WP_HOME/WP_SITEURL.

nginx try_files, proxy_pass and upstream

I'm building a dockerised testing 'platform' for PHP apps - specifically (for the moment) for WordPress. I'm using PHPFarm to serve different versions of PHP on different ports. Using nginx in front, I've got much of it working. ( https://github.com/richardtape/testit is the main repo )
The big issue I'm facing now is getting WordPress's "pretty permalinks" to work. In a standard nginx setup, it's just a case of something like
location / {
index index.php index.html index.htm;
try_files $uri $uri/ /index.php?$args;
}
But in order to be able to have nice urls from the host machine, and in order to have one code base, I'm using something along the following lines:
server {
listen 80;
index index.php index.html index.htm;
server_name 52.spaces.dev;
location / {
proxy_pass http://phpfarm_52;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
root /var/www;
}
upstream phpfarm_52{
server phpfarm:8052;
}
This, as it stands, works. (There are 5 more rules similar for this for PHP 5.3, 5.4, 5.5, 5.6 and 7) The home page loads on each of the different server_names from the host machine (and if you output the PHP version on each of them, you can see that you're getting a different PHP version).
However, the second I switch to an 'internal' url (or any non-root i.e. http://52.spaces.dev/about/), I get a 404. I've tried something similar to
location / {
try_files $uri $uri/ /index.php?$args
}
location ~ \.php$ {
proxy_pass http://phpfarm_52;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
I get a redirect loop, depending on a few different ways I've tried it's either just been a series of 301 redirects and the page never loads or an error such as
nginx_1 | 2016/04/08 20:31:29 [error] 5#5: *4 rewrite or internal redirection cycle while processing "/index.php", client: 192.168.99.1, server: 52.spaces.dev, request: "GET /favicon.ico HTTP/1.1", host: "52.spaces.dev", referrer: "http://52.spaces.dev/"
I'm stuck. I'm also pretty new to nginx configuration (which may be obvious) so I might well be doing something completely wrong and/or dumb. Any suggestions?
The issue you're experiencing with the redirect loop in your question is that basically every request, even for static files tries to route via your index.php?$args block.
I see 2 possible solutions here. First, if you are willing to implement the nginx setup with a single nginx instance, look at this thread: NGINX try_files with multiple named locations and this blog post http://linuxplayer.org/2013/06/nginx-try-files-on-multiple-named-location-or-server
What needs to happen, is you need to first test if the resource is present on the upstream as is (i.e., doesn't return 404). If it does, then you serve it as is. If it does not, then you would be calling the rewrite block which tries to put it as a parameter to index.php. So you would end up with something like this (sorry, I don't really have a chance to test this, but hopefully it gives an idea):
location / {
try_files $uri $uri/ #phpproxy;
}
location #phpproxy {
proxy_pass http://phpfarm_52;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 404 = #rewrite_proxy;
}
location #rewrite_proxy {
rewrite ^/(.*)$ /index.php?$1 break;
proxy_pass http://phpfarm_52;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
The second solution (that I would personally prefer) is to front every upstream with its own nginx. Then the top nginx which is fronting all others will have a much cleaner structure with simple proxy_pass's (+ maybe some static content) and that's it. It will also cut down on the request round-trip, since you wouldn't need to resolve 404's coming from the upstreams.

Categories