nginx rewrite leads to redirect loop - django

I'm using nginx with a reverse proxy to my gunicorn django server at local to run my django app.
I am trying to force ssl from any http request, as the site is only intended https access. The app will ONLY listen to port 8888, (80 and 443 taken), so the site should only be accessible when specified the 8888 port.
I tried to rewrite with this rewrite ^ https://domain.net:8888$request_uri? permanent; in the server block and location block. Not only does it not redirect http requests to the same url, it also causes a redirect loop when there is a https request.
server {
listen 8888;
server_name sy-system.net;
rewrite ^ https://domain.net:8888$request_uri? permanent;
ssl on;
ssl_certificate /path/to/domain.pem;
ssl_certificate_key /path/to/domain.key;
# serve directly - analogous for static/staticfiles
location /media/ {
root /path/to/root;
}
location /static/ {
root /path/to/root;
}
location / {
#rewrite ^ https://sy-system.net:8888$request_uri? permanent;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 10;
proxy_read_timeout 10;
proxy_pass http://127.0.0.1:8881/;
proxy_set_header X-Forwarded-Protocol https;
}
# what to serve if upstream is not available or crashes
error_page 500 502 503 504 /media/50x.html;
}

You are unconditionally redirecting from domain.net:8888 to domain.net:8888. An infinite redirect loop is the only possible result.
In any case, what you are attempting to do isn't possible. Nginx is talking SSL, your browser is not, so no data will be transferred between them (which is why you don't get the redirect loop when connecting with plain HTTP). When they do talk (via SSL) your redirect loop takes over.

Related

docker + nginx http requests not working in browsers

I have a AWS EC2 instance running Linux with docker containers running gunicorn/django and an nginx reverse proxy.
I don't want it to redirect to https at the moment.
When I try to reach the url by typing out http://url.com in the browser it seems to automatically change to https://url.com and gives me ERR_CONNECTION_REFUSED. The request doesn't show up at all in the nginx access_log.
But when I try to reach it with curl I get a normal response and it does show up in the nginx access_log.
I have ascertained that the django security middleware is not the cause as the HSTS options are disabled.
I've tried clearing the browser cache and deleting the domain from the chrome security policies.
nginx config:
upstream django_server {
server app:8001 fail_timeout=0;
}
server {
listen 80;
server_name url.com www.url.com;
client_max_body_size 4G;
charset utf-8;
keepalive_timeout 5;
location /static/ {
root /usr/share/nginx/sdev/;
expires 30d;
}
location / {
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;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass http://django_server;
}
}
}
What am I overlooking?

Rewriting URL in an nginx Docker container wrongly uses internal port

I have a Django application that runs in a Docker environment; one container for gunicorn and one for nginx. My application's nginx server listens on port 9081, which is internal to the system (it's not exposed to the outside world). Another nginx container (which routes traffic) sits on port 80 and sends traffic to my site as necessary (based on the hostname a request receives).
Here's my application's nginx setup, stripped down to the basics:
upstream project {
server gun_project:8001; # gunicorn container
}
server {
listen 9081;
server_name mytool.myhost.com;
set_real_ip_from 172.17.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location / {
proxy_pass http://project;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Here's the router nginx setup, again stripped down:
upstream project {
server ngx_project:9081; # nginx container
}
server {
listen 80;
server_name mytool.myhost.com;
return 302 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mytool.myhost.com;
# SSL Info
ssl_certificate /etc/nginx/ssl/mycert.cer;
ssl_certificate_key /etc/nginx/ssl/mycert.key;
location / {
proxy_pass http://project;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
I want to redirect a URL on this site from one location to another (the URL has permanently changed). I'm doing so via a rewrite in the location block of my application's nginx configuration (the first nginx block above):
location / {
rewrite "^/oldpath/$" /newpath/ permanent;
proxy_pass http://project;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
When I do this, and I attempt to load the old URL (mytool.myhost.com/oldpath/) in a web browser, I'm redirected to mytool.myhost.com:9081/newpath/ which fails because it doesn't exist (that port isn't exposed externally).
Is there something basic I'm missing? I don't want that internal port to be a part of the redirect.
Here's how I ended up doing it:
I added a dedicated location in the nginx configuration for the site, and performed the redirect there:
# Redirect the previous URL to the newer one
location = /old-path/ {
return 302 https://$host/new-path/;
}

How to serve React app AND Django blog from Nginx?

I'm using nginx to serve a React app from my domain root / and a Django blog app from /blog. I'm also using nginx to redirect all http to https.
The problem is some weird behaviour... If I navigate to the blog with a clear cache, it shows the blog. If I then navigate to the index page it shows the react app. All good so far. But then, if I return to /blog it continues to show the react app, not the blog! I think the problem involves caching, but I'm not sure where.
I am not using react-router, so I'm not sure how the urls could get redirected on the client side.
Here is my nginx config:
server {
listen 80;
server_name mydomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name mydomain.com;
root /production_build;
location /static/ {
root /var/www/mysite;
}
location /blog {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://upstream_django_server;
}
location /cms {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://upstream_django_server;
}
location / {
try_files $uri /index.html;
}
}
You should disable the service worker in react, as it is interfering with your /blog url and returning its own response the next time.
Since you are mixing 2 apps here, you don't want to take a risk of having something which is difficult to get rid of.
Service workers can be sometimes very nasty because of caching responses

Rails 4 + Websocket-rails + Passenger + Nginx + Load balancer

I've added some features to a couple of our web apps that needs websocket-rails. Everything works fine in development, but I am not sure how to deploy all this in our production environment since it's a bit more complex.
The production setup:
1 server used as a Load balancer (Nginx).
2 servers used as web servers, where our rails apps run using Nginx and Passenger (both servers are identical).
Several other servers used by the app servers but I believe they are irrelevant for this question.
All sites are running on HTTPS.
Load balancer configs
Here's an example for one of the sites, the others have similar configs:
upstream example {
ip_hash;
server xx.xx.xx.xx:443;
server xx.xx.xx.xx:443;
}
server {
listen 80;
listen 443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/example.chained.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
server_name example.com;
rewrite ^(.*) https://www.example.com$1 permanent;
}
server {
listen 80;
listen 443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/example.chained.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
server_name www.example.com;
if ($ssl_protocol = "") {
rewrite ^ https://$server_name$request_uri? permanent;
}
client_max_body_size 2000M;
location /css { root /home/myuser/maintenance; }
location /js { root /home/myuser/maintenance; }
location /img { root /home/myuser/maintenance; }
location /fonts { root /home/myuser/maintenance; }
error_page 502 503 #maintenance;
location #maintenance {
root /home/myuser;
if ($uri !~ ^/maintenance/) {
rewrite ^(.*)$ /maintenance/example.html break;
}
}
location / {
proxy_pass https://example;
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;
}
}
Web server configs
Again, here's an example for one of the sites, the others have similar configs:
server {
server_name example.com;
rewrite ^(.*) https://www.example.com$1 permanent;
}
server {
listen 80;
listen 443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/example.chained.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
root /var/www/example/public;
server_name www.example.com;
if ($ssl_protocol = "") {
rewrite ^ https://$server_name$request_uri? permanent;
}
client_max_body_size 2000M;
passenger_enabled on;
rails_env production;
passenger_env_var SECRET_KEY_BASE "SOME_SECRET";
}
What I've gathered so far:
I'll need to enable passenger sticky sessions
I'll need to create a location in the site's server section where the websocket server is listening to.
I'll need to override the concurrent requests of passenger for the websocket location to unlimited.
My Questions:
Do I have to enable the passenger sticky sessions also in the load balancer's configs? I am guessing this is only for the web servers.
How would the location section for the websocket server look like?
Do I have to create the websocket location section also on the load balancer?
Having the sticky sessions is enough to keep the various apps and servers in synch?
I have various apps running on each server and they should all receive the same notifications (socket messages) so they should all connect to the same websocket server (I'm guessing). Now that websocket-rails is part of their gemsets, won't each app try to spawn their own websocket server? If so, how do I prevent that and make them spawn only one in case none is running yet?
As you can see I am quite confused about how websocket-rails works with passenger and nginx in production so even if you don't have all the answers, any input is greatly appreciated!
UPDATE
I've tried the following on the load balancer:
upstream websocket {
server xx.xx.xx.xx:443;
server xx.xx.xx.xx:443;
}
location /websocket {
proxy_pass https://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#also tried with this:
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection "upgrade";
}
and on the app servers:
location /websocket {
proxy_pass https://www.example.com/websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#also tried with this:
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection "upgrade";
}
On the client side I connect to the url WebSocketRails('www.example.com/websocket'); and i get the following error:
WebSocket connection to 'wss://www.example.com/websocket' failed: Error during WebSocket handshake: Unexpected response code: 404
Any ideas?
I don't think you'll need passenger sticky sessions on the load balancer
This blog covers relevant WebSocket config for NGINX. You need the WebSocket config on the load balancer, and also on the web server if you want to pass the Upgrade and Connection headers to the rails app.

Django #login_required dropping https

I'm trying to test my Django app locally using SSL. I have a view with the #login_required decorator. So when I hit /locker, I get redirected to /locker/login?next=/locker. This works fine with http.
However, whenever I use https, the redirect somehow drops the secure connection, so I get something like https://cumulus.dev/locker -> http://cumulus.dev/locker/login?next=/locker
If I go directly to https://cumulus.dev/locker/login?next=locker the page opens fine over a secure connection. But once I enter the username and password, I go back to http://cumulus.dev/locker.
I'm using Nginx to handle the SSL, which then talks to runserver. My nginx config is
upstream app_server_djangoapp {
server localhost:8000 fail_timeout=0;
}
server {
listen 80;
server_name cumulus.dev;
access_log /var/log/nginx/cumulus-dev-access.log;
error_log /var/log/nginx/cumulus-dev-error.log info;
keepalive_timeout 5;
# path for static files
root /home/gaurav/www/Cumulus/cumulus_lightbox/static;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://app_server_djangoapp;
break;
}
}
}
server {
listen 443;
server_name cumulus.dev;
ssl on;
ssl_certificate /etc/ssl/cacert-cumulus.pem;
ssl_certificate_key /etc/ssl/privkey.pem;
access_log /var/log/nginx/cumulus-dev-access.log;
error_log /var/log/nginx/cumulus-dev-error.log info;
keepalive_timeout 5;
# path for static files
root /home/gaurav/www/Cumulus/cumulus_lightbox/static;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://app_server_djangoapp;
break;
}
}
}
Django is running on plain HTTP only behind the proxy, so it will always use that to construct absolute URLs (such as redirects), unless you configure it how to see that the proxied request was originally made over HTTPS.
As of Django 1.4, you can do this using the SECURE_PROXY_SSL_HEADER setting. When Django sees the configured header, it will treat the request as HTTPS instead of HTTP: request.is_secure() will return true, https:// URLs will be generated, and so on.
However, note the security warnings in the documentation: you must ensure that the proxy replaces or strips the trusted header from all incoming client requests, both HTTP and HTTPS. Your nginx configuration above does not do that with X-Forwarded-Ssl, making it spoofable.
A conventional solution to this is to set X-Forwarded-Protocol to http or https, as appropriate, in each of your proxy configurations. Then, you can configure Django to look for it using:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')