添加负载均衡器时 Django 多租户站点的重定向循环
Redirect loop for Django multi tenanted site when load balancer added
我有两个虚拟主机,每个 运行 五个站点(多租户)。服务器具有三个 IP 地址。两个 public 正面,一个在内部。
两个 public面向的网站都有 SSL 证书。一个站点是我的暂存站点,有一个 letsencrypt SSL 证书,另一个是实时站点,有一个 godaddy SSL 证书。
我首先用一个节点(我的云实例)设置了一个 Rackspace 负载均衡器,将证书和密钥从我的服务器复制到均衡器,并成功使用以下 nginx 配置让负载均衡器代理我的站点来自我在面向内部的 IP
上服务的 Web 服务器
upstream django {
server unix:///run/uwsgi/app/introtest/socket;
}
# configuration of the server, first redirect http to https...
server {
listen 10.181.104.195:80;
if ($http_x_forwarded_proto = "http") {
return 302 https://$http_host$request_uri;
}
# the domain name it will serve for
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /srv/test/media;
}
location /static {
alias /srv/test/static;
}
# Finally, send all non-media requests to the Django server.
location / {
if (-f /srv/maintenance_test.html) {
return 503;
}
uwsgi_pass django;
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Host $server_name;
}
# Error pages
error_page 503 /maintenance_test.html;
location = /maintenance_test.html {
root /srv;
}
}
顺便说一句,如果可以的话,我不会使用永久重定向,也不会使用暂存服务器。实时服务器已经设置并永久重定向到 https,但我认为我们总是希望将实时站点重定向到 SSL,因此重定向是 301。
将临时站点的根域的 DNS 条目更改为负载平衡器后,效果很好。
tail -f /var/log/nginx/access.log
显示请求来自负载均衡器的内部 IP 地址,页面得到正确处理。
我改回了所有内容(即 nginx conf 和登台根域的 DNS 条目),并让登台可以从网络服务器提供服务。然后我将 godaddy SSL 证书信息复制到负载平衡器。然后为实时服务器使用以下 nginx 配置:
upstream intro_live {
server unix:///run/uwsgi/app/introsites/socket;
}
server {
listen <SERVER PUBLIC IP>:80;
listen <SERVER PUBLIC IP>:443;
location / {
return 503;
}
error_page 503 /interruption.html;
location = /interruption.html {
root /srv;
}
}
# configuration of the server
server {
# the port your site will be served on
listen 10.181.104.195:80;
# reidrect http to https from load balancer
if ($http_x_forwarded_proto = "http") {
set $http_test S${http_host};
}
if ($http_test = 'Sintrotravel.com') {
rewrite ^ https://www.introtravel.com$request_uri;
}
if ($http_test = 'Sozintro.com') {
rewrite ^ https://www.ozintro.com$request_uri permanent;
}
if ($http_test = 'Sbalintro.com') {
rewrite ^ https://www.balintro.com$request_uri permanent;
}
if ($http_test = 'Sthaintro.com') {
rewrite ^ https://www.thaintro.com$request_uri permanent;
}
if ($http_test = 'Svietnamintro.com') {
rewrite ^ https://www.vietnamintro.com$request_uri permanent;
}
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /srv/intro/media;
expires 7d;
add_header Pragma public;
add_header Cache-Control "public";
}
location /static {
alias /srv/intro/static;
expires 1d;
add_header Pragma public;
add_header Cache-Control "public";
}
# Finally, send all non-media requests to the Django server.
location / {
if (-f /srv/maintenance_on.html) {
return 503;
}
uwsgi_pass intro_live;
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Host $server_name;
}
# Error pages
error_page 503 /maintenance_on.html;
location = /maintenance_on.html {
root /srv;
}
}
- 第一个
upstream django
指向我的uwsgi配置。
- 第二个服务器配置侦听服务器的 public 面向地址,因此如果某人的网站 DNS 条目尚未更新,他们将获得一个静态页面,说明服务器正在维护。
- 第三个服务器配置侦听内部地址上的端口 80,检查登台服务器是否已设置
http_x_forwarded_proto
,以及是否已将测试变量设置为域。我专门将这五个站点的 http 流量重定向到 https。
- 最后的 503 东西检测到文件是否存在,如果存在,站点将进入维护模式。
/etc/nginx/conf.d/ 中没有任何内容,我的 /etc/nginx/nginx.conf 看起来像这样:
user www-data;
worker_processes 20;
pid /run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
一旦我将五个站点的 DNS A 记录设置为指向负载均衡器,就可以重定向到 https,但我在每个页面中都出现了重定向循环。所以,我从配置中取出所有重定向,即
if ($http_test = 'Sintrotravel.com') {
rewrite ^ https://www.introtravel.com$request_uri;
}
if ($http_test = 'Sozintro.com') {
rewrite ^ https://www.ozintro.com$request_uri permanent;
}
if ($http_test = 'Sbalintro.com') {
rewrite ^ https://www.balintro.com$request_uri permanent;
}
if ($http_test = 'Sthaintro.com') {
rewrite ^ https://www.thaintro.com$request_uri permanent;
}
if ($http_test = 'Svietnamintro.com') {
rewrite ^ https://www.vietnamintro.com$request_uri permanent;
}
并重新启动了 nginx。我仍然有一个重定向循环。为什么???我知道重写是永久性的,但这不是因为 return 302
和 rewrite
的差异,因为当我取出 all[= 时服务器仍处于无限重定向循环中50=] 重定向。出于显而易见的原因,我不能花很多时间试验实时站点。真的,我需要在 10-15 分钟内完成。有人有什么建议吗?
仔细想想,我意识到问题一定出在 Django 应用程序上。与 nginx 无关。有一个设置
SECURE_SSL_REDIRECT = True
这使得 django 将 http 流量重定向到 https(这在暂存中是错误的)。当您添加负载均衡器时,这会成为一个问题,因为负载均衡器接收 https 流量,但它只会将端口 80 上的不安全流量传送到我的网站 server/application。这就是为什么您在条件
上进行重定向的原因
if ($http_x_forwarded_proto = "http") {...
(由负载均衡器设置)在您的 nginx 配置中。仅从 nginx 中以这种方式删除重定向不会解决问题,我不得不从 django 中完全删除它。
无论你经验多么丰富,如果你赶时间,DNS 会让人感到困惑,因为它的分布式特性,当一个赚钱的网站宕机时,你也很匆忙。
Django 可以通过这个解决这个问题:
https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER
添加:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
并确保您的代理设置适当 header 将防止重定向循环。发生的事情是你的代理没有告诉 Django 连接是安全的,所以它一直重定向到安全版本,尽管它已经是安全的 - 因此无限循环。提供 header 是解决此问题的方法之一。
我有两个虚拟主机,每个 运行 五个站点(多租户)。服务器具有三个 IP 地址。两个 public 正面,一个在内部。
两个 public面向的网站都有 SSL 证书。一个站点是我的暂存站点,有一个 letsencrypt SSL 证书,另一个是实时站点,有一个 godaddy SSL 证书。
我首先用一个节点(我的云实例)设置了一个 Rackspace 负载均衡器,将证书和密钥从我的服务器复制到均衡器,并成功使用以下 nginx 配置让负载均衡器代理我的站点来自我在面向内部的 IP
上服务的 Web 服务器upstream django {
server unix:///run/uwsgi/app/introtest/socket;
}
# configuration of the server, first redirect http to https...
server {
listen 10.181.104.195:80;
if ($http_x_forwarded_proto = "http") {
return 302 https://$http_host$request_uri;
}
# the domain name it will serve for
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /srv/test/media;
}
location /static {
alias /srv/test/static;
}
# Finally, send all non-media requests to the Django server.
location / {
if (-f /srv/maintenance_test.html) {
return 503;
}
uwsgi_pass django;
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Host $server_name;
}
# Error pages
error_page 503 /maintenance_test.html;
location = /maintenance_test.html {
root /srv;
}
}
顺便说一句,如果可以的话,我不会使用永久重定向,也不会使用暂存服务器。实时服务器已经设置并永久重定向到 https,但我认为我们总是希望将实时站点重定向到 SSL,因此重定向是 301。
将临时站点的根域的 DNS 条目更改为负载平衡器后,效果很好。
tail -f /var/log/nginx/access.log
显示请求来自负载均衡器的内部 IP 地址,页面得到正确处理。
我改回了所有内容(即 nginx conf 和登台根域的 DNS 条目),并让登台可以从网络服务器提供服务。然后我将 godaddy SSL 证书信息复制到负载平衡器。然后为实时服务器使用以下 nginx 配置:
upstream intro_live {
server unix:///run/uwsgi/app/introsites/socket;
}
server {
listen <SERVER PUBLIC IP>:80;
listen <SERVER PUBLIC IP>:443;
location / {
return 503;
}
error_page 503 /interruption.html;
location = /interruption.html {
root /srv;
}
}
# configuration of the server
server {
# the port your site will be served on
listen 10.181.104.195:80;
# reidrect http to https from load balancer
if ($http_x_forwarded_proto = "http") {
set $http_test S${http_host};
}
if ($http_test = 'Sintrotravel.com') {
rewrite ^ https://www.introtravel.com$request_uri;
}
if ($http_test = 'Sozintro.com') {
rewrite ^ https://www.ozintro.com$request_uri permanent;
}
if ($http_test = 'Sbalintro.com') {
rewrite ^ https://www.balintro.com$request_uri permanent;
}
if ($http_test = 'Sthaintro.com') {
rewrite ^ https://www.thaintro.com$request_uri permanent;
}
if ($http_test = 'Svietnamintro.com') {
rewrite ^ https://www.vietnamintro.com$request_uri permanent;
}
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /srv/intro/media;
expires 7d;
add_header Pragma public;
add_header Cache-Control "public";
}
location /static {
alias /srv/intro/static;
expires 1d;
add_header Pragma public;
add_header Cache-Control "public";
}
# Finally, send all non-media requests to the Django server.
location / {
if (-f /srv/maintenance_on.html) {
return 503;
}
uwsgi_pass intro_live;
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Host $server_name;
}
# Error pages
error_page 503 /maintenance_on.html;
location = /maintenance_on.html {
root /srv;
}
}
- 第一个
upstream django
指向我的uwsgi配置。 - 第二个服务器配置侦听服务器的 public 面向地址,因此如果某人的网站 DNS 条目尚未更新,他们将获得一个静态页面,说明服务器正在维护。
- 第三个服务器配置侦听内部地址上的端口 80,检查登台服务器是否已设置
http_x_forwarded_proto
,以及是否已将测试变量设置为域。我专门将这五个站点的 http 流量重定向到 https。 - 最后的 503 东西检测到文件是否存在,如果存在,站点将进入维护模式。
/etc/nginx/conf.d/ 中没有任何内容,我的 /etc/nginx/nginx.conf 看起来像这样:
user www-data;
worker_processes 20;
pid /run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
一旦我将五个站点的 DNS A 记录设置为指向负载均衡器,就可以重定向到 https,但我在每个页面中都出现了重定向循环。所以,我从配置中取出所有重定向,即
if ($http_test = 'Sintrotravel.com') {
rewrite ^ https://www.introtravel.com$request_uri;
}
if ($http_test = 'Sozintro.com') {
rewrite ^ https://www.ozintro.com$request_uri permanent;
}
if ($http_test = 'Sbalintro.com') {
rewrite ^ https://www.balintro.com$request_uri permanent;
}
if ($http_test = 'Sthaintro.com') {
rewrite ^ https://www.thaintro.com$request_uri permanent;
}
if ($http_test = 'Svietnamintro.com') {
rewrite ^ https://www.vietnamintro.com$request_uri permanent;
}
并重新启动了 nginx。我仍然有一个重定向循环。为什么???我知道重写是永久性的,但这不是因为 return 302
和 rewrite
的差异,因为当我取出 all[= 时服务器仍处于无限重定向循环中50=] 重定向。出于显而易见的原因,我不能花很多时间试验实时站点。真的,我需要在 10-15 分钟内完成。有人有什么建议吗?
仔细想想,我意识到问题一定出在 Django 应用程序上。与 nginx 无关。有一个设置
SECURE_SSL_REDIRECT = True
这使得 django 将 http 流量重定向到 https(这在暂存中是错误的)。当您添加负载均衡器时,这会成为一个问题,因为负载均衡器接收 https 流量,但它只会将端口 80 上的不安全流量传送到我的网站 server/application。这就是为什么您在条件
上进行重定向的原因if ($http_x_forwarded_proto = "http") {...
(由负载均衡器设置)在您的 nginx 配置中。仅从 nginx 中以这种方式删除重定向不会解决问题,我不得不从 django 中完全删除它。
无论你经验多么丰富,如果你赶时间,DNS 会让人感到困惑,因为它的分布式特性,当一个赚钱的网站宕机时,你也很匆忙。
Django 可以通过这个解决这个问题:
https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER
添加: SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
并确保您的代理设置适当 header 将防止重定向循环。发生的事情是你的代理没有告诉 Django 连接是安全的,所以它一直重定向到安全版本,尽管它已经是安全的 - 因此无限循环。提供 header 是解决此问题的方法之一。