以前写过一篇类似的博文,之前的文章现在看起来逻辑有点混乱,当时用的是阿里云的红帽发行版本,现在空间不租了,我用 Mac 电脑又折腾了一遍,为了加深印象,我把过程记录下来。

简介

Nginx 是一个高性能的 HTTP 和 reverse proxy 服务器,同时也支持邮件代理。由俄罗斯程序员 Igor Sysoev 创建,目的为了解决 C10k 问题,即并发问题。在 2004 年 8 月 1 日首次发布。2019 年 3 月,Nginx 已经被 F5 公司收购。

Nginx 是一个轻量级的 Web 服务器,并且支持反向代理,负载均衡,HTTP 缓存,图片转换,静态内容加速等。在服务器市场占有率迄今为止是最高的。

安装

  1. Linux
1
2
sudo apt update
sudo apt install nginx
  1. Mac OS
1
brew install nginx
  1. Windows
1
2
scoop install nginx
choco install nginx
  1. docker
1
docker pull nginx

启动

  1. Linux,Mac OS
1
nginx
  1. Windows 双击 nginx.exe

  2. docker

1
docker run -d -p 8080:8080 nginx

浏览器打开:localhost:8080

nignx

验证是否启动成功:

1
ps -ef | grep nginx

第二栏就是 nginx 进程的 PID 53807 (Master) 和 53808 (Worker)
nginx进程

Nginx 的工作模式是主进程 Master - Worker 模式,主进程主要复制读取和验证配置文件,而 Worker 子进程负责处理请求。

查看 8080 端口占用情况:

1
lsof -i:8080

查看nginx端口占用

常用命令

  1. 停止,重载及重新打开日志
1
nginx -s <signal>

上面的<signal>可以是:

  • stop:停止 Nginx
  • quit:退出 Nginx
  • reload:重载 Nginx 配置文件
  • reopen:重新打开 Nginx 日志文件
  1. 查看 Nginx 各种配置参数

查看所有参数:nginx -V,再看 --conf-path 参数的值

1
2
3
4
5
6
(base) zyzy:~ $ nginx -V
nginx version: nginx/1.25.5
built by clang 15.0.0 (clang-1500.3.9.4)
built with OpenSSL 3.2.1 30 Jan 2024 (running with OpenSSL 3.3.0 9 Apr 2024)
TLS SNI support enabled
configure arguments: --prefix=/opt/homebrew/Cellar/nginx/1.25.5 --sbin-path=/opt/homebrew/Cellar/nginx/1.25.5/bin/nginx --with-cc-opt='-I/opt/homebrew/opt/pcre2/include -I/opt/homebrew/opt/openssl@3/include' --with-ld-opt='-L/opt/homebrew/opt/pcre2/lib -L/opt/homebrew/opt/openssl@3/lib' --conf-path=/opt/homebrew/etc/nginx/nginx.conf --pid-path=/opt/homebrew/var/run/nginx.pid --lock-path=/opt/homebrew/var/run/nginx.lock --http-client-body-temp-path=/opt/homebrew/var/run/nginx/client_body_temp --http-proxy-temp-path=/opt/homebrew/var/run/nginx/proxy_temp --http-fastcgi-temp-path=/opt/homebrew/var/run/nginx/fastcgi_temp --http-uwsgi-temp-path=/opt/homebrew/var/run/nginx/uwsgi_temp --http-scgi-temp-path=/opt/homebrew/var/run/nginx/scgi_temp --http-log-path=/opt/homebrew/var/log/nginx/access.log --error-log-path=/opt/homebrew/var/log/nginx/error.log --with-compat --with-debug --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-ipv6 --with-mail --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module

也可以用 nginx -t查看

1
2
3
(base) zyzy:~ $ nginx -t
nginx: the configuration file /opt/homebrew/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /opt/homebrew/etc/nginx/nginx.conf test is successful

一般来说,Mac 系统该配置文件在 /opt/homebrew/etc/nginx/nginx.conf Linux 系统该配置文件在 /etc/nginx/conf/usr/local/etc/nginx

配置

这个是 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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

#user nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 8080;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}
include servers/*;
}

别看这么多,其实就 3 部分内容

  1. 全局块:例如worker_processes配置 Nginx 的进程数,默认为 1;
  2. events:事件部分,配置 Nginx 的事件处理方式,如 worker_processesworker_connections
  3. http:HTTP 模块部分,配置 Nginx 的 HTTP 模块,如 serverlocation

worker_processes

我们可以配置该参数为 auto,这样就能自动匹配电脑应有的核心数。

这里我故意把配置写错:worker_processes 10,尾部没加;,运行 nginx -t,能帮我们自带检查该文件错误:

1
2
3
(base) zyzy:1.25.5 (master) $ nginx -t
nginx: [emerg] directive "worker_processes" is not terminated by ";" in /opt/homebrew/etc/nginx/nginx.conf:12
nginx: configuration file /opt/homebrew/etc/nginx/nginx.conf test failed

每次修改完成后,nginx -s reload 重新加载 nginx.conf 文件

改为 auto 后,跑 ps -ef | grep nginx,发现 nginx 进程数变为 8 个,代表我用的 mac 电脑有 8 核,如下图:

events

worker_connections:配置 Nginx 的最大连接数,默认为 1024
multi_accept:是否允许同时接受多个连接,默认为 off,表示只接受一个连接,如果为 on,则表示允许同时接受多个连接

http

这个是被修改得最多的模块

include mime.types:包含 Nginx 能处理文件类型,默认为路径为: /opt/homebrew/etc/nginx/mime.types

server

http 模块下,包含多个 server 模块,每个 server 模块代表一个虚拟主机。

修改默认的 HTML 静态页面

部署一个静态站点,我们需要按以下配置修改对应的 html 文件:

1
2
3
4
location / {
root html;
index index.html index.htm;
}

location /:表示匹配所有请求,root:表示静态资源文件存放的目录,index:表示默认首页,默认为 index.html

那就去找这个 html 文件夹,可用 nginx -V 找到 --prefix=/opt/homebrew/Cellar/nginx/1.25.5字段,能找到 Nginx 的安装路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(base) zyzy:~ $ cd /opt/homebrew/Cellar/nginx/1.25.5
(base) zyzy:1.25.5 (master) $ ll
total 680
-rw-r--r-- 1 ziyouzhiyi admin 319K 4 16 22:37 CHANGES
-rw-r--r-- 1 ziyouzhiyi admin 1.3K 5 14 10:49 INSTALL_RECEIPT.json
-rw-r--r-- 1 ziyouzhiyi admin 1.4K 4 16 22:37 LICENSE
-rw-r--r-- 1 ziyouzhiyi admin 49B 4 16 22:37 README
drwxr-xr-x 3 ziyouzhiyi admin 96B 5 14 10:49 bin
-rw-r--r-- 1 ziyouzhiyi admin 685B 5 14 10:49 homebrew.mxcl.nginx.plist
-rw-r--r-- 1 ziyouzhiyi admin 202B 5 14 10:49 homebrew.nginx.service
lrwxr-xr-x 1 ziyouzhiyi admin 16B 5 14 10:49 html -> ../../../var/www
drwxr-xr-x 4 ziyouzhiyi admin 128B 4 16 22:37 share
(base) zyzy:1.25.5 (master) $ ls -ld $(readlink html)
drwxr-xr-x 4 ziyouzhiyi admin 128 2 15 00:03 ../../../var/www
(base) zyzy:1.25.5 (master) $ cd html
(base) zyzy:html (master) $ ls
50x.html index.html

可以清晰的看到,html 不仅仅是一个软链接,他还是一个文件夹,LinuxMacOS 都可以将文件夹作为软链接,这样可以避免移动文件夹。

我们改动 index.html,然后 nginx -s reload 重新加载配置,可以看到浏览器的页面已经修改:

修改index

1
2
3
(base) zyzy:1.25.5 (master) $ cd html
(base) zyzy:html (master) $ ls
50x.html index.html
  • location / 下的 root 指定

include servers/*;:表示将 servers 目录下的所有配置文件包含进来

反向代理及负载均衡

写一个最简单的 node http 服务 index.js

1
2
3
4
5
6
7
8
9
10
11
12
const http = require("http");

const PORT = 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.write(`<h1>Node.js web server! Port: ${PORT}</h1>`);
res.end();
});

server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

我们将其复制 2 份,端口号分别改为 3020 和 3030,文件名改为 index-3000.jsindex-3020.jsindex-3030.js,分别跑起来,页面localhost:3000localhost:3020localhost:3030 都能打开。

再修改 nginx.conf,添加反向代理配置:http 模块中添加 upstream反向代理字段, server 模块中添加 proxy_pass字段,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http {
# 其他配置。。。

upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3010;
server 127.0.0.1:3020;
}

server {
location /app {
# backend字段对应上面upstream的backend字段
proxy_pass http://backend;
}
}
}

重载 nginx -s reload,然后访问 localhost:8080/app,再不断刷新,页面显示如下,反向代理成功:

upstream

请求策略

  • weight

upstream 字段下的 ip 地址,可以添加权重 weight,如:

1
2
3
4
5
upstream backend {
server 127.0.0.1:3000 weight=3;
server 127.0.0.1:3010;
server 127.0.0.1:3020;
}

这样,我们多数的请求就会分配到端口为 3000 的服务器上,3000 的请求数量就会是其余两个的 3 倍。

  • ip_hash
1
2
3
4
5
6
upstream backend {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3010;
server 127.0.0.1:3020;
}

ip_hash:根据客户端的 IP 地址进行哈希,同一个客户端的请求会分配到同一个服务器上,这样主要是解决 session 命中的问题。

SSL 配置

HTTPS 的安全层正数,可以在主流的云平台申请,我们也可以自己制作证书

生成密钥和证书

1
2
3
openssl genrsa -out private.key 2048
openssl req -new -key private.key -out cert.csr
openssl x509 -req -in cert.csr -out cacert.pem -signkey private.key -days 3650

按照以上命令生成了 cacert.pem(证书) 和 private.key(私钥) 两个文件。将两个文件移动到 /opt/homebrew/etc/nginx/ 目录下

1
(base) zyzy:1.25.5 (master) $ mv cacert.pem private.key /opt/homebrew/etc/nginx/

配置 nginx.conf 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
# ssl证书默认443端口:
listen 443 ssl;
server_name localhost;

# 以下写法固定:
# 证书文件名称:
ssl_certificate /opt/homebrew/etc/nginx/cacert.pem;
# 私钥文件名称:
ssl_certificate_key /opt/homebrew/etc/nginx/private.key;
# ssl验证:
ssl_session_timeout 5m; # 缓存有效期
# 安全链接可选的加密协议:
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# 加密套件/加密算法,写法遵循 openssl的格式:
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
# 使用服务端的首选算法:
ssl_prefer_server_ciphers on;
}

当我们访问 localhost:8080,会发现 chrome 浏览器会提示我们不安全,这是因为证书是我们自己制作的,并非机构颁发,我们选择继续访问,并在这里可以点开查看我们刚刚输入的证书信息:

nginx配置ssl1
点击看到证书:
nginx配置ssl2

配置重定向

可以将 http 的访问重定向到我们的 https 端口上

1
2
3
4
5
server {
listen 8080;
server_name localhost;
return 301 https://$host$request_uri;
}

如果有配置域名,则改为如下:

1
2
3
4
5
server {
listen 8080;
server_name zyzy.info;
return 301 https://$server_name$request_uri;
}

虚拟主机

在网站初期,访问量较小,一个服务器配置多个服务可以节省资源,这种多服务器配置称为虚拟主机,下面我们来实现

server 独立配置

我们将 /opt/homebrew/etc/nginx/nginx.conf 下上面配置好的 server单独剪切出来,新建一个 localhost.conf 文件,放到 /opt/homebrew/etc/nginx/server 下,

重载 nginx -s reload,浏览器打开发现网站正常运行,说明 server 目录可以存放多个虚拟主机的配置文件。

/nginx/server下创建其他服务

创建前端 SPA 页面

结合的 SPA 项目,我们用 vite 生成一个 react 项目,

1
2
3
4
npm create vite@latest demo-project -- --template react
cd vite_react
npm install
npm run build

进入 dist 目录,复制路径:

1
2
(base) zyzy:dist $ pwd
/Users/ziyouzhiyi/servers/demo-project/dist

创建server 模块

我们在 /opt/homebrew/etc/nginx/server 目录下写一个 vite_react.conf 配置文件,配置如下:将上面/Users/ziyouzhiyi/servers/demo-project/dist 路径配置如下:

1
2
3
4
5
6
7
8
9
server {
listen 5173;
server_name vite_react;

location / {
root /Users/ziyouzhiyi/servers/demo-project/dist;
index index.html index.htm;
}
}

重载 nginx -s reload

检验配置

访问 localhost:5173,页面正常显示,说明配置成功。页面访问:http://localhost:5173/,如下:

nginx配置虚拟主机

上面的页面还没配置 ssl,我们把他改成 https 访问,将 localhost.conf,的配置粘过来,改动如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 5173 ssl;
server_name vite_react;

# 证书文件名称:
ssl_certificate /opt/homebrew/etc/nginx/cacert.pem;
# 私钥文件名称:
ssl_certificate_key /opt/homebrew/etc/nginx/private.key;
# 以下配置固定:
# ssl验证:
ssl_session_timeout 5m; # 缓存有效期
# 安全链接可选的加密协议:
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# 加密套件/加密算法,写法遵循 openssl的格式:
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
# 使用服务端的首选算法:
ssl_prefer_server_ciphers on;

location / {
root /Users/ziyouzhiyi/servers/demo-project/dist;
index index.html index.htm;
}
}