nginx-ep2

ooowl
  • 系统设计
  • 网关
  • 网关
  • nginx
About 19 min

nginx-ep2

配置文件

使用#来注释
一般来说分三大块

  • 全局块,社设置nginx运行的时候软件的相关的变量
  • events块,和nginx服务器网络相关的内容,和性能相关比较大
  • http块,日志服务第三方等都在这个块中配置
    • server块,在http里面配置每一个供访问的资源路径,可以配置多个
# 这是把注释了的都删掉的nginx配置文件,可以当原型看待
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}

配置命令

📌Tip

尽量记下来配置不对的时候会报的常见错误,这样出了问题好排查,指令和问题这里记一下,后面也会有专门的问题记录
不同层级的块会互相覆盖,更细粒度的块配置会覆盖更粗的,可以

全局块

语法默认值位置说明
user user_name [user_group];nobody全局块用户组默认使用用户名的组权限启动进程运行,此指令指定的用户访问无权限的目录比如html文件夹指定到了没权限的文件夹,页面会直接403
master_process on;on全局块如果设为off 表示禁用主进程,而是直接以单进程模式运行。只会使用一个 worker 进程来处理所有请求。在这种情况下,Nginx 将无法平滑地重启或重新加载配置文件
worker_process auto;auto全局块auto自己获取处理器核心数量,配置 worker 进程的数量可以是数字指定几个
daemon on;on全局块是否守护进程运行
pid file_name;/var/run/nginx.pid全局块默认是指定pid文件存放在哪
error_log logs/error.log error;/var/log/nginx/error.log error全局块、http、server、location警报级别debug info notice warn error crit alert emerg最好不要设置info以下,写入太多了会占用IO导致性能下降
include file;any包含其他的子配置文件,便于管理,子文件的内容会被按照块的顺序完全继承

events块

📌Tip

"惊群"问题 大致意思是在某一个时刻,客户端发来一个请求连接,Nginx后台是以多进程的工作模式,也就是说有多个worker进程会被同时唤醒,但是最终只会有一个进程可以获取到连接,如果每次唤醒的进程数目太多,就会影响Nginx的整体性能。如果将accept_mutex值设置为on(开启状态),将会对多个Nginx进程接收连接进行序列号,一个个来唤醒接收,就防止了多个进程对连接的争抢

语法默认值位置说明
accept_mutex on;onevents解决"惊群"问题,
multi_accept ononevents
worker_connections 512;512events这里的连接数不仅仅包括和前端用户建立的连接数,而是包括所有可能的连接数。另外,其值不能大于操作系统支持打开的最大文件句柄数量
use method;select/poll/epoll/kqueueevents此值会根据操作系统自己获得,就是使用什么样的事件驱动

http块

语法默认值位置说明
include mime.types;httpnginx需要区分不同的请求携带的内容类型,这些类型是通过MIME来指定的,这些东西被存放到mime.types,http直接引用就可以了
default_type text/plain;text/plainhttp、server、location某些接口需要返回固定的文本字符串或者json,如果逻辑非常简单或者干脆是固定的字符串,可以直接使用nginx实现这样速度会快
sendfile off;offhttp、server、location是否使用sendfile()传输文件,可大大提高Nginx处理静态资源的性能,会额外消耗一些内存,无法传输动态文件,因为是跨过用户态直接从内核态复制文件,注意权限问题
tcp_nopush off;offhttp、server、locationsendfile打开才会生效,包在缓存区满了才会发
tcp_nodelay on;onhttp、server、locationkeep-alive打开才会生效,有包就发
keepalive_timeout 75s;75shttp、server、locationtcp长连接超时时间
keepalive_requests 100;100http、server、location每个tcp长连接的复用次数
Click to see more

直接返回简单的字符

location /get_text {
	#这里也可以设置成text/plain
    default_type text/html;
    return 200 "This is nginx's text";
}
location /get_json{
    default_type application/json;
    return 200 '{"name":"TOM","age":18}';
}

server块

server {
    listen       80; # 监听什么端口
    server_name  localhost; # 监听哪个域名
    location / { # 访问的哪个资源
        root   html; # 资源的根目录
        index  index.html index.htm; # 从前往后找,优先返回
    }
   
    error_page   500 502 503 504 404  /50x.html; # 捕捉状态码进行处理
    location = /50x.html { # 直接访问500页面的时候会返回首页
        root   html;
    }
}

listen只能在server块里面设置后面参数有很多,使用的时候可以去文档open in new window

listen 127.0.0.1:8000; 监听指定的IP和端口
listen 127.0.0.1;	监听指定IP的所有端口
listen 8000;	监听指定端口上的连接
listen *:8000;	监听指定端口上的连接

一般配合server_name使用, 唯一确定访问host

Warning

listen 8080 default_server;default_server 标志此为一个默认服务期
当匹配不到指定的server_name的时候,会使用以下策略(不指定服务器 server_name __; )

  • 没有匹配的 server_name 时,会使用默认服务器处理该请求。
  • 没有匹配的 server_name 且没有指定默认服务器,Nginx 将选择配置文件中的第一个 server 块来处理请求。

server_name name 配置可以匹配多个用空格隔开,精确匹配通配符正则表达式都支持
使用通配符的时候, 可以出现在前缀和后缀,*.name.* 但是不能www.name.c*
使用~作为正则表达式字符串的开始标记,比如~^www\.(\w+)\.com$

server_name的优先级问题

  1. 精确匹配server_name
  2. 通配符在前缀匹配server_name成功
  3. 通配符在后缀匹配server_name成功
  4. 正则表达式匹配server_name成功
  5. 被默认的default_server处理,如果没有指定默认找第一个server

location块

单独拿出来好看

location / { # 访问的哪个资源
    root   html; # 资源的根目录
    index  index.html index.htm; # 从前往后找,优先返回
}

匹配路径:
默认匹配路径是只要是按照这个开头的就行,相当于/url(.*)
= 用于不包含正则表达式的uri前,必须与指定的模式精确匹配,带后缀斜杠关键字参数都不行
~ 表示使用正则表达式,并且区分大小写换~*不区分

root 和 alias 都是指定资源路径,root在找不到指定的绝对路径的时候会使用nginx的安装的root路径+资源相对路径,alias只接受绝对路径而且会无视location的规定直接访问alias中指定的资源,一般来会和location指定的完全相同,斜杠也是

语法默认值位置说明
index index.html index2.htmlindex.htmlhttp、server、location从前往后找,第一个为止
error_page 500=200 http://www.google.com;_http、server、location可以配置多个,也是从上往下找匹配,等于号可以更改状态码

日志

语法默认值位置说明
access_log /var/log/nginx/access.log name buffer=32k;/var/log/nginx/access.loghttp、server、location日志的拦截规则,main是此日志规则的名字,可与log_format配合,指定日志的格式
log_format name escape=default 'format' ;http规定日志输出的格式,配合上面使用
escape用于指定日志中的特殊字符的转义方式。它有三个可选值:
  • default: 使用默认转义方式,将特殊字符转义为它们的可见ASCII码表示,比如将换行符转义为\n
  • json: 将日志转义为JSON格式,这样可以方便地在日志分析工具中解析和使用。
  • none: 不进行任何转义,特殊字符将以原始形式输出。 format中规定了日志具体的每行输出格式,示例'$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"'

静态压缩

算力换带宽

ngx_http_gzip_module 会自己启用 ngx_http_gzip_static_modulengx_http_gunzip_module 需要自己添加,模块引入之后就有新命令可以用了

语法默认值位置说明
gzip off;offhttp、server、location开启静态资源压缩,只有开启了此模块的其他指令才会起效
gzip_types mime-type;text/htmlhttp、server、location对指定的类型开启压缩,建议直接指定mime-type就行了,多个值空格分开也可以用 * 来表示都压缩
gzip_comp_level 1;1http、server、location压缩率越大越文件越小CPU占用越高自己权衡
gzip_vary off;offhttp、server、locationheader中携带Vary:Accept-Encoding;告诉此响应已压缩过
gzip_buffers 23khttp、server、location缓冲区参数,不用自己设置,会根据系统自动设置
gzip_disable -;-http、server、location设置一个正则,UA符合此正则的就不启用gzip,此举是为了低版本浏览器不支持gzip时可禁用,或者移动端压缩pc不压缩节省流量。不建议自己设置
gzip_http_version 1.1;1.1http、server、location使用gzip的最低http版本
gzip_min_length 20;20(Bytes)http、server、location小于此字节的不压缩
gzip_proxied off;offhttp、server、location作为反向代理服务器的时候是否对服务器返回至此的内容压缩再返回
gzip_proxied的选项值
off # 不启用
any # 启用压缩
expired # header头中包含 "Expires" 的时候启用压缩
no-cache # header头中包含 "Cache-Control:no-cache" 的时候启用压缩
no-store # header头中包含 "Cache-Control:no-store" 的时候启用压缩
private # header头中包含 "Cache-Control:private" 的时候启用压缩
no_last_modified # header头中不包含 "Last-Modified" 的时候启用压缩
no_etag # header头中不包含 "ETag" 的时候启用压缩
auth # header头中包含 "Authorization" 的时候启用压缩

Warning

sendfile()跨过用户态直接从内核态复制文件,但是Gzip要想对资源压缩,是需要经过用户进程进行操作的,会冲突。 如果响应的内容是可压缩的(如文本文件),NGINX会将内容进行gzip压缩,否则就正常通过sendfile发送包。
如果要解决这个冲突需要在编译的时候指定插件,配置ngx_http_gzip_static_modulegzip_static命令解决,先把资源一次性压缩完放那,gzip会直接拿,此时头信息里面也会多两个信息指定Content-Encoding: gzip; Vary:Accept-Encoding;

缓存

nginx主要是设置浏览器缓存,服务器缓存不在这讨论

header说明 HTTP协议中和页面缓存相关的字段
Expires缓存过期的日期和时间
Cache-Control设置和缓存相关的配置信息
Last-Modified请求资源最后修改时间
ETag请求变量的实体标签的当前值,比如文件的MD5值
语法默认值位置说明
expires off;offhttp、server、location如果直接写整数值以秒计数,负数就不缓存;epoch或max会把Cache-Control的值设置为no-cache或者10年
add_header Cache-control no-cache always;-http、server、location自定义添加头信息,always代表不检查兼容直接添加
Cache-Control作为响应头可以设置如下值
Cache-control: must-revalidate可缓存但必须再向源服务器进行确认
Cache-control: no-cache缓存前必须确认其有效性
Cache-control: no-store不缓存请求或响应的任何内容
Cache-control: no-transform代理不可更改媒体类型
Cache-control: public可向任意方提供响应的缓存
Cache-control: private仅向特定用户返回响应
Cache-control: proxy-revalidate要求中间缓存服务器对缓存的响应有效性再进行确认
Cache-control: max-age=<秒>响应最大Age值
Cache-control: s-maxage=<秒>公共缓存服务器响应的最大Age值

跨域

CORS

跨域是最基本的浏览器安全服务没有甚至会崩溃,跨域需要被访问的服务配置头信息,不仅要在服务框架中配置还要在网关中配置
add_header Access-Control-Allow-Origin *; 允许哪些URI跨域访问服务,多个可以用逗号add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE; 允许哪些方法访问,

下面三个看看知道就行,用的时候再看

  1. add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept, Authorization': 这个配置指定了允许跨域请求携带的额外头信息。这些头信息在跨域请求时需要进行预检(OPTIONS请求)来确认是否允许。常见的头信息包括Origin、X-Requested-With、Content-Type、Accept和Authorization等。这里我们允许请求携带这些头信息。
  2. add_header 'Access-Control-Allow-Credentials' 'true': 这是一个可选配置。如果你的跨域请求需要携带认证信息(如cookies或HTTP认证),则需要将该配置设置为true。如果不需要携带认证信息,则可以省略此配置,默认值为false。
  3. add_header 'Access-Control-Max-Age' 3600: 这是一个可选配置,用于设置预检请求(OPTIONS请求)的缓存时间,单位为秒。预检请求是浏览器在发送真实请求之前发送的检查请求,以确保跨域请求是安全的。通过设置缓存时间,可以减少不必要的预检请求,提高性能。在这里,设置缓存时间为3600秒(1小时)。

Warning

请注意,这些配置仅适用于HTTP响应头,因为跨域访问是由浏览器执行的安全策略。因此,它们只影响浏览器发起的跨域请求。如果你使用其他方式(例如服务器到服务器通信,RPC等),则可能不受这些配置的限制。

防盗链

(Hotlinking)使用Referer字段,表明此链接从哪过来的,服务器根据Referer信息来判断是否为信任的,不是则可以返回403(服务端拒绝访问)。

server {
    listen 80;
    server_name yourdomain.com;

    location /images/ { # 整个文件夹下的都验证Referer
        valid_referers none blocked yourdomain.com; # 允许来自yourdomain.com的请求,也可添加多个域名,空格隔开,例如:yourdomain.com example.com
        if ($invalid_referer) {
            return 403;
        }
        root /path/to/your/images;
    }
}

配置中的valid_referers是允许什么域名访问,一般的域名可以自己定义,注意none 和blocked代表Referer空或者直接不携带该字段的,配置上了也会允许。
此指令并不直接拦截或者放行,而是更改内置变量$invalid_referer的值,需要判断后自己做处理
更细粒度的控制使用第三方模块ngx_http_accesskey_module,需要了自己去官网找

重定向

Nginx的rewrite功能通常需要使用PCRE(Perl Compatible Regular Expressions)库但是会自动启用不用额外配置,Nginx使用的是ngx_http_rewrite_module 模块open in new window

  • 重定向浏览器地址会发生变化而转发则不变
  • 一次重定向会产生两次请求而一次转发只会产生一次请求
  • 地址重定向到的页面必须路径而地址转发则不需要
  • 地址重定向因为是两次请求所以request范围内属性不能传递给新页面而地址转发因为是一次请求所以可以传递值
  • 地址转发速度快于地址重定向
语法默认值位置说明
set $var value;-http、server、location设置一个变量可以是字符串,变量或者组合,不能与Nginx服务器预设的全局变量同名
if (condition){}-server、location判断 = !=
break;-server、location、if此命名空间中break之后的指令全都失效
return 200 abc;-server、location、if返回此响应就结束了,可以放字符串json也可以直接放一个url可以跳转过去,如果想要展示可以使用default_type去指定
rewrite ^/rew/(url)\w*$ http://baidu.com/?$1=abc-server、location、if匹配/rew/url和任意字符,跳转到http://baidu.com/?url=abc 跳转的时候可以使用正则匹配的变量

用法如下

  • ~代表匹配正则表达式过程中区分大小写,
  • ~*代表匹配正则表达式过程中不区分大小写
  • !~!~*刚好和上面取相反值,如果匹配上返回false,匹配不上返回true
  • -f $document_uri时,如果请求的文件存在返回true,不存在返回false,!f $document_uri返回的状态相反 Rewrite常用全局变量
变量说明
$http_user_agent用户访问服务的代理信息(如果通过浏览器访问,记录的是浏览器的相关版本信息)
$host访问服务器的server_name值
$document_root当前请求对应location的root值,如果未设置,默认指向Nginx自带html目录所在位置
$content_length请求头中的Content-Length的值
$content_type请求头中的Content-Type的值
$http_cookie客户端的cookie信息,可以通过add_header Set-Cookie 'cookieName=cookieValue'来添加cookie数据
$limit_rateNginx服务器对网络连接速率的限制,也就是Nginx配置中对limit_rate指令设置的值,默认是0,不限制。
$remote_addr客户端的IP地址
$remote_port客户端与服务端建立连接的端口号
$remote_user客户端的用户名,需要有认证模块才能获取
$scheme访问协议
$server_addr服务端的地址
$server_name客户端请求到达的服务器的名称
$server_port客户端请求到达服务器的端口号
$server_protocol客户端请求协议的版本,比如"HTTP/1.1"
$request_body_file发给后端服务器的本地文件资源的名称
$request_method客户端的请求方式,比如"GET","POST"等
$request_filename当前请求的资源文件的路径名
$document_uri当前访问地址的URI,功能和$uri一样,但不带参数
$request_uri当前请求的URI,并且携带请求参数,去除掉ip和端口后的server的唯一资源
$args请求URL中的关键字参数,功能和$query_string一样

可以在日志的format中用

Warning

if指令在location块中使用时,其影响其他location的原因是因为if条件的判断逻辑是在配置加载时生成的,而不是在每个请求级别上执行的。 当Nginx配置文件加载时,它会解析所有的location块,并对每个location中的if条件进行求值,并为每个location生成对应的判断逻辑。这些判断逻辑在服务器运行期间是静态的,不会随着请求的不同而变化

server {
    listen 80;
    server_name example.com;

    location / {
        if ($uri ~* \.(jpg|png|gif)$) {
            return 403;
        }
        proxy_pass http://backend;
    }

    location /admin {
        if ($uri ~* \.(jpg|png|gif)$) {
            return 404;
        }
        proxy_pass http://backend_admin;
    }
}

在上面的配置中,两个location块都有相同的if条件,即匹配URI是否以图片文件结尾。如果有一个请求 "/example.jpg",它会匹配到两个location块的if条件,即使第一个location块中的if条件返回了403,第二个location块的if条件也会对其进行判断。这会导致最终请求被第二个location块返回404,而不是预期的403。 #TODO 阴间的ifopen in new window

反向代理

正向代理就是客户端和代理服务器是一块的,请求数据的时候,服务器看到的是代理服务器的地址。
反向代理是代理服务器和服务端是一块的,请求数据的时候客户端看到的是统一的代理服务器的地址,常用来做反向代理。
只能在location里面加,注意斜杠的作用

proxy_passproxy_set_headerproxy_redirect

location /abc{ # 注意这个location
        # proxy_pass http://192.168.0.1; 
        # 上面这个相当于访问http://192.168.0.1/abc/index.html
        # 如果是http://192.168.0.1/aaa,那访问的会是http://192.168.0.1/aaaindex.html
        proxy_pass http://192.168.0.1/; # 这个相当于访问http://192.168.0.1/index.html
        proxy_set_header username TOM; # 可以在这里设置带到服务器的任意的头使用$username,可以放在http、server、location
}

如果location是只有一个根目录,那加不加效果都是一样的

proxy_redirect 修改301或者302转发过程中的Location。默认值为proxy_redirect default

location / {
    proxy_pass http://192.168.8.46:8080; #去掉/
    proxy_redirect off #修改默认值default为off
    }

如果去掉最后的/以后
curl -I http://192.168.8.46/haha4
访问会显示 Location: http://192.168.8.46:8080/haha4/
浏览器访问显示的地址栏为http://192.168.8.46:8080/haha4/,(如果还是之前的,需要先删缓存)
真实的Location地址全部暴露出来的,这个时候就需要使用proxy_redirect修改这个Location

location / {
    proxy_pass http://192.168.8.46:8080;
    proxy_redirect http://192.168.8.46:8080/haha4/ http://192.168.8.46/haha4/;
}

这样,就能修改Location的地址,Location: http://192.168.8.46/haha4/,在浏览器里也是如此,就不会暴露端口号等信息,当然你还可以直接重定向Location弄到其它网站上去
proxy_redirect http://192.168.8.46:8080/haha4/; 然后浏览器就跳转过去了

SSL证书

一般的安全控制策略是nginx将不同的请求转发到不同的服务器端口,服务器防火墙只开放应用端口就可以,维护nginx网关而不是服务器。
Nginx的模块是没有添加http_ssl_module的,需要自己手动编译添加(docker的镜像应该有的)
基本所有的命令都是在http,server块中配置

server{
    listen 443 ssl; # 等于 ssl on; 
    server_name e.ooowl.fun; # 监听的域名
    
    ssl_certificate server.cert;
    ssl_ceritificate_key server.key; # 这个后缀一般是key,另一个不一定是啥
    
    ssl_session_timeout 5m; # session缓存的超时时间
    ssl_ciphers HIGH:!aNULL:!MD5; # 指出允许的密码,密码指定为OpenSSL支持的格式
    ssl_perfer_server_ciphers off; # 指定是否服务器密码优先客户端密码 
    ssl_session_cache none; # 见下面详解
    
    location / {
        root   html;
        index  index.html index.htm;
    }
}
语法ssl_sesion_cache off|none|[builtin[:size]] [shared:name:size]
默认值ssl_session_cache none;
位置http、server
  • off:禁用会话缓存,客户端不得重复使用会话
  • none:禁止使用会话缓存,客户端可以重复使用,但是并没有在缓存中存储会话参数
  • builtin:内置OpenSSL缓存,仅在一个工作进程中使用。
  • shared:所有工作进程之间共享缓存,缓存的相关信息用name和size来指定

可以建立一个监听80端口的,然后通过rewrite重定向到https

server {
	listen	80;
	server_name e.ooowl.fun;
	access_log logs/access.log main;
	location / {
		rewrite ^(.*) https://e.ooowl.fun$1; # 重定向
	}
}

证书申请下来就有两个文件server.key,server.cert,分别配置就好,证书不会随着ip或者机器变化而变化,证书有子域名,泛域名证书,可以使用acme.sh进行 证书自动续期open in new window ,也可以使用certbot

Click to see more

** 我自己配置文件的一个例子**

server {
  # nps管理端口;
  server_name e.ooowl.fun;
  listen 443 ssl http2;
  ssl_certificate /home/nginxWebUI/.acme.sh/chain.cer;
  ssl_certificate_key /home/nginxWebUI/.acme.sh/fun.key;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
  listen 80;
  if ($scheme = http) {
    return 301 https://$host:443$request_uri;
  }
  location / {
    proxy_pass http://127.0.0.1:800006;
    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 $http_host;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_redirect http:// https://;
  }
}
Loading...