正确理解并设置nginx中remote_addr和x_forwarded_for参数

zkbhj 发表了文章 • 0 个评论 • 1437 次浏览 • 2017-04-24 16:03 • 来自相关话题

做网站时经常会用到remote_addr和x_forwarded_for这两个头信息来获取客户端的IP,然而当有反向代理或者CDN的情况下,这两个值就不够准确了,需要调整一些配置。

什么是remote_addr

remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间没有任何代理,那么网站的web服务器(Nginx,Apache等)就会把remote_addr设为你的机器IP,如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样web服务器就会把remote_addr设为这台代理机器的IP。

什么是x_forwarded_for

正如上面所述,当你使用了代理时,web服务器就不知道你的真实IP了,为了避免这个情况,代理服务器通常会增加一个叫做x_forwarded_for的头信息,把连接它的客户端IP(即你的上网机器IP)加到这个头信息里,这样就能保证网站的web服务器能获取到真实IP

使用HAProxy做反向代理

通常网站为了支撑更大的访问量,会增加很多web服务器,并在这些服务器前面增加一个反向代理(如HAProxy),它可以把负载均匀的分布到这些机器上。你的浏览器访问的首先是这台反向代理,它再把你的请求转发到后面的web服务器,这就使得web服务器会把remote_addr设为这台反向代理的IP,为了能让你的程序获取到真实的客户端IP,你需要给HAProxy增加以下配置option forwardfor它的作用就像上面说的,增加一个x_forwarded_for的头信息,把你上网机器的ip添加进去

使用Nginx的realip模块

当Nginx处在HAProxy后面时,就会把remote_addr设为HAProxy的IP,这个值其实是毫无意义的,你可以通过nginx的realip模块,让它使用x_forwarded_for里的值。使用这个模块需要重新编译Nginx,增加--with-http_realip_module参数set_real_ip_from   10.1.10.0/24;
real_ip_header     X-Forwarded-For;上面的配置就是把从10.1.10这一网段过来的请求全部使用X-Forwarded-For里的头信息作为remote_addr

将Nginx架在HAProxy前面做HTTPS代理

网站为了安全考虑通常会使用https连接来传输敏感信息,https使用了ssl加密,HAProxy没法直接解析,所以要在HAProxy前面先架台Nginx解密,再转发到HAProxy做负载均衡。这样在Web服务器前面就存在了两个代理,为了能让它获取到真实的客户端IP,需要做以下配置。

首先要在Nginx的代理规则里设定proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;这样会让Nginx的https代理增加x_forwarded_for头信息,保存客户的真实IP。

其次修改HAProxy的配置option     forwardfor except 10.1.10.0/24这个配置和之前设定的差不多,只是多了个内网的IP段,表示如果HAProxy收到的请求是由内网传过来的话(https代理机器),就不会设定x_forwarded_for的值,保证后面的web服务器拿到的就是前面https代理传过来的。

为什么PHP里的HTTP_X_FORWARDED_FOR和Nginx的不一样

当你的网站使用了CDN后,用户会先访问CDN,如果CDN没有缓存,则回源站(即你的反向代理)取数据。CDN在回源站时,会先添加x_forwarded_for头信息,保存用户的真实IP,而你的反向代理也会设定这个值,不过它不会覆盖,而是把CDN服务器的IP(即当前remote_addr)添加到x_forwarded_for的后面,这样x_forwarded_for里就会存在两个值。Nginx会使用这些值里的第一个,即客户的真实IP,而PHP则会使用第二个,即CDN的地址。为了能让PHP也使用第一个值,你需要添加以下fastcgi的配置。fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;它会把nginx使用的值(即第一个IP)传给PHP,这样PHP拿到的x_forwarded_for里其实就只有一个值了,也就不会用第二个CDN的IP了。

忽略x_forwarded_for

其实,当你使用了Nginx的realip模块后,就已经保证了remote_addr里设定的就是客户端的真实IP,再看下这个配置set_real_ip_from   10.1.10.0/24;
real_ip_header     X-Forwarded-For;它就是把x_forwarded_for设为remote_addr,而nginx里的x_forwarded_for取的就是其中第一个IP。

使用这些设置就能保证你的remote_addr里设定的一直都是客户端的真实IP,而x_forwarded_for则可以忽略。 查看全部
做网站时经常会用到remote_addr和x_forwarded_for这两个头信息来获取客户端的IP,然而当有反向代理或者CDN的情况下,这两个值就不够准确了,需要调整一些配置。

什么是remote_addr

remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间没有任何代理,那么网站的web服务器(Nginx,Apache等)就会把remote_addr设为你的机器IP,如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样web服务器就会把remote_addr设为这台代理机器的IP。

什么是x_forwarded_for

正如上面所述,当你使用了代理时,web服务器就不知道你的真实IP了,为了避免这个情况,代理服务器通常会增加一个叫做x_forwarded_for的头信息,把连接它的客户端IP(即你的上网机器IP)加到这个头信息里,这样就能保证网站的web服务器能获取到真实IP

使用HAProxy做反向代理

通常网站为了支撑更大的访问量,会增加很多web服务器,并在这些服务器前面增加一个反向代理(如HAProxy),它可以把负载均匀的分布到这些机器上。你的浏览器访问的首先是这台反向代理,它再把你的请求转发到后面的web服务器,这就使得web服务器会把remote_addr设为这台反向代理的IP,为了能让你的程序获取到真实的客户端IP,你需要给HAProxy增加以下配置option forwardfor它的作用就像上面说的,增加一个x_forwarded_for的头信息,把你上网机器的ip添加进去

使用Nginx的realip模块

当Nginx处在HAProxy后面时,就会把remote_addr设为HAProxy的IP,这个值其实是毫无意义的,你可以通过nginx的realip模块,让它使用x_forwarded_for里的值。使用这个模块需要重新编译Nginx,增加--with-http_realip_module参数set_real_ip_from   10.1.10.0/24;
real_ip_header     X-Forwarded-For;上面的配置就是把从10.1.10这一网段过来的请求全部使用X-Forwarded-For里的头信息作为remote_addr

将Nginx架在HAProxy前面做HTTPS代理

网站为了安全考虑通常会使用https连接来传输敏感信息,https使用了ssl加密,HAProxy没法直接解析,所以要在HAProxy前面先架台Nginx解密,再转发到HAProxy做负载均衡。这样在Web服务器前面就存在了两个代理,为了能让它获取到真实的客户端IP,需要做以下配置。

首先要在Nginx的代理规则里设定proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;这样会让Nginx的https代理增加x_forwarded_for头信息,保存客户的真实IP。

其次修改HAProxy的配置option     forwardfor except 10.1.10.0/24这个配置和之前设定的差不多,只是多了个内网的IP段,表示如果HAProxy收到的请求是由内网传过来的话(https代理机器),就不会设定x_forwarded_for的值,保证后面的web服务器拿到的就是前面https代理传过来的。

为什么PHP里的HTTP_X_FORWARDED_FOR和Nginx的不一样

当你的网站使用了CDN后,用户会先访问CDN,如果CDN没有缓存,则回源站(即你的反向代理)取数据。CDN在回源站时,会先添加x_forwarded_for头信息,保存用户的真实IP,而你的反向代理也会设定这个值,不过它不会覆盖,而是把CDN服务器的IP(即当前remote_addr)添加到x_forwarded_for的后面,这样x_forwarded_for里就会存在两个值。Nginx会使用这些值里的第一个,即客户的真实IP,而PHP则会使用第二个,即CDN的地址。为了能让PHP也使用第一个值,你需要添加以下fastcgi的配置。fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;它会把nginx使用的值(即第一个IP)传给PHP,这样PHP拿到的x_forwarded_for里其实就只有一个值了,也就不会用第二个CDN的IP了。

忽略x_forwarded_for

其实,当你使用了Nginx的realip模块后,就已经保证了remote_addr里设定的就是客户端的真实IP,再看下这个配置set_real_ip_from   10.1.10.0/24;
real_ip_header     X-Forwarded-For;它就是把x_forwarded_for设为remote_addr,而nginx里的x_forwarded_for取的就是其中第一个IP。

使用这些设置就能保证你的remote_addr里设定的一直都是客户端的真实IP,而x_forwarded_for则可以忽略。

如何重置计划任务的错误和日志输出地址?

回复

zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 4262 次浏览 • 2017-04-24 11:58 • 来自相关话题

数据库中INFORMATION_SCHEMA的说明及使用

zkbhj 发表了文章 • 0 个评论 • 1486 次浏览 • 2017-03-23 14:39 • 来自相关话题

第一个查询看看库里有多少个表,表名等select * from INFORMATION_SCHEMA.TABLES
information_schema这张数据表保存了MySQL服务器所有数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。再简单点,这台MySQL服务器上,到底有哪些数据库、各个数据库有哪些表,每张表的字段类型是什么,各个数据库要什么权限才能访问,等等信息都保存在information_schema表里面。


Mysql的INFORMATION_SCHEMA数据库包含了一些表和视图,提供了访问数据库元数据的方式。

元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。


下面对一些重要的数据字典表做一些说明:

SCHEMATA表:提供了关于数据库的信息。

TABLES表:给出了关于数据库中的表的信息。

COLUMNS表:给出了表中的列信息。

STATISTICS表:给出了关于表索引的信息。

USER_PRIVILEGES表:给出了关于全程权限的信息。该信息源自mysql.user授权表。

SCHEMA_PRIVILEGES表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。

TABLE_PRIVILEGES表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。

COLUMN_PRIVILEGES表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。

CHARACTER_SETS表:提供了关于可用字符集的信息。

COLLATIONS表:提供了关于各字符集的对照信息。

COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。

TABLE_CONSTRAINTS表:描述了存在约束的表。

KEY_COLUMN_USAGE表:描述了具有约束的键列。

ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。

VIEWS表:给出了关于数据库中的视图的信息。

TRIGGERS表:提供了关于触发程序的信息。 查看全部
第一个查询看看库里有多少个表,表名等
select * from INFORMATION_SCHEMA.TABLES

information_schema这张数据表保存了MySQL服务器所有数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。再简单点,这台MySQL服务器上,到底有哪些数据库、各个数据库有哪些表,每张表的字段类型是什么,各个数据库要什么权限才能访问,等等信息都保存在information_schema表里面。


Mysql的INFORMATION_SCHEMA数据库包含了一些表和视图,提供了访问数据库元数据的方式。

元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。


下面对一些重要的数据字典表做一些说明:

SCHEMATA表:提供了关于数据库的信息。

TABLES表:给出了关于数据库中的表的信息。

COLUMNS表:给出了表中的列信息。

STATISTICS表:给出了关于表索引的信息。

USER_PRIVILEGES表:给出了关于全程权限的信息。该信息源自mysql.user授权表。

SCHEMA_PRIVILEGES表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。

TABLE_PRIVILEGES表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。

COLUMN_PRIVILEGES表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。

CHARACTER_SETS表:提供了关于可用字符集的信息。

COLLATIONS表:提供了关于各字符集的对照信息。

COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。

TABLE_CONSTRAINTS表:描述了存在约束的表。

KEY_COLUMN_USAGE表:描述了具有约束的键列。

ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。

VIEWS表:给出了关于数据库中的视图的信息。

TRIGGERS表:提供了关于触发程序的信息。

关于nginx服务器重大配置失误的发现(来自旭哥)

zkbhj 发表了文章 • 0 个评论 • 2096 次浏览 • 2017-03-16 09:34 • 来自相关话题

今天早上登录92号机器巡查,发现nginx主配置文件中有以下定义:
worker_processes  8;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
events {
    worker_connections  5196;
}


全是注释,表示使用默认的日志级别,默认的级别是error,对一个重要服务,仅仅记录error级别日志是远远不够的,我的习惯和经验是尽量记录为notice级别,以便及时发现问题,顺手修改为:
error_log  logs/error.log  notice;

nginx -s reload之后,在error.log中发现以下警告日志:
5196 worker_connections are more than open file resource limit: 1024
显然,nginx单个进程连接数被限制为1024,一共启用8进程,理论上nginx同时最多能打开8*1024个连接(这包括客户端、到后端机器的连接),对我们的应用理论上nginx最多同时能为1024*8/2个客户端(4096)服务,显然这个值配置的太低了。
高并发系统中,worker_connections应该配置尽可能高一些(如64K 65535),以便让nginx能充分利用硬件资源,能处理更多连接。

解决办法:
1. 配置nginx错误日志级别为notice
2. 配置nginx子进程最大连接数至少64K
2. 配置open_file_limit, 两种方式均可
   A. 系统中默认配置/etc/security/limits.conf
   B. nginx配置中全局指定:
   worker_rlimit_core 0;
   worker_rlimit_nofile 65535;

说明:这两项的默认值与系统的limits配置一致,但系统的默认值对高并发系统来说太保守了,需要提升。

扩展总结:对任何一个进程,都会受这些限制的影响(这是Linux的系统保护机制),要特别留意这两项参数的配置。
FPM:rlimit_files rlimit_core
MySQL: open_files_limit max_connections
对其它应用,如果在配置文件中没有此类参数的配置项,应该在启动脚本中指定:
ulimit -n 65535 -c 0
另外,如果条件允许,重要应用软件的日志级别一定要详细,推荐notice级别。 查看全部

今天早上登录92号机器巡查,发现nginx主配置文件中有以下定义:
worker_processes  8;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
events {
    worker_connections  5196;
}


全是注释,表示使用默认的日志级别,默认的级别是error,对一个重要服务,仅仅记录error级别日志是远远不够的,我的习惯和经验是尽量记录为notice级别,以便及时发现问题,顺手修改为:
error_log  logs/error.log  notice;

nginx -s reload之后,在error.log中发现以下警告日志:
5196 worker_connections are more than open file resource limit: 1024
显然,nginx单个进程连接数被限制为1024,一共启用8进程,理论上nginx同时最多能打开8*1024个连接(这包括客户端、到后端机器的连接),对我们的应用理论上nginx最多同时能为1024*8/2个客户端(4096)服务,显然这个值配置的太低了。
高并发系统中,worker_connections应该配置尽可能高一些(如64K 65535),以便让nginx能充分利用硬件资源,能处理更多连接。

解决办法:
1. 配置nginx错误日志级别为notice
2. 配置nginx子进程最大连接数至少64K
2. 配置open_file_limit, 两种方式均可
   A. 系统中默认配置/etc/security/limits.conf
   B. nginx配置中全局指定:
   worker_rlimit_core 0;
   worker_rlimit_nofile 65535;

说明:这两项的默认值与系统的limits配置一致,但系统的默认值对高并发系统来说太保守了,需要提升。

扩展总结:对任何一个进程,都会受这些限制的影响(这是Linux的系统保护机制),要特别留意这两项参数的配置。
FPM:rlimit_files rlimit_core
MySQL: open_files_limit max_connections
对其它应用,如果在配置文件中没有此类参数的配置项,应该在启动脚本中指定:
ulimit -n 65535 -c 0
另外,如果条件允许,重要应用软件的日志级别一定要详细,推荐notice级别。

Linux中service命令和/etc/init.d/有啥关系?

回复

zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 4298 次浏览 • 2017-03-08 09:14 • 来自相关话题

Linux上如何安装和写在rpm安装的软件?

回复

zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 4439 次浏览 • 2017-03-07 14:25 • 来自相关话题

Linux上如何挂载磁盘?

回复

zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 2739 次浏览 • 2017-03-06 10:41 • 来自相关话题

Linux下如何查看磁盘相关信息?

回复

zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 2771 次浏览 • 2017-03-03 15:41 • 来自相关话题

请求的cookie过大,服务器异常问题如何分析?

回复

zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 5438 次浏览 • 2017-03-03 12:00 • 来自相关话题

nginx 配置之 proxy_pass 神器

zkbhj 发表了文章 • 0 个评论 • 1595 次浏览 • 2017-03-03 10:00 • 来自相关话题

proxy 模块是 nginx 中最碉堡的模块之一。就是有了 proxy 模块,nginx 才能和其它 http 服务器关联起来,极大程度地提高了 nginx 的可用性。proxy 这个模块比较大,光是指令就有一大堆。这里我们只是简单地介绍一下它最核心的指令 —— proxy_pass 。

当我们遇到跨域问题,而且客户端无法支持 CORS 时,最好的办法就是让服务器来做代理。在前端页面所在的服务器 nginx 配置上开一个路由,然后使用 proxy 去请求另一个域名下的资源。如果跨域资源也部署在同一台机器上,我们甚至可以 proxy 到 127.0.0.1,比如:location /api { proxy_pass http://127.0.0.1:1234; }
当客户端请求 /api 这个路径下的资源时服务器就会帮助我们去 127.0.0.1 的 1234 端口上取资源,解决了跨域的问题。proxy_pass 会将当前的 $uri 带过去,所以如果 /api 这个路由是我们擅自加的,在发送到目标服务前可以使用 rewrite 来处理掉这个多余的路由,比如:location /api/ { rewrite ^/api/(.*) /$1 break; proxy_pass http://127.0.0.1:1234; }
rewrite 的作用是修改 $uri,但要注意 rewrite 要有个重新匹配 location 的副作用。由于 proxy_pass 的处理阶段比 location 处理更晚,所以这里需要 break 掉,以防止 rewrite 进入下一次 location 匹配而丢失 proxy_pass。

另外还有一个值得注意的地方,proxy_pass 后面的 host 如果填写一个域名的话,这个域名将会在 nginx 启动时解析。如果 nginx 启动时域名无法解析将会抛出异常无法启动,比如: location /api { proxy_pass http://xxx; } nginx: [emerg] host not found in upstream "xxx"
而且由于 nginx 解析域名是在启动时做的,所以在 nginx 启动之后修改域名的解析对 nginx 是不会生效的。

如果觉得让 nginx 启动时去查询 DNS 这件事不靠谱(我就不推荐这么做,因为 DNS 确实是不可控的),那么可以在 proxy_pass 时到某个 IP 上,hostname 可以通过 porxy_set_header 指令强制设置 proxy 的 HTTP 请求中的 Host 字段来修改它,比如:location /api { proxy_set_header Host api.web-tinker.com; proxy_pass http://127.0.0.1:8080; } 
除了设置 Host 这个请求头之外,proxy_set_header 还能设置别的头,只要你的脑洞够大就可以用它来做更多奇怪的事情!

另外还有个要注意的点。proxy_pass 默认使用的是 http 1.0,可以通过 proxy_http_version 指令让它使用 http 1.1,以便开启 keepalive 之类的功能。location /api { proxy_http_version 1.1; proxy_pass http://127.0.0.1:8080; } 查看全部
proxy 模块是 nginx 中最碉堡的模块之一。就是有了 proxy 模块,nginx 才能和其它 http 服务器关联起来,极大程度地提高了 nginx 的可用性。proxy 这个模块比较大,光是指令就有一大堆。这里我们只是简单地介绍一下它最核心的指令 —— proxy_pass 。

当我们遇到跨域问题,而且客户端无法支持 CORS 时,最好的办法就是让服务器来做代理。在前端页面所在的服务器 nginx 配置上开一个路由,然后使用 proxy 去请求另一个域名下的资源。如果跨域资源也部署在同一台机器上,我们甚至可以 proxy 到 127.0.0.1,比如:
location /api { proxy_pass http://127.0.0.1:1234; } 

当客户端请求 /api 这个路径下的资源时服务器就会帮助我们去 127.0.0.1 的 1234 端口上取资源,解决了跨域的问题。proxy_pass 会将当前的 $uri 带过去,所以如果 /api 这个路由是我们擅自加的,在发送到目标服务前可以使用 rewrite 来处理掉这个多余的路由,比如:
location /api/ { rewrite ^/api/(.*) /$1 break; proxy_pass http://127.0.0.1:1234; } 

rewrite 的作用是修改 $uri,但要注意 rewrite 要有个重新匹配 location 的副作用。由于 proxy_pass 的处理阶段比 location 处理更晚,所以这里需要 break 掉,以防止 rewrite 进入下一次 location 匹配而丢失 proxy_pass

另外还有一个值得注意的地方,proxy_pass 后面的 host 如果填写一个域名的话,这个域名将会在 nginx 启动时解析。如果 nginx 启动时域名无法解析将会抛出异常无法启动,比如:
 location /api { proxy_pass http://xxx; } nginx: [emerg] host not found in upstream "xxx" 

而且由于 nginx 解析域名是在启动时做的,所以在 nginx 启动之后修改域名的解析对 nginx 是不会生效的

如果觉得让 nginx 启动时去查询 DNS 这件事不靠谱(我就不推荐这么做,因为 DNS 确实是不可控的),那么可以在 proxy_pass 时到某个 IP 上,hostname 可以通过 porxy_set_header 指令强制设置 proxy 的 HTTP 请求中的 Host 字段来修改它,比如:
location /api { proxy_set_header Host api.web-tinker.com; proxy_pass http://127.0.0.1:8080; }
 
除了设置 Host 这个请求头之外,proxy_set_header 还能设置别的头,只要你的脑洞够大就可以用它来做更多奇怪的事情!

另外还有个要注意的点。proxy_pass 默认使用的是 http 1.0,可以通过 proxy_http_version 指令让它使用 http 1.1,以便开启 keepalive 之类的功能。
location /api { proxy_http_version 1.1; proxy_pass http://127.0.0.1:8080; }