0%

nginx配置和源码分析

前言

一、配置

1. 配置实例

1.1. 80端口转443

1
2
3
4
5
server {
listen 80 default_server;
listen [::]:80 default_server;
rewrite ^ https://$http_host$request_uri? permanent;
}

1.2. 请求url代理到另一个地址

  • proxy_pass会自动将get参数和post参数带到代理的地址去,不需要加上$query_string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location /test {
# 代理地址如果不带后面地址,将会请求 http://127.0.0.1:1234/test
proxy_pass http://127.0.0.1:1234;

# 代理地址如果带后面地址,将会请求 http://127.0.0.1:1234/aaa
proxy_pass http://127.0.0.1:1234/aaa;
}

# 假设请求的地址为 http://x.x.x.x/test/bbb
location /test/ {
# 代理地址如果不带后面地址,将会请求 http://127.0.0.1:1234/test/bbb
proxy_pass http://127.0.0.1:1234;

# 代理地址如果带后面地址,将会请求 http://127.0.0.1:1234/aaabbb
# 由于location加了/,proxy_pass会删除/test/,剩下bbb拼接到后面url
proxy_pass http://127.0.0.1:1234/aaa;

# 代理地址如果带后面地址,将会请求 http://127.0.0.1:1234/aaa/bbb
proxy_pass http://127.0.0.1:1234/aaa/;
}

1.3. 支持websocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
map $http_upgrade $connection_upgrade {
default keep-alive; #默认为keep-alive 可以支持 一般http请求
'websocket' upgrade; #如果为websocket 则为 upgrade 可升级的。
}
...
server {
listen 8002;
listen [::]:8002;
server_name localhost;

location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

1.4. 配置tcp代理

  • 需要使用stream模块
  • 下面配置将本地8888的tcp代理到外部8889,可以通过0.0.0.0:8889访问到127.0.0.1:8888
1
2
3
4
5
6
7
8
9
10
11
12
stream {
upstream tcpServer {
server 127.0.0.1:8888;
}

server {
listen 8889;
proxy_pass tcpServer;
proxy_connect_timeout 10s;
proxy_timeout 24h;
}
}

1.5. root和alias

  • root可以配置到httpserverlocation
  • alias只能配置到location
  • root配置访问的是 root + location
  • alias访问的只有 alias
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
location /test {
# 请求http://x.x.x.x/test 访问 /var/www/html/test
# 请求http://x.x.x.x/test/ 访问 /var/www/html/test/
root /var/www/html;

# 请求http://x.x.x.x/test 访问 /var/www/html
# 请求http://x.x.x.x/test/ 访问 /var/www/html/
alias /var/www/html;

# 请求http://x.x.x.x/test 访问 /var/www/html/
# 请求http://x.x.x.x/test/ 访问 /var/www/html//
alias /var/www/html/;
}

location /test/ {
# 请求http://x.x.x.x/test 无法访问
# 请求http://x.x.x.x/test/ 访问 /var/www/html/test/
root /var/www/html;

# 请求http://x.x.x.x/test/aaa 访问 /var/www/htmlaaa
alias /var/www/html;

# 请求http://x.x.x.x/test/aaa 访问 /var/www/html/aaa
alias /var/www/html/;
}

1.6. 反向代理

  • keepalive为每个工作进程中缓存的到上游的空闲保持连接的最大数量,超过会关闭最近最少使用的连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream http_proxy {
server 127.0.0.1:8080;

keepalive 16;
}

server {
...
location / {
proxy_pass http://http_proxy;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}

2. 安全配置

限制不安全请求方法,拒绝接受除POST和GET,HEAD以外的请求方法

1
2
3
if ($request_method !~ ^(GET|POST)$ ) {
return 405;
}

去除nginx指纹信息

1
2
server_tokens off;
proxy_hide_header X-Powered-By; #隐藏powered-by,如果使用反向代理,也可以通过 proxy_hide_header 指令隐藏它

防止溢出,给所有客户端配置buffer容许最大值

1
2
3
4
client_body_buffer_size 1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;

控制iframe,防止iframe注入

1
add_header  X-Frame-Options  SAMEORIGIN; #设置iframe内容必须同源

XSS防护配置

1
2
add_header  X-XSS-Protection  "1; mode=block"; #启用内置XSS filter
add_header Content-Security-Policy "default-src 'self'; img-src 'self' *.alicdn.com; object-src 'none'; script-src 'self' *.alicdn.com; style-src 'self' *.alicdn.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"; #启用内容安全策略,减少XSS攻击

限制仅使用HTTPS访问

1
add_header  Strict-Transport-Security "max-age=31536000; includeSubDomains"; #(HSTS) 告诉浏览器该站点只能通过 HTTPS 访问,如果使用了子域,也建议对任何该站点的子域强制执行此操作。

cookie信息安全

1
2
add_header  Set-Cookie "Path=/; HttpOnly; Secure";
proxy_cookie_path / "/; httponly; secure; SameSite=None"; #反向代理时要设置参数解决Cookie跨域丢失

跨域请求设置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 通过配置Access-Control-Allow-Origin参数可以指定哪些域可以访问你的服务器,这个值要么是* 要么是带协议端口号确定的值, *.xx.com都是错误的值。
*.xx.com
set $cors "";
if ($http_origin ~* (.*\.atpool.com)) {
set $cors $http_origin;
}
add_header Access-Control-Allow-Origin $cors;
add_header Access-Control-Allow-Methods "GET,POST,OPTIONS,DELETE,PUT";
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers *;
if ($request_method = "OPTIONS") {
return 204;
}

加密与证书设置

1
2
3
4
5
6
7
8
# 只容许TLS1.2及以上版本,加密算法只容许强加密算法
ssl_certificate cert/xx.com.pem;
ssl_certificate_key cert/xx.com.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# 只启用TLS1.2 以上
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;

禁止nginx服务器目录列表功能

1
autoindex off;

3. 配置详解

3.1. 反向代理配置

  • 配置代理请求缓存,如果为on,nginx会将代理过程中的数据做缓存,缓存到proxy_temp目录下
  • 此配置和配置到http/server/location中
  • 处理的是单个连接中,如果上下游速度不匹配而导致的数据可以进行缓存,加快上游或下游的写包速度,而非缓存用于同样请求的响应
1
2
proxy_request_buffering off; # 关闭代理请求缓存
proxy_buffering off; # 关闭代理响应缓存

3.2. so_keepalive keepalive配置

1
2
3
server {
listen 80 so_keepalive=2m:10s:3;
}
  • 为连接保持的设置,上面配置连接空闲2min后发送keepalive包,间隔10s发一次,3次没有响应连接关闭

3.3. proxy_ssl_protocols 配置代理的ssl版本

  • 作为客户端连接上游时设置的版本信息
1
2
3
4
Syntax:	proxy_ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2] [TLSv1.3];
Default:
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
Context: http, server, location
  • TLSv1.3仅在nginx版本1.23.4之后作为默认值

3.4. 日志

  • 访问日志access.log中的时间是响应的时间,不是请求的时间

二、命令详解

1. 常用命令

2. 配置检查

1
2
3
4
=> nginx -t
2022/09/01 00:49:22 [warn] 4992#4992: could not build optimal types_hash, you should increase either types_hash_max_size: 1024 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size
2022/09/01 00:49:22 [emerg] 4992#4992: "types_hash_max_size" directive is not allowed here in /etc/nginx/nginx.conf:144
nginx: configuration file /etc/nginx/nginx.conf test failed

三、openrestry

OpenResty 的核心和精髓:cosocket

1. 和nginx的区别

OpenResty(也称为 ngx_openresty)是一个全功能的 Web 应用服务器。它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。

通过揉和众多设计良好的 Nginx 模块,OpenResty 有效地把 Nginx 服务器转变为一个强大的 Web 应用服务器,基于它开发人员可以使用 Lua 编程语言对 Nginx 核心以及现有的各种 Nginx C 模块进行脚本编程,构建出可以处理一万以上并发请求的极端高性能的 Web 应用。

2. 安装和运行

2.1. docker

  • 官方镜像如下
1
docker pull openresty/openresty

1) 运行

  • 使用命令运行
1
docker run -p 8080:80 -p 443:443 -d -i openresty/openresty

2) 开发

  • 先拷贝对应的目录到本地
1
2
3
4
5
6
7
8
9
10
11
mkdir -p ./usr/local/openresty/nginx
# openresty配置
docker cp openresty:/usr/local/openresty/nginx/conf ./usr/local/openresty/nginx
# 静态界面
docker cp openresty:/usr/local/openresty/nginx/html ./usr/local/openresty/nginx
# logs
docker cp openresty:/usr/local/openresty/nginx/logs ./usr/local/openresty/nginx

mkdir -p ./etc
# nginx配置
docker cp openresty:/etc/nginx ./etc
  • 编辑一个openresty-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'
services:
openresty:
container_name: openresty
image: 'openresty/openresty:latest'
network_mode: 'host'
volumes:
- ./usr/local/openresty/nginx/conf:/usr/local/openresty/nginx/conf:rw
- ./usr/local/openresty/nginx/html:/usr/local/openresty/nginx/html:rw
- ./usr/local/openresty/nginx/logs:/usr/local/openresty/nginx/logs:rw
- ./etc/nginx:/etc/nginx:rw
tty: true
privileged: false
restart: always # 开机启动
  • 运行
1
docker-compose -f openresty-compose.yml up -d

3) lua支持

  • 编辑/usr/local/openresty/nginx/conf/nginx.conf
1
2
3
4
5
6
7
8
9
10
11
http {
# 在http块下添加下面的代码
lua_socket_read_timeout 5m;
lua_code_cache on;
# /usr/local/openresty/nginx/conf/lua/?.lua 自定义lua文件,使用require时,字符串会替换问号,所以只能有一个问号
# /usr/local/openresty/lualib/?.lua 系统的lua文件
lua_package_path '/usr/local/openresty/nginx/conf/lua/?.lua;/usr/local/openresty/lualib/?.lua;';
# 系统的lua的c库
lua_package_cpath '/usr/local/openresty/luajit/lib/?.so;/usr/local/openresty/luajit/lib/?.so;';
...
}
  • 新增/usr/local/openresty/nginx/conf/lua/test.lua写入如下内容
1
2
3
4
5
6
7
local _M = {}

function _M:run()
ngx.say("<p>hello, world</p>")
end

return _M
  • 编辑/etc/nginx/conf.d/default.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name localhost;

#charset koi8-r;
location = /test {
content_by_lua_block {
local test = require "test"
test.run()
}
}
...
}
  • 访问http://localhost/test就能看到hello, world

3. 协程调度

3.1. ngx.thread

1) ngx.thread.spawn

  • 看似是起了一个线程,实际上这个ngx.thread.spawn是起了一个协程,这个协程是依托于主线程的,在主线程上同一时间只有一个协程在运行
  • 使用cosocket或显式调用coroutine.yield()才可以主动释放cpu,否则到哪个协程就会在这个协程一直跑下去

四、好用的第三方module

https://github.com/alibaba/tengine.git

踩坑记

1. nginx启动报[emerg] bind() to 0.0.0.0:xxx failed (13: Permission denied)

  • 出现问题的设备是centos7,默认开启了selinux,对http监听的端口有范围限制
  • 如果是小于1024是需要使用root运行,大于1024参照下面方案

方案1 关闭selinux

1
2
3
4
# 查看selinux当前状态
getenforce
# 设置selinux关闭
setenforce 0

方案2 使用semanage添加端口

  • 安装semanage自己百度
1
2
3
4
5
# 查看支持的http端口列表
=> semanage port -l | grep http_port_t
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
# 添加端口
=> semanage port -a -t http_port_t -p tcp 8090

2. nginx报[warn] 4992#4992: could not build optimal types_hash, you should increase either types_hash_max_size: 1024 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size

  • 需要在http模块内部添加配置将types_hash_max_size调大
1
2
3
4
http {
types_hash_max_size 4096;
...
}

3. nginx反向代理后time_wait过多存在问题

  • 在upstream中添加keep_alive选项,可以在一个tcp连接发送多个http请求,防止tcp连接过多而time_wait状态太多造成端口不可用的问题
  • 需要同时设置http版本为1.1,connection为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream http_proxy {
server 127.0.0.1:8080;

keepalive 16;
}

server {
...
location / {
proxy_pass http://http_proxy;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}