Nginx学习笔记

警告
本文最后更新于 2023-02-28,文中内容可能已过时,请谨慎使用。
路径 说明
/var/log/nginx 存放nginx运行日志
/etc/nginx nginx配置所在文件夹
/etc/nginx/nginx.conf nginx主配置文件
/etc/nginx/conf.d 存放所有server块配置文件

一个Nginx配置文件通常包含3个模块:

  • 全局块:比如工作进程数,定义日志路径;
  • Events块:设置处理轮询事件模型,每个工作进程最大连接数及http层的keep-alive超时时间;
  • http块:路由匹配、静态文件服务器、反向代理、负载均衡等。

其中http块又可以进一步分成3块,http全局块里的配置对所有站点生效,server块配置仅对单个站点生效,而location块的配置仅对单个页面或url生效。

# 全局块
...              
# events块
events {         
   ...
}
# http块
http      
{
    # http全局块
    ...   
    # 虚拟主机server块
    server        
    { 
        # server全局块
        ...       
        # location块
        location [PATTERN]   
        {
            ...
        }
        location [PATTERN] 
        {
            ...
        }
    }
    server
    {
      ...
    }
    # http全局块
    ...     
}

nginx包括一个主配置文件nginx.conf和多个server块配置(存放于/etc/nginx/conf.d/)

查看去掉注释后的nginx.conf文件,grep去除了带 # 的行和 ^$ (即空白行)

cat /etc/nginx/nginx.conf | grep -vE "#|^$"

nginx主配置文件:nginx.conf

user www-data; # 启动nginx的用户
worker_processes auto; # 工作进程数,建议配置为CPU核心数
pid /run/nginx.pid; # 设置记录主进程ID的文件
include /etc/nginx/modules-enabled/*.conf; 

events {
	worker_connections 768; # 定义工作进程最大连接数
}

http {

    ##
    # Basic Settings
    ##
	
    sendfile on; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 65; # 设置超时时间(秒)
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types; #文件扩展名与文件类型映射表
    default_type application/octet-stream;  #默认文件类型

    ##
    # 设置ssl
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

  	# 请求日志和错误日志

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;   #开启gzip压缩输出

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

注意到倒数第二行和倒数第三行的include,它表示将/etc/nginx/conf.d/目录下以.conf结尾的文件和目录/etc/nginx/site-enabled/下的所有文件直接包含进来。

你可以理解为将文件的内容直接复制到这里(即/etc/nginx/nginx.conf中)。

为了方便维护我们server相关配置,不会让某一个配置文件过于庞大。通常是将所有的虚拟主机配置文件(也就是server配置块的内容)存放在 /etc/nginx/conf.d/ 或者 /etc/nginx/sites-enabled/ 目录中,在主配置文件中已经默认声明了会读取这两个文件夹下所有 *.conf 文件。

根据使用习惯,我一般会在/etc/nginx/conf.d/建立配置文件来配置server块的内容

首先将默认的配置文件拷贝到/etc/nginx/conf.d/

cp /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf 

查看默认的server块配置:

cat /etc/nginx/conf.d/default.conf  | grep -vE "#|^$"

默认配置如下:

server {    
    # 侦听 80 端口,分别配置了 IPv4 和 IPv6    
    listen 80 default_server;    
    listen [::]:80 default_server ipv6only=on;     
    # 定义服务器的默认网站根目录位置    
    root /usr/share/nginx/html;     
    # 定义主页的文件名    
    index index.html index.htm;     
    # 定义虚拟服务器的名称    
    server_name localhost;     
    # location 块    
    location / {        
        try_files $uri $uri/ =404;    
    } 
}

1.首先在文件nginx.confhttp { ... }块中加#注释掉include /etc/nginx/sites-enabled/*;

2.复制nginx默认的server块配置到/etc/nginx/conf.d文件夹中

cp /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf 

3.新建配置文件,监听80之外的端口

新建一个配置文件myweb.conf,新建文件夹/var/myweb,里面创建一个文件index.html

根目录为/var/myweb,监听端口为6543,根目录和端口号可以自定义

server {
    listen 6543 default_server;
    listen [::]:6543 default_server;
    root /var/myweb;
    index index.html index.htm;
    server_name _;
    location / {
        try_files $uri $uri/ =404;
    }
}

4.重载nginx

nginx -t # 检查nginx配置文件是否正确,ok表示配置文件语法正确
nginx -s reload # 重载nginx
匹配符 匹配规则 优先级
= 精确匹配 1
^~ 前缀匹配,以某个字符串开头 2
~ 区分大小写的正则匹配 3
~* 不区分大小写的正则匹配 4
!~ 区分大小写的不匹配正则 5
!~* 不区分大小写的不匹配正则 6
/uri 返回最长匹配的location,与location所在位置顺序无关 7
/ 通用匹配,任何请求都会匹配到 8

规则:先精确匹配,没有则查找带有 ^~的前缀匹配,没有则进行正则匹配,最后才返回前缀匹配的结果(如果有的话)

如果优先级相同的话,按照匹配程序选择,匹配程度越高越优先;如果匹配程度相同,则按照nginx中的配置文件中的先后顺序进行选择。

/images/all/forward-proxyservers-vs-reverse-proxyservers.png
正向代理和反向代理的区别

正向代理(forward proxy):是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

客户端必须设置正向代理服务器,当然前提是要知道正向代理服务器的IP地址,还有代理程序的端口。

正向代理的用途:

  1. 访问原来无法访问的资源,如某些404网站
  2. 可以做缓存,加速访问资源
  3. 对客户端访问授权,上网进行认证
  4. 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息

反向代理(reverse proxy):实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

反向代理的用途:

  1. 保证内网的安全,可以使用反向代理提供WAF功能,阻止web攻击
  2. 负载均衡,通过反向代理服务器来优化网站的负载

后端服务的接口设置为8080,nginx监听的端口为80

提示
location后面的路径最好以/结尾,不然会有奇奇怪怪的错误!
location / {
	proxy_pass http://localhost:8080;
}
  • 如果proxy-pass的地址只配置到端口,不包含/或其他路径,那么location将被追加到转发地址中

    location /path/ {
        proxy_pass http://localhost:8080;
    }
    

    例如访问 http://localhost/path/page.html 将被代理到 http://localhost:8080/path/page.html

  • 如果proxy-pass的地址包括/或其他路径,那么location不会追加到转发地址中

    location /path/ {
        proxy_pass http://localhost:8080/zh-cn/;
    }
    

    例如访问 http://localhost/path/page.html 将被代理到 http://localhost:8080/zh-cn/page.html

    更多例子请访问这个链接 !!!

用户可以重新定义或追加header信息传递给后端服务器。可以包含文本、变量及其组合。默认情况下,仅重定义两个字段:

proxy_set_header Host $proxy_host;
proxy_set_header Connection close;

由于使用反向代理之后,后端服务无法获取用户的真实IP,所以,一般反向代理都会设置以下header信息。

location /path/ {
    proxy_set_header Host $host; # 保留代理之前的host
    proxy_set_header X-Real-IP $remote_addr; # 保留代理之前的真实客户端ip
    proxy_set_header REMOTE-HOST $remote_addr; 
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 在多级代理的情况下,记录每次代理之前的客户端真实ip

    proxy_pass http://localhost:8080;
}

使用 upstream定义一组服务 。

upstream 位于 http上下文中,与server 并列,不要放在server中

  1. 轮询

    轮询可用的服务器进行转发

    upstream index {
        server 127.0.0.1:6002;
        server 127.0.0.1:6001;
    }
    server {
        listen 6500 default_server;
        listen [::]:6500 default_server;
        root /home/zfp/myweb;
        index index.html index.htm;
        server_name _;
        location / {
            try_files $uri $uri/ =404;
            proxy_pass http://index;
        }
    }
    
  2. 最小连接数

    将下一个请求分配给活动连接数最少的服务器(较为空闲的服务器)

    upstream index {
        least_conn;
        server 127.0.0.1:6002;
        server 127.0.0.1:6001;
    }
    

    请注意,使用轮循机制或最少连接的负载平衡,每个客户端的请求都可能分发到不同的服务器。不能保证同一客户端将始终定向到同一服务器。

  3. ip-hash

    upstream index {
        ip_hash;
        server 127.0.0.1:6002;
        server 127.0.0.1:6001;
    }
    

    此方法可确保来自同一客户端的请求将始终定向到同一服务器,除非此服务器不可用。

  4. 随机

    每个请求都将传递到随机选择的服务器。

    two是可选参数,nginx在考虑服务器权重的情况下随机选择两台服务器,然后使用指定的方法选择其中一台,默认为选择连接数最少(least_conn)的服务器。

    upstream index {
        random two least_conn;
        server 127.0.0.1:6002;
        server 127.0.0.1:6001;
    }
    
  5. 权重

    每接收到4个请求,3个转发到http://localhost:6002,1个转发到http://localhost:6001

    upstream index {
        server 127.0.0.1:6002 weight=3;
        server 127.0.0.1:6001 weight=1;
    }
    

在反向代理中,如果后端服务器在某个周期内响应失败次数超过规定值,nginx会将此服务器标记为失败,并在之后的一个周期不再将请求发送给这台服务器。

通过fail_timeout来设置检查周期,默认为10秒。

通过max_fails来设置检查失败次数,默认为1次。

在以下示例中,如果nginx无法向服务器发送请求或在30秒内请求失败次数超过3次,则会将服务器标记为不可用30秒。

upstream index {
    server 127.0.0.1:6002;
    server 127.0.0.1:6001 max_fails=3 fail_timeout=30s;
}

使用openssl生成证书

openssl genrsa -des3 -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

nginx配置

如果证书设置了密码需要将密码保存到文件中,并通过下面的语句访问

ssl_password_file   /etc/nginx/conf.d/server.pass;

详细配置如下:

upstream index{
    server 127.0.0.1:6002 weight=3;
    server 127.0.0.1:6001 weight=1;
}
server {
    # 启用 https
    listen 6500 default_server ssl http2;
    root /home/zfp/myweb;
    index index.html index.htm;
    # 后面跟上证书的key和crt文件
    ssl_certificate     /etc/nginx/conf.d/server.crt;
    ssl_certificate_key /etc/nginx/conf.d/server.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    # 证书如果设置了密码需要添加下面这一行
    ssl_password_file   /etc/nginx/conf.d/server.pass; 
    # 设置ssl会话缓存
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    server_name _;
    location / {
        try_files $uri $uri/ =404;
        proxy_pass http://index;
    }
    location /nginx_status {
        stub_status on;
    }
}

注意: stream块和http块处于同一级,所以设置tcp反向代理需要在nginx.conf中操作

nginx.conf中添加mysql的反向代理

#HTTP代理
http {
    ...
}

#TCP代理
stream {
    server {
        listen 13306;
        proxy_pass localhost:3306;
    }
}
/images/all/image-20230226231407730.png
数据库反向代理成功

负载均衡

stream {

    upstream backend-mysql {
        server localhost:3306;
        server localhost:3307;
        keepalive 8;
    }

    server {
        listen 13306;
        proxy_pass backend-mysql;
    }
}

使用keepalive定义连接池里空闲连接的数量。

keepalive_timeout 默认60s。如果连接池里的连接空闲时间超过这个值,则连接关闭。

服务端停止处理并将状态码status code返回给客户端

  • return code URL
  • return code text
  • return code
  • return URL

status code类型

  • 2xx 成功

  • 3xx 表示重定向

    • 301 永久重定向
    • 302 临时重定向
  • 4xx 请求地址出错

    • 403 拒绝请求
    • 404 请求找不到
  • 5xx 服务器内部错误

下面是一个临时将请求链接重定向到其他端口的例子

server {
    listen 6004 default_server;
    listen [::]:6004 default_server;
    root /home/zfp/myweb;
    index index.html index.htm;
    server_name _;
    # 临时重定向
    # 这里需要写真实ip,不能用localhost,重定向不是反向代理
    # 将http://192.168.153.131:6004/重定向到https://192.168.153.131:6500/
    return 302 https://192.168.153.131:6500;
    location / {
        try_files $uri $uri/ =404;
    }
    location /nginx_status {
        stub_status on;
    }
}

域名变更,将老域名的请求永久重定向到新域名

server {
    listen 80;
    listen 443 ssl;
    server_name www.old-name.com old-name.com;
    return 301 $scheme://www.new-name.com;
}

请求的地址前加上www

server {
    listen 80;
    listen 443 ssl;
    server_name domain.com;
    return 301 $scheme://www.domain.com$request_uri;
}

如果指定的正则表达式与请求URI匹配,则URI将按照字符串中的指定进行更改。指令按其在配置文件中出现的先后顺序执行。

server {
    # ...
    rewrite ^(/download/.*)/media/(\w+)\.?.*$ $1/mp3/$2.mp3 last;
    rewrite ^(/download/.*)/audio/(\w+)\.?.*$ $1/mp3/$2.ra  last;
    return  403;
    # ...
}

上面是使用该指令的示例 nginx重写规则。它匹配以字符 /download 开头的 URL,然后在路径后面的某个位置包含 /media/ 或 /audio/ 目录。它会将目录替换为 /mp3/,并添加相应的文件扩展名 .mp3.ra

例如,/download/cdn-west/media/file1 变成了 /download/cdn-west/mp3/file1.mp3

使用重写规则建议打开重写日志

rewrite_log on;

规则:

  • last:如果当前规则不匹配,停止处理后续rewrite规则,使用重写后的路径,重新搜索location及其块内指令

  • break:如果当前规则不匹配,停止处理后续rewrite规则,执行{}块内其他指令

/home/AdminLTE-3.2.0/pages下创建一个1.txt,里面内容是this is a file

假设请求地址为http://192.168.153.131:8000/old/1.txt

例1:不适用last和break

server {

    listen 8000;
    server_name _;

    rewrite_log on;

    location / {
        rewrite ^/old/(.*) /new/$1; 
        rewrite ^/new/(.*) /pages/$1;
        #根目录
        root /home/AdminLTE-3.2.0;
        #首页
        index index.html index2.html index3.html;
    }

    location  /pages/1.txt {
        return 200 "this is rewrite test!";
    }

}
说明
会依次执行两条rewrite语句,请求变为http://192.168.153.131:8000/pages/1.txt,最后输出this is rewrite test!

例2:使用break

server {

    listen 8000;
    server_name _;

    rewrite_log on;

    location / {
        rewrite ^/old/(.*) /new/$1 break;
        rewrite ^/new/(.*) /pages/$1;
        #根目录
        root /home/AdminLTE-3.2.0;
        #首页
        index index.html index2.html index3.html;
    }

    location  /pages/1.txt {
        return 200 "this is rewrite test!";
    }

}
说明
执行完第一条rewrite语句后地址变为http://192.168.153.131:8000/new/1.txt,阻止执行第二条rewrite语句,以新路径/new/1.txt去匹配location, 先匹配到location /, 有匹配到location里的rewrite ^/new/(.*) /pages/$1规则,重定向到/pages/1.txt匹配到了location /pages/1.txt ,最终返回了this is rewrite test!

例3:使用last

server {

    listen 8000;
    server_name nginx-dev;

    rewrite_log on;

    location / {
        rewrite ^/old/(.*) /new/$1 last;
        rewrite ^/new/(.*) /pages/$1;
        #根目录
        root /home/AdminLTE-3.2.0;
        #首页
        index index.html index2.html index3.html;
    }

    location  /pages/1.txt {
        return 200 "this is rewrite test!";
    }

}
说明
执行完第一条后地址变为http://192.168.153.131:8000/new/1.txt,阻止执行第二条rewrite语句,以新路径/new/1.txt执行location /下剩余指令,没有发现/home/AdminLTE-3.2.0/new/1.txt文件,返回404 not found

1. gzip压缩

默认情况下,nginx仅使用压缩MIME类型是text/html的响应。若要使用其他MIME类型压缩响应,可以使用gzip_types指令并列出其他类型。

gzip on; # 打开gzip压缩
gzip_types text/plain application/xml;  # 添加压缩文件类型
gzip_min_length 1000;  # 指定压缩响应的最小长度,

2. sendfile

sendfile指令通常用于上传和下载文件的请求中。

默认情况下,nginx处理文件传输本身,并在发送之前将文件复制到缓冲区中。启用sendfile指令可消除将数据复制到缓冲区的步骤,直接将一个文件复制到另一个文件。

sendfile指令与tcp_nopush指令一起使用。这使nginx能够在获得数据块后立即在一个数据包中发送HTTP响应标头。

location /download {
  sendfile           on;
  tcp_nopush         on; 
  ...
} 

3. try_files

try_files指令可用于检查指定的文件或目录是否存在;如果不存在,则重定向到指定位置。

如下,如果原始URI对应的文件不存在,nginx将内部重定向到/home/zfp/myweb/1.txt

server {
    listen 6002 default_server;
    listen [::]:6002 default_server;

    root /home/zfp/myweb;
    index index.html index.htm;
    server_name _;
    location / {
        try_files $uri $uri/ /1.txt;
    }
    location /nginx_status {
        stub_status on;
    }
}

最后一个参数也可以为状态码

在下面的示例中,如果指令的所有参数都无法解析为现有文件或目录,则会返回404错误。

try_files $uri $uri/ =404;

4. error_page

为错误指定显示的页面。值可以包含变量。

error_page 404             /404.html; 
error_page 500 502 503 504 /50x.html;
注意
本节主要介绍一些使用nginx的技巧,不介绍具体语法
场景
假设用户原来的请求为http://localhost:6501,现在升级到了https,希望使用原请求能跳转到https://localhost:6501

方案1:

使用错误页将http请求重定向为https请求,这样就解决了原先的http请求无缝切换为https请求

error_page 497 https://$http_host$request_uri;

方案2:

之前是6501端口监听http请求,6500端口监听https请求

现在是6501监听到http请求将强制使用https,不需要用户开放6500端口来处理https请求

在使用TCP(stream)代理转发流量时,可以使用ssl_preread_protocol变量区分SSL/TLS和其他协议。

nginx.conf中添加下列配置:

stream {
    upstream web {
        server 192.168.153.131:6502; # 多使用一个端口来处理http请求
    }

    upstream https {
        server 192.168.153.131:6500;
    }

    log_format basic 'ssl_version: $ssl_preread_protocol | upstream: $upstream';
    access_log /var/log/nginx/nginx-access.log basic ;

    # 根据不同的访问协议转发给不同的上游
    map $ssl_preread_protocol $upstream {
        "" web;
        "TLSv1.3" https;
        default https;
    }

    # HTTPS and HTTP on the same port
    server {
        listen 6501; # 监听6501端口

        proxy_pass $upstream;
        ssl_preread on; # 开启ssl_preread功能
    }
}

监听6501端口,根据不同请求进行转发:

http请求转发到http://192.168.153.131:6502, https请求转发到https://192.168.153.131:6500

修改原来的server配置

server {
    listen       6502;
    server_name  localhost;
    return 301 https://192.168.153.131:6501;
}

server {
        # listen 6501 default_server;
        listen 6500 default_server ssl http2;
        root /home/zfp/myweb;
        index index.html index.htm;
        # error_page 497 https://$http_host$request_uri;
        ssl_certificate     /etc/nginx/conf.d/server.crt;
        ssl_certificate_key /etc/nginx/conf.d/server.key;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ssl_password_file   /etc/nginx/conf.d/server.pass;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_timeout 10m;
        server_name _;
        location / {
                try_files $uri $uri/ =404;
        }
}