标签 nginx 下的文章

2021.3.15更新

网站套上了Cloudflare CDN后,因为服务器隐藏在cloudflare的后端,所以网站实际接收到访问请求都是Cloudflare的节点服务器发送过来的,那么访客IP也都是Cloudflare的节点服务器的IP,不是访客真实的IP。

如果只是需要nginx的日志记录访客的真实IP,那么不需要开启ngx_http_realip_module模块,只需要调整一下nginx的日志格式log_format即可。

Cloudflare 会将原始访问者 IP 地址包含在 X-Forwarded-For 和 CF-Connecting-IP 请求标头header中,我们只需要增加一个新的nginx log_format。

Nginx的log_format有一个默认的combined格式,具体可以参考nginx的帮助文档http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

增加一个新的log_format:

1、编辑nginx.conf文件 sudo vim /usr/local/nginx/conf/nginx.conf

2、在sever指令加载前添加新的log_format,并保存

log_format csf '$http_x_forwarded_for - $remote_user [$time_local] '
               '"$request" $status $body_bytes_sent '
               '"$http_referer" "$http_user_agent"';

3、对需要开启access log的网站,编辑对应的配置文件 sudo vim /usr/local/nginx/conf/vhost/example.site.conf

4、在对应的sever指令中加入相应的access_log设置

access_log /home/wwwlogs/example.site.access.log csf;

5、重启Nginx,sudo lnmp nginx restart

这样,Nginx的访问日志记录的就是访客的真实IP了,当然如果不只是需要在access log中记录访客真实IP,那可以继续往下看哦。


2018.8.20分割线

写了一个小网站,刚开始估计也没什么流量,打算开启一下nginx的访问日志access_log,不过问题来了,因为上线时直接套上了Cloudflare CDN,结果发现访问日志里的IP全是Cloudflare节点服务器IP,不是访客的真实IP,这有点尴尬了,拿不到真实IP访问日志就没啥用了,不好分析哪个访问是蜘蛛,哪个访问是真实访客,想着这个问题应该有解决方案,直接在Cloudflare帮助里搜了一下,可以可以,确实有,https://support.cloudflare.com/hc/en-us/articles/200170706-How-do-I-restore-original-visitor-IP-with-Nginx,可以用nginx的 ngx_http_realip_module 模块来获取访客的真实IP,那还说什么,赶紧操作起来。

在网站的nginx配置文件里,加上Cloudflare提供的配置参数

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;

# use any of the following two
real_ip_header CF-Connecting-IP;
#real_ip_header X-Forwarded-For;

大概意思就是列表里的ip访问时,会在请求的header参数CF-Connecting-IPX-Forwarded-For中添加访客的真实IP,ngx_http_realip_module模块会在列表中的IP访问时,直接从CF-Connecting-IP中获取访客的真实IP,这些IP是Cloudflare的节点服务器IP,可能会不定时的更新,在这里 https://www.cloudflare.com/ips/ 可以获取到最新的Cloudflare节点服务器IP列表。

配置好后,重载nginx,/etc/init.d/nginx reload,报错

Reload service nginx... nginx: [emerg] unknown directive "set_real_ip_from" in /usr/local/nginx/conf/vhost/xxx.com.conf:10
 done

这是因为ngx_http_realip_module模块默认是不启用的,需要在编译安装时添加配置参数 --with-http_realip_module 开启,然后重新编译安装一下nginx。

如果用的是lnmp一键安装脚本,那就改一下脚本里的nginx编译配置参数,lnmp1.5版本的在 include/nginx.sh 文件的70行

   if echo ${Nginx_Ver} | grep -Eqi 'nginx-[0-1].[5-8].[0-9]' || echo ${Nginx_Ver} | grep -Eqi 'nginx-1.9.[1-4]$'; then
        ./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_spdy_module --with-http_gzip_static_module --with-ipv6 --with-http_sub_module ${Nginx_With_Openssl} ${Nginx_Module_Lua} ${NginxMAOpt} ${Nginx_Modules_Options}
    else
        ./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --with-stream --with-stream_ssl_module ${Nginx_With_Openssl} ${Nginx_Module_Lua} ${NginxMAOpt} ${Nginx_Modules_Options}
    fi  

在编译配置参数里加上 --with-http_realip_module

   if echo ${Nginx_Ver} | grep -Eqi 'nginx-[0-1].[5-8].[0-9]' || echo ${Nginx_Ver} | grep -Eqi 'nginx-1.9.[1-4]$'; then
        ./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_realip_module --with-http_stub_status_module --with-http_ssl_module --with-http_spdy_module --with-http_gzip_static_module --with-ipv6 --with-http_sub_module ${Nginx_With_Openssl} ${Nginx_Module_Lua} ${NginxMAOpt} ${Nginx_Modules_Options}
    else
        ./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_realip_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --with-stream --with-stream_ssl_module ${Nginx_With_Openssl} ${Nginx_Module_Lua} ${NginxMAOpt} ${Nginx_Modules_Options}
    fi  

lnmp1.5支持单独安装nginx,所以只需要重新编译安装一下nginx即可,安装包目录下运行

./install.sh nginx

等上几分钟,编译安装nginx完成,OK,nginx的访问日志记录的是访客的真实IP了,当然不只是Cloudflare CDN,如果用其他的CDN,这种方法也可以的,不过配置里相应的节点IP要换成对应的CDN服务商提供的IP。

PS,如果跑affiliate markting的话,可能会用到追踪系统,比如iMobiTrax这样的可以自建的追踪系统,然后会发现在加上cloudfalre后,访客日志报表里记录的IP全是cloudflare的节点服务器IP,不是真实的访客IP,哈哈,是不是很尴尬,而且iMobiTrax又是加密的,没有办法通过修改源代码来获取真实IP,之前用的时候遇到过,找了很久,没有找到一个可用的解决方案,最后不了了之只好把cloudflare去掉了,不过,今天遇到这个问题正好想到了,也测试了一下,用上边的修改nginx的方式是可以的哦,给nginx添加ngx_http_realip_module模块,然后修改一下相应配置,就可以愉快的给iMobiTrax这样的追踪系统套上Cloudflare CDN了,再也不怕获取不到访客的真实IP了,当然不仅是iMobiTrax,其他的程序也一样,因为这样nginx会直接把真实IP传递给程序,不需要在程序里修改代码来获取真实IP了。

nginx禁止访问文件或目录的配置方法

nginx禁止访问指定后缀的文件

location ~ .*\.(ini|inc|conf|dat|txt)$ {
    # 正则表达式匹配,禁止访问后缀为ini,inc,conf,dat,txt的文件
    # 修饰符 ~ 表示正则匹配区分大小写,可以使用修饰符 ~* 表示不区分大小写
    deny all;
}

nginx禁止访问指定目录

location ^~ /path1/ {
    # 普通字符串前缀匹配,修饰符 ^~ 表示如果匹配到这条普通规则后,不再进行之后的正则搜索了
    deny all;
}

location ^~ /path2/ {
    deny all;
}

也可以使用正则匹配同时禁止访问多个指定目录

location ~ ^/(App|config|Include|Theme|vendor)/ {
    deny all;
}

nginx禁止访问指定目录中的特定后缀的文件

location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { 
    # 禁止访问这些指定目录中的php文件    
    deny all;
}

nginx location指令的uri匹配规则

nginx可以通过配置location指令来对用户请求的uri进行调度,执行不同的文件。

location指令有两种匹配模式,普通字符串前缀匹配和正则表达式匹配。

普通字符串前缀匹配必须以相应的uri开头,如下的匹配规则,必须以/some/path开头的uri才会被匹配,比如/some/path/document.html,而/my-site/some/path不会被匹配。

location /some/path/ {
    ...
}

匹配的顺序是先匹配普通字符串前缀,再匹配正则表达式,不过正则匹配优先级高于普通匹配除非使用修饰符 ^~

语法规则

location [ = | ~ | ~* | ^~ ] uri { ... }

location = /uri        =  开头表示精确匹配,只有完全匹配上才能生效,匹配成功则停止其他匹配。
location ^~ /uri       ^~ 开头对uri路径进行普通字符串前缀匹配,匹配成功则不再进行正则匹配。
location ~ pattern     ~  开头表示区分大小写的正则匹配。
location ~* pattern    ~* 开头表示不区分大小写的正则匹配。
location /uri          不带任何修饰符的普通字符串前缀匹配,优先级低于正则匹配。
location /             通用匹配,任何未匹配到其它location的请求都会匹配到。

匹配执行流程

  1. 查找普通字符串前缀的匹配规则
  2. 以修饰符 = 开头的普通字符串进行精确匹配,如果匹配到,则停止之后的规则查找
  3. 如果普通字符串以修饰符 ^~ 开头,并且是匹配到的最长普通字符串前缀,在匹配到这条规则后不再进行之后的正则查找了
  4. 存储匹配到的最长普通字符串前缀规则
  5. 查找正则表达式的匹配规则
  6. 在所有正则匹配规则中找到第一个匹配项后,就以此项为最终匹配结果
  7. 如果没有查找到正则表达式匹配,则以4中存储的普通匹配为最终匹配结果

普通字符串前缀匹配与配置文件中的前后定义顺序无关,匹配顺序从长到短,正则表达式匹配则受定义的前后顺序影响。