# 通信协议

# 关于 TCP

# keepalive

:用于启用 Nginx 到 upstream 的 TCP 长连接。

  • 可用范围:upstream

  • 例:

    keepalive 8;                # 启用长连接,并限制每个 worker 保持的空闲连接数,但并不限制长连接总数
    # keepalive_requests 1000;  # 限制每个长连接可以发送的请求数。如果超过,则关闭 TCP 连接
    # keepalive_time 1h;        # 限制每个长连接的存在时间。如果超过,则在当前 HTTP 请求结束后关闭 TCP 连接
    # keepalive_timeout 60s;    # 限制每个长连接的空闲时间。如果超过,则关闭 TCP 连接
    
  • Nginx 默认启用长连接,但反向代理 upstream 时默认禁用长连接。

    • 此时,upstream 发出响应报文之后,会主动关闭连接,让 Socket 变成 TIME_WAIT 状态。
  • 例:启用 Nginx 到 upstream 的长连接

    upstream backend {
        server 127.0.0.1:80;
        keepalive 8;
    }
    server {
        listen 80;
        location / {
            proxy_pass  http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection '';
        }
    }
    
    • 此时,由 Nginx 主动关闭长连接,会让 Nginx 上的 Socket 变成 TIME_WAIT 状态。增加 keepalive_requests 可以避免频繁关闭长连接。
    • 保留的 TCP 长连接较多时,会占用较多内存。

# send_timeout

:限制发送响应报文时,写操作中断的超时时间。

  • 可用范围:http、server、location
  • 默认值:
    send_timeout 60s;
    
  • 如果超过该值,关闭 TCP 连接。
  • timeout 之类的参数取值过大时容易遭受 DDOS 攻击,取值过小时对网速较慢的客户端不友好。

# sendfile

:用于提高发送本机文件的速度。

  • 可用范围:http、server、location
  • 默认值:
    sendfile    off;
    

# tcp_nodelay

:一有数据就立即发出 TCP 包。

  • 可用范围:http、server、location
  • 默认值:
    tcp_nodelay   on;
    
  • 仅在 TCP 长连接中有效。这样会增加网络 I/O 量,但是能减少通信延迟。

# tcp_nopush

:有数据时先不发送,而是等满足 TCP 包最大段大小(Maximum Segment Size ,MSS)时才发送。

  • 可用范围:http、server、location
  • 默认值:
    tcp_nopush    off;
    
  • 仅在 sendfile 模式中有效。这样能降低网络 I/O 量,不容易阻塞网络。
  • 如果同时启用 tcp_nodelay ,tcp_nopush ,则最后一个 TCP 包采用 tcp_nodelay ,其它 TCP 包采用 tcp_nopush 。

# 关于 HTTP

# gzip

:以 gzip 格式压缩响应报文 body 。

  • 可用范围:http、server、location
  • 例:
    gzip on;                  # 是否启用 gzip ,默认为 off
    # gzip_buffers 16 8k;     # 设置 gzip 缓冲区的数量、大小。默认大小等于一个内存页
    gzip_comp_level 1;        # 压缩率,取值为 1~9 。默认为 1 ,压缩率最低,CPU 负载也最小
    gzip_http_version 1.1;    # 只压缩指定协议以上的响应报文,避免客户端版本过老二不能解析 gzip 报文。默认为 HTTP/1.1
    gzip_min_length 1k;       # 限制只压缩指定大小以上的响应报文,默认为 20
    gzip_proxied any;         # 压缩 proxy_pass 哪些类型的响应报文。默认为 off
    gzip_types text/plain text/javascript text/css text/xml application/json application/javascript;  # 默认只压缩 text/html 类型的响应报文,这里增加其它类型
    gzip_vary on;             # 是否在响应头中加入 Vary: Accept-Encoding ,告诉客户端这是 gzip 报文。默认为 off
    
    • 开启 gzip 压缩能大幅减小响应体积,提高网页加载速度,但是会增加 Nginx 的 CPU 负载。
    • 文本类型的响应容易压缩,而图片、视频不容易压缩,因为本身已经被压缩了。

# add_header

:用于添加响应报文的 header 。

  • 可用范围:http、server、location
  • 语法:
    add_header name value [always];
    
    • 该指令可以多次使用。
    • 默认如果响应报文的状态码属于 200, 201, 206, 301, 302, 303, 307, 308 ,则在头部末尾添加 header 。
      • 如果声明了 always ,则无论状态码是什么,都会添加 header 。
    • 默认如果当前级别没有定义任何 add_header 指令,则继承上一级别的 add_header 指令。
  • 例:
    location /api {
        add_header Content-Type 'application/json; charset=utf-8';
        return  200 '{"code": 200, "msg": "请求成功"}';
    }
    
  • 例:允许 CORS 请求
    add_header Access-Control-Allow-Origin * always;
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'Content-Type,User-Agent,X-Requested-With';
    if ($request_method = 'OPTIONS') {
      add_header Access-Control-Max-Age 1728000;
      return 204;
    }
    

# charset

:用于在响应报文的 Content-Type 中加入 charset 。

  • 可用范围:http、server、location
  • 默认值:
    charset off;
    
  • 例:
    charset utf-8;
    

# types

:用于定义 MIME 类型与文件扩展名(不区分大小写)的映射关系。

  • 可用范围:http、server、location
  • 默认值:
    types {
        text/html   html;
        image/gif   gif;
        image/jpeg  jpg;
    }
    
  • Nginx 会根据该映射关系设置响应报文头部中 Content-Type 的值。比如发送一个扩展名为 html 的文件时,就设置 Content-Type: text/html
  • /etc/nginx/mime.types 文件中通过 types 指令记录了很多 MIME 类型与文件扩展名的映射关系。

# default_type

:设置响应报文头部中 Content-Type 的默认值。

  • 可用范围:http、server、location
  • 默认值:
    default_type    text/plain;
    

# client_*

:关于接收 HTTP 请求报文的配置参数。

  • 可用范围:http、server
  • 默认值:
    client_header_buffer_size 1k;   # 读取请求报文头部的缓冲区大小
    client_body_buffer_size   4k;   # 读取请求报文 body 的内存缓冲区大小,默认为一个内存页大小。超出内存缓冲区的部分会写入 client_body_temp_path 目录下的临时文件中,并记录 warn 日志: a client request body is buffered to a temporary file
    client_body_temp_path     /tmp/nginx/client_temp 1 2;
    
    client_header_timeout   60s;    # 读取请求报文头部的超时时间。如果超时,则返回响应报文:408 Request Time-out
    client_body_timeout     60s;    # 限制读取请求报文 body 时,读操作中断的超时时间。如果超时,则返回响应报文:408 Request Time-out
    client_max_body_size    1m;     # 限制请求报文 body 的最大体积。如果超过限制,则返回响应报文:413 Request Entity Too Large 。设置成 0 则取消限制
    

# 关于缓存

# expires

:设置响应报文的缓存过期时间。

  • 可用范围:http、server、location
  • 启用 expires 指令时,如果响应报文的状态码属于 200, 201, 204, 206, 301, 302, 303, 304, 307, 308 ,则会添加响应头 Expires、Cache-Control 。
  • 例:
    expires   off;              # 默认值,禁用 expires 指令
    expires   epoch;            # 不使用缓存。这会设置 Expires: "Thu, 01 Jan 1970 00:00:01 GMT"; Cache-Control: "no-cache"
    expires   max;              # 最长缓存。这会设置 Expires: "Thu, 31 Dec 2037 23:55:55 GMT"; Cache-Control: "max-age=315360000"
    
    expires   10m;              # 过期时间为 10m 之后
    expires   modified  10m;    # 过期时间为文件的最后修改时间 + 10m
    
    location / {
        root /www/;
    
        # HTML 文件通常会变化,因此不使用缓存
        if ( $uri ~* \.html$ ){
            expires epoch;
        }
        # 设置静态文件的缓存
        if ( $uri ~* \.(js|css|jpe?g|png|gif|ico|svg)$ ){
            expires 30d;
        }
    }
    

# etag

:是否添加响应头 etag 。

  • 可用范围:http、server、location
  • 例:
    etag  on;     # 默认值
    etag  off;
    
  • 因为计算文件哈希值的开销较高,Nginx 是根据文件的 last_modified 时间和 content_length 长度生成 etag 。

# if_modified_since

:设置处理请求头 If-Modified-Since 的方式。

  • 可用范围:http、server、location
  • 例:
    if_modified_since  exact;   # 默认值,要求文件的 last_modified 时间等于 If-Modified-Since ,才命中缓存,返回 304 报文
    if_modified_since  before;  # 只要文件的 last_modified 时间小于等于 If-Modified-Since ,就返回 304 报文
    if_modified_since  off;     # 忽略请求头 If-Modified-Since
    

# 关于 SSL

# ssl_*

:关于 HTTPS 协议的配置参数。

  • 可用范围:http、server
  • 例:
    server {
        listen    443  ssl;                     # 监听时采用 ssl 协议
        server_name localhost;
    
        ssl_certificate /ssl/cert.crt;          # SSL 证书
        ssl_certificate_key /ssl/cert.key;      # SSL 私钥
    
        ssl_protocols TLSv1.2 TLSv1.3;          # 设置可用的 SSL 协议版本,默认为 TLSv1 TLSv1.1 TLSv1.2
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';  # 设置 SSL 加密算法
        ssl_prefer_server_ciphers on;           # 在 SSL 握手时优先采用服务器的加密算法
    
        # 在一段时间内复用一个 SSL 会话,以节省 SSL 握手的时间
        ssl_session_cache shared:SSL:10m;       # 设置 SSL 缓存的大小,10M 大概可以存储 40000 个 SSL 会话
        ssl_session_timeout 10m;                # 设置缓存的失效时间
    }
    
  • 如果 Nginx 的某个 SSL 端口被多个 server 监听,则会采用默认 server 的 SSL 证书进行 SSL 握手。
    • 因为在 SSL 握手时,还没有开始 HTTP 通信,所以 Nginx 不能根据 server_name 进行路由。
  • HTTP、HTTPS 协议分别默认采用 TCP 80、443 端口,互不兼容。
    [root@CentOS ~]# curl  https://127.0.0.1:80/    # 不支持向 HTTP 端口发送 HTTPS 请求
    curl: (35) SSL received a record that exceeded the maximum permissible length.
    [root@CentOS ~]# http://127.0.0.1:443/          # 不支持向 HTTPS 端口发送 HTTP 请求
    <html>
    <head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
    <body bgcolor="white">
    <center><h1>400 Bad Request</h1></center>
    
    • 当用户访问 HTTPS 网站时,浏览器通常会禁止发出 HTTP 请求,比如 chrome 浏览器会报错:blocked:mixed-content

# http2

  • 例:

    server {
        listen    443  ssl http2;   # 监听时采用 ssl 和 http2 协议
    }
    
    • 一般的浏览器只支持在 HTTPS 端口启用 HTTP/2 ,因此需要服务器同时启用 ssl 和 http2 。
  • Nginx 端口启用了 http2 协议时,依然允许接收 http1 请求。可加入以下配置,拒绝 http1 请求:

    if ($server_protocol ~* "HTTP/1*") {
        return 444;
    }