localhost in build_absolute_uri for Django with Nginx - django

On production I use the chain Django - UWSGI - Docker - Nxing. UWSGI works with the port 50012 and Ngxin is configured as:
proxy_pass http://localhost:50012;
Django process thinks that its host is localhost:50012 instead of the domain that Nginx listens to. So when the function build_absolute_uri is called there's localhost:50012 instead of my domain. Is there a way to make Django use the custom host name when build_absolute_uri is called?
Notice: in some libraries build_absolute_uri called implicitly (like social-django, or example), so avoiding this function is not a solution in my case.

The problem
When the public hostname you use to reach the proxy differ from the internal hostname of the application server, Django has no way to know which hostname was used in the original request unless the proxy is passing this information along.
Possible Solutions
1) Set the proxy to pass along the orginal host
From MDN:
The X-Forwarded-Host (XFH) header is a de-facto standard header for identifying the original host requested by the client in the Host HTTP request header.
Host names and ports of reverse proxies (load balancers, CDNs) may differ from the origin server handling the request, in that case the X-Forwarded-Host header is useful to determine which Host was originally used.
There are two things you should do:
ensure all proxies in front of Django are passing along the X-Forwarded-Host header
turn on USE_X_FORWARDED_HOST in the settings
if the internal and external scheme differ as well, set SECURE_PROXY_SSL_HEADER to a meaningful value and set the server to send the corresponding header
When USE_X_FORWARDED_HOST is set to True in settings.py, HttpRequest.build_absolute_uri uses the X-Forwarded-Host header instead of request.META['HTTP_HOST'] or request.META['SERVER_NAME'].
I will not delve too much into the proxy setup part (as it is more related to professional network administration than to programming in the scope of this site) but for nginx it should be something like:
location / {
...
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
...
proxy_pass http://upstream:port;
}
Probably the best solution as it is fully dynamic, you don't have to change anything if the public scheme/hostname changes in the future.
If the internal and external scheme differ as well you may want to set SECURE_PROXY_SSL_HEADER in settings.py to something like this:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
And then add the following to the server config:
proxy_set_header X-Forwarded-Proto https;
2) Use the same hostname for public and private servers
Lets say your public hostname is "host.example.com": you can add a line like this to your /etc/hosts (on Windows %windir%\System32\drivers\etc\hosts):
127.0.0.1 host.example.com
Now you can use the hostname in the nginx config:
proxy_pass http://host.example.com:port;
When the internal and external scheme differ as well (external https, internal http), you may want to set SECURE_PROXY_SSL_HEADER as described in the first solution.
Every time the public hostname changes you will have to update the config but I guess this is OK for small projects.

I got mine working using proxy_redirect
Lets say you have a container or an upstream with the name app and you want it to return 127.0.0.1 as the host, Then your config should include:
server {
listen 80;
location / {
proxy_pass http://app:8000;
proxy_redirect http://app:8000 http://127.0.0.1:8000;
}
}
Here's my final config:
server {
listen 80;
location / {
proxy_pass http://app:8000;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect http://app:8000 http://127.0.0.1:8000;
}
}
Also checkout this article for a detailed explanation https://mattsegal.dev/nginx-django-reverse-proxy-config.html

I had a problem same as the question, I used nginx, gunicorn and django on production without docker.
get_current_site(request).domain
returned localhost so I have some issue in drf yasg base url. Simply I solved it by adding
include proxy_params;
to nginx conf.

Related

Django + Apache2 behind NGINX reverse proxy - redirections going wrong

I have a Django project running on Apache2 with mod_wsgi in a VM - https://systems3.slt.local/
Accessing the VM directly, via https://systems3.slt.local/ works perfectly. However, I need it to work behind an NGINX reverse proxy, and I'm having problems with login redirection.
My proxy is configured like this:
location /systems3 {
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;
proxy_redirect off;
proxy_pass https://systems3.slt.local/;
}
When I try to access the project through the proxy, /systems3, Django checks that there is no user logged in, and then redirects to /accounts/login. The correct would be to redirect to /systems3/accounts/login/. Even if I try to directly access this address, I am redirected to /accounts/login.
It seems that some configuration is missing so that the address /systems3 is the root of the project, it shouldn't be redirecting me outside of systems3.
It's also giving problem with my static folder, it also looks for the folder at /static, ignoring that it was supposed to look inside systems3.

What's the purpose of setting "X-Forwarded-For" header in nginx

I have the following Nginx configuration for my Django application:
upstream api {
server localhost:8000;
}
server {
listen 80;
location / {
proxy_pass http://api;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /staticfiles {
alias /app/static/;
}
}
I based this config on a tutorial here. After some research, looks like setting the Host header allows the Django API to determine original client's IP address (instead of the IP address of the proxy).
What's the point of the X-Forwarded-For header? I see a field called $http_x_forwarded_for in the nginx logs but I'm not sure it's related.
From the Mozilla docs
The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client connecting to a web server through an HTTP proxy or a load balancer. When traffic is intercepted between clients and servers, server access logs contain the IP address of the proxy or load balancer only. To see the original IP address of the client, the X-Forwarded-For request header is used.
In fact, I think that you have misunderstood the Host header. My understanding is that it will be the IP of the nginx server.

Django Rest framework swagger base url

I have successfully running my rest framework swagger on localhost..
These was my rest framework swagger localhost screenshoot. It has run successfully.
Now i want to deploy it on server testing.
These is the screenshot result of my testing deployment.
As you can see the base path changed and served from localhost, the schemes read the right configuration which is https schemes, and the application run in the right domain which is api.executivemafia.com, so that's not problem. The problem come when I want to try the API. The server point is localhost. So it's make my endpoint request become 'https://127.0.0.1:8000/instagram/hello".
My questions are:
Why the rest framework swagger still read localhost although my application already served in https://api.executivemafia.com/docs/?
Fyi, I set the proxy pass nginx to locahost:8000 because my gunicorn run on localhost:8000.
Can the base url rest framework swagger testing server using my testing server domain end point ? If it's able to, how to do it?
Any help and reference will be appreciated.
Thank you Guys!!
Regards,
Meikelwis Wijaya
Set the following inside the nginx configuration file:
server{
........
location /{
proxy_pass http://yourhostname:port;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
it worked for me !!

Nginx does not forwards remote address to gunicorn

I have the following nginx configuration to forward requests to gunicorn.
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
However, when I access remote address using request.META['REMOTE_ADDR'], it always returns 127.0.0.1. I am using Django 1.9
That is correct and expected behavior. If you would like to access users IP you will need to use:
request.META['HTTP_X_FORWARDED_FOR']
Note that in development (without running nginx), REMOTE_ADDR is still correct.
My recommendation is to add a middleware or a utility method which will do the conditional logic to get the actual users IP depending on your settings.

How to run several apps on one EC2 instance?

It's probably related to this question: How to run more than one app on one instance of EC2
But that question only seemed to be talking about multiple node.js apps.
I am trying learn several different things, so I'm building different websites to learn Ruby on Rails, LAMP, and node.js. Along with my personal website and blog.
Is there any way to run all these on the same EC2 instance?
First, there's nothing EC2-specific about setting up multiple web apps on one box. You'll want to use nginx (or Apache) in "reverse proxy" mode. This way, the web server listens on port 80 (and 443), and your apps run on various other ports. Each incoming request reads the "Host" header to map the request to a backend. So different DNS names/domains show different content.
Here is how to setup nginx in reverse proxy mode: http://www.cyberciti.biz/tips/using-nginx-as-reverse-proxy.html
For each "back-end" app, you'll want to:
1) Allocate a port (3000 in this example)
2) write an upstream stanza that tells it where your app is
3) write a (virtual) server stanza that maps from the server name to the upstream location
For example:
upstream app1 {
server 127.0.0.1:3000; #App1's port
}
server {
listen *:80;
server_name app1.example.com;
# You can put access_log / error_log sections here to break them out of the common log.
## send request to backend
location / {
proxy_pass http://app1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
I prefer to have Nginx in front of Apache for two reasons: 1) nginx can serve static files with much less memory, and 2) nginx buffers data to/from the client, so people on slow internet connections don't clog your back-ends.
When testing your config, use nginx -s reload to reload the config, and curl -v -H "Host: app1.example.com" http://localhost/ to test a specific domain from your config
Adding to the #Brave answer, I would like to mention the configuration of my nginx for those who are looking for the exact syntax in implementing it.
server {
listen 80;
server_name mysite.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:3000;
}
}
server {
listen 80;
server_name api.mysite.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:4500;
}
}
Just create two server objects with unique server name and the port address.
Mind proxy_pass in each object.
Thank you.