目录

📌本文学习自:https://www.yuque.com/wukong-zorrm/cql6cz/uoz0cq,并根据自己的实践重新整理

安装

服务器环境准备

简单起见,这里我使用Docker容器,容器里面包含一个用于学习的Ubuntu Linux环境,已经预安装了一下开发工具包,如:nodejs、 openssh-server(提供ssh和scp)、vim、wget等,下面是Dockerfile:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
FROM ubuntu:latest

# 避免交互式安装
ENV DEBIAN_FRONTEND=noninteractive

# 更新并安装常用工具
RUN apt-get update && \
apt-get install -y \
vim \
openssh-server \
curl \
wget \
lsof \
tree \
git \
net-tools \
iputils-ping \
sudo \
less \
man-db \
locales && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# 安装Node.js 18 LTS
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get install -y nodejs

# 验证安装
RUN node -v && \
npm -v


# SSH 配置
RUN mkdir /var/run/sshd
# 设置 root 密码为 root
RUN echo 'root:root' | chpasswd

# 允许 root 通过 SSH 登录(仅用于学习环境)
RUN sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# 开放 22 端口
EXPOSE 22

# 默认启动 sshd
CMD ["/usr/sbin/sshd", "-D"]

构建容器镜像:

1
2
3
4
5
6
7
#在Dockerfile同级目录下执行
#安装过程可能需要比较长的时间,主要看你的网络
docker build -t my-linux-env .

docker run -d -p 2222:22 --name my-linux-env my-linux-env


远程连接docker内的linux服务器:

1
ssh root@localhost -p 2222

SSH连接成功

如果我们不想每次连接时都输入密码,可以配置一下ssh免密,直接使用ssh-copy-id命令就可以:

1
2
3
4
ssh-copy-id -p 2222 root@localhost

#我们也可以指定你的公钥地址
ssh-copy-id -i ~/.ssh/mykey.pub -p 2222 root@localhost

SSH免密设置

如果你这一步失败了,那可能是你还没有在本地生成自己的密钥。

安装nginx

我们目前使用的是ubuntu的linux发行版,它默认的包管理器是apt,所以下面使用apt来安装nginx:

1
2
apt update
apt install nginx -y

查看nginx版本

1
nginx -v

卸载

1
apt remove nginx

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#启动
nginx

#也可以指定配置文件启动
nginx -c /path/to/nginx.conf

#优雅停止
nginx -s stop

#热加载 当配置文件更新或者静态资源更新的时候要进行重新加载才能生效
nginx -s reload

#测试配置文件是否正确
nginx -t

查看帮助:

Nginx帮助命令

如果需要安装指定版本nginx,推荐使用源码编译的方式。

vscode远程连接服务器

由于在服务器上编辑配置文件非常麻烦,我们可以用vscode编辑器远程连接我们的服务器,这样的话我们就可以直接在vscode上编辑远程服务器上的文件,当然也可以对服务器执行相关的指令。

打开我们的vscode,搜索并安装Remote-SSH扩展:

VSCode Remote-SSH扩展

安装完成之后,打开我们的Remote-SSH插件并建立ssh连接:

Remote-SSH连接步骤1

Remote-SSH连接步骤2

Remote-SSH连接步骤3

Remote-SSH连接步骤4

密码正确之后我们就可以打开服务器上的文件了:

VSCode打开服务器文件

VSCode文件浏览器

还可以安装Nginx-Configuration扩展来高亮显示和语法提示:

Nginx配置扩展

还可以安装Nginx Config Formatter插件来处理配置文件的格式化:

Nginx配置格式化器

📌注意:我们现在在容器中的linux环境,我们要访问里面的nginx服务必须进行端口转发,或者将容器端口进行暴露。
后者并不适合动态暴露端口的场景,因此我们使用端口转发

在vscode中进行远程服务的端口转发非常方便,例如我们现在要转发远程服务的80端口到本地:

VSCode端口转发

现在我们可以使用后面的本地端口进行访问:

Nginx默认页面

nginx.conf基本介绍

title
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
user www-data; # 指定 Nginx 运行的用户
worker_processes auto; # 自动设置工作进程数,通常与 CPU 核心数一致
pid /run/nginx.pid; # 指定 PID 文件路径
error_log /var/log/nginx/error.log; # 错误日志文件路径
include /etc/nginx/modules-enabled/*.conf; # 引入已启用模块的配置文件

events {
worker_connections 768; # 每个 worker 允许的最大连接数
# multi_accept on; # 是否允许 worker 一次接受多个新连接
}

http {

##
# 基本设置
##

sendfile on; # 启用高效文件传输
tcp_nopush on; # 优化 TCP 包发送,减少网络延迟
types_hash_max_size 2048; # MIME 类型哈希表最大值
# server_tokens off; # 隐藏 Nginx 版本信息

# server_names_hash_bucket_size 64; # 服务器名称哈希桶大小
# server_name_in_redirect off; # 禁止重定向时使用服务器名

include /etc/nginx/mime.types; # 引入 MIME 类型配置
default_type application/octet-stream; # 默认 MIME 类型

##
# SSL 设置
##

ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # 支持的 TLS 协议版本,禁用 SSLv3
ssl_prefer_server_ciphers on; # 优先使用服务器端加密套件

##
# 日志设置
##

access_log /var/log/nginx/access.log; # 访问日志文件路径

##
# Gzip 压缩设置
##

# 启用 gzip 压缩,提高传输效率
gzip on;

# gzip_vary on; # 针对代理服务器的 gzip 设置
# gzip_proxied any; # 针对代理的 gzip 设置
# gzip_comp_level 6; # gzip 压缩级别
# gzip_buffers 16 8k; # gzip 缓冲区设置
# gzip_http_version 1.1; # gzip 适用的 HTTP 版本
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # 需要压缩的类型

include /etc/nginx/conf.d/*.conf; # 引入其他配置文件,这可以让你更灵活地管理配置
include /etc/nginx/sites-enabled/*;
}


#mail {
#mail {
# 邮件代理相关配置(默认注释,通常不启用)
# 详细配置可参考官方文档
#}


#tcp服务配置
#stream {

#}

部署静态网页

用nginx配置一个静态的AdminLTE后台管理系统。

AdminLTE官方网址:https://adminlte.io/
源码下载:https://codeload.github.com/ColorlibHQ/AdminLTE/tar.gz/refs/tags/v3.2.0

1. 下载源码

1
2
3
4
5
6
7
8
9
#静态资源通常放在www下
mkdir /root/www
cd /root/www
wget https://codeload.github.com/ColorlibHQ/AdminLTE/tar.gz/refs/tags/v3.2.0

#解压
tar -zxvf v3.2.0

#然后就得到了 /root/www/AdminLTE-3.2.0这个静态资源目录了

2. 新建配置

在nginx.conf中我们可以看到:

1
include /etc/nginx/conf.d/*.conf;

这表示我们可以将配置文件拆分,放在/etc/nginx/conf.d/下面,以起到配置隔离的作用,避免一个配置文件变得难以维护。

我们再/etc/nginx/conf.d/下创建8000.conf文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
server{

listen 8000;
server_name localhost;

location / {
root /root/www/AdminLTE-3.2.0;
index index.html index2.html index3.html;
}

}

配置说明:

  • server

    虚拟主机server通过listen和server_name进行区分,可以有多个server配置,但是listen + server_name 不能重复。

  • listen

    监听可以配置成IP端口IP+端口

    • listen 127.0.0.1:8000;
    • listen 127.0.0.1;(端口不写,默认80)
    • listen 8000;
    • listen *:8000;
    • listen localhost:8000;
  • server_name

    server_name主要用于区分,可以随便起。

    也可以使用变量 $hostname 配置成主机名。

    或者配置成域名: example.org www.example.org *.example.org

    如果多个server的端口重复,那么根据域名或者主机名去匹配 server_name 进行选择。

    例如下面的例子中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # curl http://localhost:80 会访问这个 
    server {
    listen 80;
    server_name localhost;

    #access_log /var/log/nginx/host.access.log main;

    location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    }

    # curl http://nginx-dev:80 会访问这个
    server{
    listen 80;
    server_name nginx-dev;#主机名

    location / {
    root /root/www/AdminLTE-3.2.0;
    index index.html index2.html index3.html;
    }

    }
  • location

    / 表示请求指向 root 目录

    location 总是从/目录开始匹配,如果有子目录,例如/css,他会指向/static/css

    例如:

    1
    2
    3
    location /css {
    root /static;
    }

3. 热加载使配置生效

1
nginx -s reload

4. 端口转发

8000端口转发

浏览器访问:localhost:8000:

AdminLTE 403错误

报错了~ 我们看看错误日志咋回事:

tail -fn400 /var/log/nginx/error.log

Nginx错误日志

这是由于selinux的安全策略引起的。解决方法如下:

  1. 关闭SELINUX
  2. 使用root启动,或者将www目录授权给我们的启动用户

这里我们使用root用户启动吧:

Nginx配置root用户

nginx -s reload 后再次访问:

AdminLTE成功访问

HTTP反向代理

正向代理和反向代理

  • 正向代理

    客户端代理转发请求称为正向代理。例如VPN。

  • 反向代理

    服务器端代理转发请求称为反向代理。例如nginx。

部署后端服务

📌源码地址:https://github.com/penghs520/nodejs-vue-todos

这里我们会部署一个前后端分离的项目来学习nginx的反向代理,这是一个非常简单的todos应用,没有依赖数据库和任何中间件,后端使用nodejs编写,前端使用vue,效果就是这样:

Todos应用演示

你可以直接在服务器中下载它,然后编译部署,具体步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cd /opt/

git clone https://github.com/penghs520/nodejs-vue-todos.git

cd nodejs-vue-todos/backend/

#安装pnpm,用pnpm来管理包,npm可能下载不到依赖
pnpm install pnpm -g

pnpm install
#启动后端服务,端口是8080
pnpm run dev



后端服务运行

配置反向代理

我们希望访问8001端口能够代理到node-todos 应用的8080端口。

在 /etc/nginx/conf.d下新建8001.conf配置文件:

1
2
3
4
5
6
7
8
9
10
11
server {

listen 8001;

server_name todo_app;

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

}

热加载:

1
nginx -s reload

记得端口转发:

8001端口转发

测试:

1
curl http://localhost:8001/api/todos  #返回一个空数组就对了

配置说明

proxy_pass配置说明

  • 如果proxy-pass的地址只配置到端口,不包含/或其他路径,那么location将被追加到转发地址中

    例如:

    1
    2
    3
    location /some/path/ {
    proxy_pass http://localhost:8080/zh-cn/;
    }

    此时访问 http://localhost/some/path/page.html 将被代理到 http://localhost:8080/``some/path``/page.html

  • 如果proxy-pass的地址包括/或其他路径,那么/some/path将会被替换

    例如:

    1
    2
    3
    location /some/path/ {
    proxy_pass http://localhost:8080 /zh-cn/ ;
    }

    此时访问 http://localhost/some/path/page.html 将被代理到 http://localhost:8080/zh-cn/page.html。‎

设置代理请求headers

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

1
2
proxy_set_header Host       $proxy_host;
proxy_set_header Connection close;

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

1
2
3
4
5
6
7
8
9
location /some/path/ {
#nginx的主机地址
proxy_set_header Host $http_host;
#用户端真实的IP,即客户端IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://localhost:8088;
}

常用变量的值:

  • $host:nginx主机IP,例如192.168.56.105
  • $http_host:nginx主机IP和端口,192.168.56.105:8001
  • $proxy_host:localhost:8088,proxy_pass里配置的主机名和端口
  • $remote_addr:用户的真实IP,即客户端IP。

非HTTP代理

如果要将请求传递到非 HTTP 代理服务器,可以使用下列指令:

  • fastcgi_pass 将请求转发到FastCGI服务器(多用于PHP)
  • scgi_pass 将请求转发到SCGI server服务器(多用于PHP)
  • uwsgi_pass 将请求转发到uwsgi服务器(多用于python)
  • memcached_pass 将请求转发到memcached服务器

动静分离

动静分离的好处

后端服务器处理css、js、图片这些静态文件的IO性能不够好,因此,将静态文件交给nginx处理,可以提高系统的访问速度,减少对后端服务器的请求次数,有效的给后端服务器降压。

构建前端制品

1
2
3
4
cd /opt/nodejs-vue-todos/frontend/
pnpm npm install
#构建前端制品
pnpm run build

前端构建

前端dist文件夹

将制品拷贝到www目录

1
mv dist /www/todos

修改8001.config配置文件,使得对静态资源的访问直接通过nginx获取,而其他请求仍然去访问后端服务:

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
26
27
28
29
30
31
server {

listen 8001;

server_name todo_app;

location / {
#nginx的主机地址
proxy_set_header Host $http_host;
#用户端真实的IP,即客户端IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:8080;
}

# =号优先级最高 ,表示精准匹配
location = /index.html {
root /root/www/todos;
}

# 表示普通字符匹配。使用前缀匹配。如果匹配成功,则不再匹配其它 location
# 优先级第二高
location ^~ /assets/ {
root /root/www/todos;
}

# 区分大小写
location ~ \.(css|js|png|jpg|gif|ico) {
root /root/www/todos/assets;
}
}

测试:

动静分离测试

location 修饰符说明

location可以使用修饰符或正则表达式

修饰符:

  • = 等于,严格匹配,匹配优先级最高。
  • ^~ 表示普通字符匹配。使用前缀匹配。如果匹配成功,则不再匹配其它 location。优先级第二高。
  • ~ 区分大小写
  • ~* 不区分大小写

优先级从高到低依次为:

  1. 精确匹配(=
  2. 前缀匹配(^~
  3. 正则匹配(~~*
  4. 不写

例如:

1
2
3
4
5
6
7
location ^~ /images/ {
proxy_pass http://localhost:8080;
}

location ~ \.jpg {
proxy_pass http://localhost:8080;
}

如上所示:

/images/1.jpg代理到 http://localhost:8080/images/1.jpg

/some/path/1.jpg 代理到http://localhost:8080/some/path/1.jpg

缓存区和缓存

缓冲区(buffer)

缓冲一般放在内存中,如果不适合放入内存(比如超过了指定大小),则会将响应写入磁盘临时文件中。
启用缓冲后,nginx先将后端的请求响应(response)放入缓冲区中,等到整个响应完成后,再发给客户端,这对于客户端网络比较慢的时候很有效。

缓冲区工作原理图

如果没有开启缓冲,可能引用客户端到nginx的网速过慢,导致nginx只能以一个较慢的速度将响应传给客户端;进而导致后端server也只能以同样较慢的速度传递响应给nginx,造成一次请求连接耗时过长。

无缓冲区工作原理图

开启代理缓冲后,nginx可以用较快的速度尽可能将响应体读取并缓冲到本地内存或磁盘中,然后同时根据客户端的网络质量以合适的网速将响应传递给客户端。

这样既解决了server端连接过多的问题,也保证了能持续稳定的像客户端传递响应。

缓冲配置:

  • proxy_buffering

    用于启用和禁用缓冲,nginx默认为 on 启用缓冲,若要关闭,设置为 off

  • proxy_buffers

    指令设置每个连接读取响应的缓冲区的大小数量。默认情况下,缓冲区大小等于一个内存页,4K 或 8K,具体取决于操作系统。

  • proxy_buffer_size

    来自后端服务器响应的第一部分存储在单独的缓冲区中,其大小通过 proxy_buffer_size 指令进行设置,此部分通常是相对较小的响应headers,通常将其设置成小于默认值。

如果整个响应不适合存到内存里,则将其中的一部分保存到磁盘上的‎‎临时文件中‎‎。

‎‎proxy_max_temp_file_size‎‎设置临时文件的最大值。

‎‎proxy_temp_file_write_size‎‎设置一次写入临时文件的大小。

缓存

📌主要用于缓存静态资源,但如果已经是静态分离的场景,就没有必要使用缓存了。

启用缓存后,nginx将响应保存在磁盘中,返回给客户端的数据首先从缓存中获取,这样子相同的请求不用每次都发送给后端服务器,减少到后端请求的数量。

缓存工作原理图

启用缓存,需要在http上下文中使用 proxy_cache_path 指令,定义缓存的本地文件目录,名称和大小。缓存区可以被多个server共享,使用proxy_cache 指定使用哪个缓存区。

1
2
3
4
5
6
7
8
9
http {
proxy_cache_path /data/nginx/cache keys_zone= mycache :10m;
server {
proxy_cache mycache ;
location / {
proxy_pass http://localhost:8000;
}
}
}

缓存目录的文件名是 proxy_cache_key 的MD5值。

例如:/data/nginx/cache/``c/29``/b7f54b2df7773722d382f4809d650``29c

proxy_cache_key 默认设置如下:

1
proxy_cache_key $scheme$proxy_host$uri$is_args$args;

也可以自定义缓存的键,例如:

1
proxy_cache_key "$host$request_uri$cookie_user";

缓存不应该设置的太敏感,可以使用proxy_cache_min_uses设置相同的key的请求,访问次数超过指定数量才会被缓存。

1
proxy_cache_min_uses 5;

默认情况下,响应无限期地保留在缓存中。仅当缓存超过最大配置大小时,按照时间删除最旧的数据。

示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
proxy_cache_path /var/cache/nginx/data keys_zone= mycache :10m;

server {

listen 8001;
server_name ruoyi.localhost;

location / {
#设置buffer
proxy_buffers 16 4k;
proxy_buffer_size 2k;
proxy_pass http://localhost:8088;
}

location ~ \.(js|css|png|jpg|gif|ico) {
#设置cache
proxy_cache mycache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

proxy_pass http://localhost:8088;
}

location = /html/ie.html {

proxy_cache mycache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

proxy_pass http://localhost:8088;
}

location ^~ /fonts/ {

proxy_cache mycache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

proxy_pass http://localhost:8088;
}

}

负载均衡

后端服务可以部署多个服务,以提高整个系统的可用性和吞吐量。使用nginx作为非常有效的HTTP负载平衡器,将流量分配到多个服务副本。

负载均衡架构图

负载均衡配置

使用 upstream定义一组服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
upstream todos-apps {
#不写,采用轮循机制
server localhost:8080;
server localhost:8081;

}

server {

listen 8000;
server_name todos.loadbalance;

location / {
proxy_pass http://todos-apps;
}

}

负载均衡策略

1.轮循机制(round-robin)

默认机制,以轮循机制方式分发。

2.最小连接(least-connected )

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

1
2
3
4
5
upstream backend {
least_conn ;
server backend1.example.com;
server backend2.example.com;
}

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

3.ip-hash

客户端的 IP 地址将用作哈希键,来自同一个ip的请求会被转发到相同的服务器。

1
2
3
4
5
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
}

📌此方法可确保来自同一客户端的请求将始终定向到同一服务器,除非此服务器不可用。
如果不这样,那就可能出现刷新一下又提示用户重新登录的情况‎

‎4.hash

通用hash,允许用户自定义hash的key,key可以是字符串、变量或组合。

例如,key可以是配对的源 IP 地址和端口,也可以是 URI,如以下示例所示:‎

1
2
3
4
5
upstream backend {
hash $request_uri consistent ; #consistent表示一致性哈希
server backend1.example.com;
server backend2.example.com;
}

由于基于 IP 的哈希算法存在一个问题:当有一个上游服务器宕机或者扩容的时候,会引发大量的路由变更,进而引发连锁反应,导致大量缓存失效等问题。

consistent参数启用 ‎‎ketama‎‎ 一致哈希算法,如果在上游组中添加或删除服务器,只会重新映射部分键,从而最大限度地减少缓存失效。‎

假设我们基于 key 来做 hash,现在有 4 台上游服务器,如果 hash 算法对 key 取模,请求根据用户定义的哈希键值均匀分布在所有上游服务器之间。

普通哈希算法示意图

当有一台服务器宕机的时候,就需要重新对 key 进行 hash,最后会发现所有的对应关系全都失效了,从而会引发缓存大范围失效。

服务器宕机后哈希重映射图

如果开启了一致性哈希:

一致性哈希算法示意图

5.‎随机‎‎ (random)

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

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

1
2
3
4
5
6
7
upstream backend {
random two least_conn;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
server backend4.example.com;
}

6.权重(weight)

权重负载均衡示意图

1
2
3
4
5
upstream my-server {
server performance.server weight=3;
server app1.server;
server app2.server;
}

如上所示,每 5 个新请求将按如下方式分布在应用程序实例中:3 个请求将定向到performance.server,一个请求将转到app1.server,另一个请求将转到app2.server。‎

7.健康检查

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

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

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

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

1
2
3
4
upstream backend {
server backend1.example.com;
server backend2.example.com max_fails=3 fail_timeout=30s;
}

参考文档:

https://nginx.org/en/docs/http/load_balancing.html

https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/

HTTPS配置

HTTPS 协议是由HTTP 加上TLS/SSL 协议构建的可进行加密传输、身份认证的网络协议,主要通过数字证书、加密算法、非对称密钥等技术完成互联网数据传输加密,实现互联网传输安全保护。

生成自签名证书

1
2
3
4
5
mkdir /root/ssl
cd /root/ssl
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

1. openssl genrsa -des3 -out server.key 2048

这个命令用于生成一个受密码保护的RSA私钥文件,用于后续的证书生成。

  • genrsa: 生成 RSA 私钥
  • -des3: 使用 3DES 算法对私钥进行加密保护(需要密码)
  • -out server.key: 将生成的私钥保存到 server.key 文件中
  • 2048: 密钥长度为 2048 位(推荐的安全长度)

2. openssl req -new -key server.key -out server.csr

这个命令用于生成证书签名请求(CSR)文件,包含您的组织信息、域名等,用于向证书颁发机构申请证书。

  • req -new: 创建新的证书签名请求
  • -key server.key: 使用刚才生成的私钥文件
  • -out server.csr: 将 CSR 保存到 server.csr 文件中

3. openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

这个命令用于生成自签名证书,有效期1年,用于 HTTPS 服务:

  • x509 -req: 处理证书请求并生成 X.509 格式证书
  • -days 365: 证书有效期为 365 天(1年)
  • -in server.csr: 使用刚才生成的 CSR 文件
  • -signkey server.key: 使用私钥对证书进行签名
  • -out server.crt: 将生成的证书保存到 server.crt 文件中

整个过程:

SSL证书生成步骤1

SSL证书生成步骤2

SSL证书生成步骤3

SSL证书生成步骤4

SSL证书生成步骤5

配置ssl

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 443 ssl ;
server_name todos.https;
ssl_certificate /root/ssl/server.crt;
ssl_certificate_key /root/ssl/server.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

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

如果设置了密码,需要加上:

1
2
3
4
5
server{
……
ssl_password_file /root/ssl/cert.pass;
……
}

热加载,需要输入密码:

Nginx SSL重载

测试前别忘了端口转发:

HTTPS端口转发

HTTPS浏览器测试

https优化

SSL 操作会消耗额外的 CPU 资源。CPU 占用最多的操作是 SSL 握手。有两种方法可以最大程度地减少每个客户端的这些操作数:

  • 使保持活动连接能够通过一个连接发送多个请求
  • 重用 SSL 会话参数以避免并行连接和后续连接的 SSL 握手

会话存储在工作进程之间共享并由 ssl_session_cache 指令配置的 SSL 会话缓存中。一兆字节的缓存包含大约 4000 个会话。默认缓存超时为 5 分钟。可以使用 ssl_session_timeout 指令增加此超时。以下是针对具有 10 MB 共享会话缓存的多核系统优化的示例配置:

1
2
ssl_session_cache   shared:SSL:10m;
ssl_session_timeout 10m;

TCP协议反向代理

TCP协议配置在stream里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stream {

upstream backend-mysql {

server localhost:3306;
server localhost:3307;

#保持的空闲连接的个数
keepalive 8;
}

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

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

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

重写(重定向)

nginx有两个重写指令:return和rewrite。

Return

服务端停止处理并将状态码status code返回给客户端,有下面四种格式:

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

场景1:强制所有请求都变成https

错误写法:

1
2
3
4
5
6
7
server {

listen 8003;
server_name ruoyi.loadbalance;

return 301 https:// localhost :8004;
}

这种写法是错误的,不能重定向到localhost,因为客户端localhost上肯定是没有部署服务的

应该使用ip:

1
2
3
4
5
6
7
server {

listen 8003;
server_name ruoyi.loadbalance;

return 301 https:// 192.168.56.105 :8004;
}

📌转发是服务端行为,重定向是客户端行为。

场景2:旧域名迁移,不让用户收藏的链接或者搜索引擎的链接失效

将请求从 www.old-name.com old-name.com 永久重定向到 www.new-name.com,包含http和https请求

1
2
3
4
5
6
server {
listen 80;
listen 443 ssl;
server_name www.old-name.com old-name.com;
return 301 $scheme://www.new-name.com $request_uri ;
}

由于捕获了域名后面的 URL 部分,因此,如果新旧网站之间存在一对一的页面对应关系(例如,www.new-name.com/about 具有与 www.old-name.com/about 相同的基本内容),则此重写是合适的。

如果除了更改域名之外还重新组织了网站,则通过省略以下内容,将所有请求重定向到主页可能会更安全:

1
2
3
4
5
6
server {
listen 80;
listen 443 ssl;
server_name www.old-name.com old-name.com;
return 301 $scheme://www.new-name.com;
}

场景3:补充www

很多时候用户访问网址可能都没有添加www,可以帮它补上:

1
2
3
4
5
6
7
# add 'www'
server {
listen 80;
listen 443 ssl;
server_name domain.com;
return 301 $scheme://www.domain.com$request_uri;
}

状态码说明

  • 2xx 成功
  • 3xx 表示重定向
    • 301 永久重定向
    • 302 临时重定向
  • 4xx 请求地址出错
    • 403 拒绝请求
    • 404 请求找不到
  • 5xx 服务器内部错误

Rewrite

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

1
2
3
4
5
6
7
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。如果文件名上有扩展名(如 .flv),则表达式会将其剥离,并将其替换为 .mp3

如果字符串包含新的请求参数,则以前的请求参数将追加到这些参数之后。如果不需要这样做,则在替换字符串的末尾放置一个问号可以避免附加它们:

1
rewrite ^/users/(.*)$ /show?user=$1? last;

last和break

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

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

  • 不使用last和break:默认按顺序执行。

  • 使用break

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    server {

    listen 8000;
    server_name nginx-dev;

    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!";
    }

    }

    访问 http://192.168.56.105:8000/old/1.txt

    1.匹配到了rewrite ^/old/(.*) /new/$1

    2.**break**指令不执行后续的rewrite规则,以新的/new/1.txt路径去执行块内的其他指令

    1. root目录下寻找文件, 由于不再村/home/AdminLTE-3.2.0/new/1.txt这个文件,返回404
  • 使用last

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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.56.105:8000/old/1.txt

    1.匹配到了rewrite ^/old/(.*) /new/$1

    2.last指令不执行后续的rewrite规则,以新的/new/1.txt路径去匹配location

    3.先匹配到location /, 有匹配到location里的rewrite ^/new/(.*) /pages/$1规则,重定向到/pages/1.txt

    4.匹配到了location /pages/1.txt ,于是返回了this is rewrite test!

压缩

压缩响应通常会显著减小传输数据的大小。但是,由于压缩发生在运行时,因此它还会增加相当大的处理开销,从而对性能产生负面影响。NGINX在将响应发送到客户端之前执行压缩,但如果后端服务器已经对内容进行了压缩,则nginx不会再压缩。

若要启用压缩,请在参数中包含 gzip 指令。

1
2
3
gzip on; 
gzip_types text/plain application/xml;
gzip_min_length 1000;

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

若要指定压缩响应的最小长度,请使用 gzip_min_length 指令。默认值为 20 个字节(此处调整为 1000)。

其他常用指令

sendfile

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

启用sendfile,类似Java中的零拷贝(zero copy)。

1
2
3
4
5
location /download {
sendfile on;
tcp_nopush on;
#...
}

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

try_files

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

如下,如果原始URI对应的文件不存在,NGINX将内部重定向到/www/data/images/default.gif

1
2
3
4
5
6
7
server {
root /www/data;

location /images/ {
try_files $uri /images/default.gif;
}
}

最后一个参数也可以是状态代码(状态码之前需要加上等号)。

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

1
2
3
location / {
try_files $uri $uri/ $uri.html =404;
}

在下一个示例中,如果原始 URI 和附加尾随斜杠的 URI 都没有解析到现有文件或目录中,则请求将重定向到命名位置,该位置会将其传递到代理服务器。

1
2
3
4
5
6
7
location / {
try_files $uri $uri/ @backend ;
}

location @backend {
proxy_pass http://backend.example.com;
}

error_page

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

1
2
error_page 404             /404.html; 
error_page 500 502 503 504 /50x.html;

推荐写法和注意事项

推荐写法

1. 重复的配置可继承自父级

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
server_name www.example.com;

location / {
root /var/www/nginx-default/;
# [...]
}
location /foo {
root /var/www/nginx-default/;
# [...]
}
location /bar {
root /some/other/place;
# [...]
}
}

推荐写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
server_name www.example.com;
root /var/www/nginx-default/;

location / {
# root继承父级配置
# [...]
}
location /foo {
# root继承父级配置
# [...]
}
location /bar {
# 覆盖
root /some/other/place;
# [...]
}
}

这样在添加新的location时,可以避免重复配置。

2. 不要将所有请求都代理到后端服务器

📌前后端分离的项目除外

不推荐:

1
2
3
location / {
proxy_pass http://localhost:8088;
}

考虑到很多请求是访问静态内容(如图片,css,javascript等文件),可以使用缓存或者配置静态目录来减少发送到后端的请求数量,这样可以减小后端服务器的开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 8002;
server_name ruoyi.tomcat;
root /home/www/static;
location / {
try_files $uri $uri/ @proxy;
}
location @proxy {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:8080;
}

}

3. 若非必要,不要缓存动态请求,只缓存静态文件

📌前后端分离的项目除外

nginx关于缓存的指令非常多

proxy_cache  

proxy_cache_background_update  

proxy_cache_bypass  

proxy_cache_convert_head  

proxy_cache_key  

proxy_cache_lock  

proxy_cache_lock_age  

proxy_cache_lock_timeout  

proxy_cache_max_range_offset  

proxy_cache_methods  

proxy_cache_min_uses  

proxy_cache_path  

proxy_cache_purge  

proxy_cache_revalidate  

proxy_cache_use_stale  

proxy_cache_valid  

proxy_no_cache

由于nginx服务端缓存非常复杂,在使用缓存的时候,我们要清楚的知道在什么条件下,哪些文件会被缓存。

在配置文件中,最好能够清晰的指定哪些文件使用缓存。

4. 检查文件是否存在使用try_files代替if -f

不推荐用法:

1
2
3
4
5
6
7
8
server {
root /var/www/example.com;
location / {
if (!-f $request_filename) {
break;
}
}
}

推荐用法:

1
2
3
4
5
6
server {
root /var/www/example.com;
location / {
try_files $uri $uri/ /index.html;
}
}

5. 在重写路径中包含http://或https://

1
2
3
4
#推荐写法
rewrite ^ http://example.com permanent;
#不推荐的写法
rewrite ^ example.com permanent;

6. 保持重写规则简单干净

例如下面的这个例子可以变得更简单易懂。

1
2
3
4
5
#复杂的写法
rewrite ^/(.*)$ http://example.com/$1 permanent;
#简单有效的写法
rewrite ^ http://example.com$request_uri? permanent;
return 301 http://example.com$request_uri;

注意事项

1. 正确的配置未生效,请清除浏览器缓存

当你确定修改的配置的正确的,但是未生效,请清除浏览器缓存或者禁用浏览器缓存。

2. 在HTTPS中不启用 SSLv3

由于 SSLv3 中存在 POODLE 漏洞,建议不要在启用了 SSL 的站点中使用 SSLv3。您可以使用以下行非常轻松地禁用 SSLv3,并仅提供 TLS 协议:

1
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

3. 不要将 root 目录配置成 //root

错误用法

1
2
3
4
5
6
7
8
9
server {
#错误用法
root /;

location /project/path {
#错误用法
root /root;
}
}

4. 谨慎使用chmod 777

这可能是解决问题最简单的方式,同时也说明,你没有真的弄清楚哪里出了问题。

可以使用namei -om /path/to/check显示路径上的所有权限,并找到问题的根本原因。

5. 不要将部署的项目拷贝到默认目录下

升级或更新nginx的时候,默认目录可能被覆盖。