0%

Nginx快速上手

Nginx头图

Nginx 是一个高性能的 HTTP 服务器,支持虚拟主机、反向代理和负载均衡。本篇简单介绍下 Nginx 的组件、用途、常用命令,以及如何配置多虚拟主机、反向代理、负载均衡。


概述

Nginx 由一个 master 进程和多个 worker 进程组成:

  • master 进程:读取、校验配置,维护 worker 进程;
  • worker 进程:接收请求并进行处理。

Nginx 可以用作什么:

  • HTTP 服务器:Nginx 可以独立提供 HTTP 服务,作为静态网页服务器;
  • 虚拟主机:在一台服务器上虚拟出多个网站;
  • 反向代理:从一组或多组后端服务器上获取资源,然后再将这些资源返回给客户端;
  • 负载均衡:多台服务器组成的集群使用 Nginx 做反向代理,分担负载。

安装

Nginx 的安装过程无需过多介绍:

1
2
3
4
5
# CentOS
$ yum install nginx

# Debian
$ apt install nginx

其他版本 Linux 的 Nginx 安装过程可以参考官方指南 nginx: Linux packages

常用命令

启动 Nginx:

1
2
3
4
5
6
7
8
9
10
11
12
# 启动Nginx,加载默认配置文件/etc/nginx/nginx.conf
$ nginx

# 使用-c参数加载指定的配置文件
$ nginx -c /etc/nginx/conf.d/blog.conf

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

# 设置开机自启
$ systemctl enable nginx.service
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.

Nginx 启动后,可以使用 -s 参数来进行控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 语法
$ nginx -s <signal>

# 重新加载配置文件,无需重启master进程
# 如果新配置无效,master会继续使用旧配置
# 如果新配置有效,master进程会启动新的worker进程;旧的worker进程停止接受新连接,在处理完毕已有请求后退出
$ nginx -s reload

# 优雅停止,会等待当前请求处理完毕
$ nginx -s quit

# 快速停止,相当于kill
$ nginx -s stop

简易配置说明

配置语法

Nginx 由模块组成,模块由配置文件中的指令控制。指令分为简单指令和块指令两种:

  • 简单指令:也称为单行指令,指令和参数用空格隔开,由分号 ; 结尾;
  • 块指令:指令和参数用空格隔开,不以分号 ; 结尾,而是用大括号 {} 包裹起来。如果块指令可以在大括号内包含其他指令,则称为 context,比如 eventshttpserverlocation

在任一 context 以外的指令被认为是在 main 这个 context 中,在全局范围生效,可以被子 context 中的配置覆盖。

常用配置项

Nginx 及模块的工作方式由配置文件决定,默认配置文件为 /etc/nginx/nginx.conf

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
# 设置运行Nginx的用户
user nginx;
# worker进程数,建议设置为CPU总核心数
worker_processes auto;

# 将指定级别及以上的错误日志记录到指定文件,默认记录级别为 error
# 严重性从低到高为 debug, info, notice, warn, error, crit, alert, emerg
error_log /var/log/nginx/error.log error;

# 设置进程pid文件
pid /run/nginx.pid;

# 加载动态模块
include /usr/share/nginx/modules/*.conf;

# 设置对一般连接的处理
events {
# 单个进程最大连接数
worker_connections 1024;
}

# 设置对HTTP流量的处理
http {
# 设置日志格式,将这种格式命名为main,日志内容使用了Nginx内置变量
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 将访问日志以指定格式记录到指定文件,默认日志格式为combined,这里使用刚设置的main格式
access_log /var/log/nginx/access.log main;

# 是否调用sendfile函数(零拷贝)来输出文件
sendfile on;
tcp_nopush on;
tcp_nodelay on;

# 长连接超时时间(秒)
keepalive_timeout 65;
types_hash_max_size 2048;

# 加载指定路径下的MIME类型配置
include /etc/nginx/mime.types;
# 定义响应的默认MIME类型
default_type application/octet-stream;

# 加载指定路径下的配置
include /etc/nginx/conf.d/*.conf;

# 设置虚拟服务器
server {
# 设置监听端口和地址。可以同时指定端口和地址,或只指定端口或地址
# default_server用于声明此address:port为默认服务器
# 如果所有服务器配置都没有声明default_server,则第一个address:port为默认服务器
listen 127.0.0.1:80 default_server;
listen [::]:80 default_server;
# 设置虚拟服务器的名称,可以有多个,用空格隔开
# 支持通配符(*)和正则表达式(用波浪符~开头)
server_name sannaha.moe;

# 设置将用于搜索文件的根目录
# 例如:请求/Hello/world.png,会发送/home/git/www/hexo/Hello/world.png
root /home/git/www/hexo;

# 加载指定路径下的配置
include /etc/nginx/default.d/*.conf;

# 设置如何响应以/开头的请求
location / {
}

# 设置如何处理404错误代码
# 使用301重定向来处理404错误,注意等号后面不能留空格
error_page 404 =301 https://sannaha.moe;

# 设置如何处理50x错误代码
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}

内置变量

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
$args                    #请求中的参数值
$query_string #同 $args
$arg_NAME #GET请求中NAME的值
$is_args #如果请求中有参数,值为"?",否则为空字符串
$uri #请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如"/foo/bar.html"。
$document_uri #同 $uri
$document_root #当前请求的文档根目录或别名
$host #优先级:HTTP请求行的主机名>"HOST"请求头字段>符合请求的服务器名
$hostname #主机名
$https #如果开启了SSL安全模式,值为"on",否则为空字符串。
$binary_remote_addr #客户端地址的二进制形式,固定长度为4个字节
$body_bytes_sent #传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的"%B"参数保持兼容
$bytes_sent #传输给客户端的字节数
$connection #TCP连接的序列号
$connection_requests #TCP连接当前的请求数量
$content_length #"Content-Length" 请求头字段
$content_type #"Content-Type" 请求头字段
$cookie_name #cookie名称
$limit_rate #用于设置响应的速度限制
$msec #当前的Unix时间戳
$nginx_version #nginx版本
$pid #工作进程的PID
$pipe #如果请求来自管道通信,值为"p",否则为"."
$proxy_protocol_addr #获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串
$realpath_root #当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径
$remote_addr #客户端地址
$remote_port #客户端端口
$remote_user #用于HTTP基础认证服务的用户名
$request #代表客户端的请求地址
$request_body #客户端的请求主体:此变量可在location中使用,将请求主体通过proxy_pass,fastcgi_pass,uwsgi_pass和scgi_pass传递给下一级的代理服务器
$request_body_file #将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off,uwsgi_pass_request_body off,or scgi_pass_request_body off
$request_completion #如果请求成功,值为"OK",如果请求未完成或者请求不是一个范围请求的最后一部分,则为空
$request_filename #当前连接请求的文件路径,由root或alias指令与URI请求生成
$request_length #请求的长度 (包括请求的地址,http请求头和请求主体)
$request_method #HTTP请求方法,通常为"GET"或"POST"
$request_time #处理客户端请求使用的时间; 从读取客户端的第一个字节开始计时
$request_uri #这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:"/cnphp/test.php?arg=freemouse"
$scheme #请求使用的Web协议,"http" 或 "https"
$server_addr #服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中
$server_name #服务器名
$server_port #服务器端口
$server_protocol #服务器的HTTP版本,通常为 "HTTP/1.0" 或 "HTTP/1.1"
$status #HTTP响应代码
$time_iso8601 #服务器时间的ISO 8610格式
$time_local #服务器时间(LOG Format 格式)
$cookie_NAME #客户端请求Header头中的cookie变量,前缀"$cookie_"加上cookie名称的变量,该变量的值即为cookie名称的值
$http_NAME #匹配任意请求头字段;变量名中的后半部分NAME可以替换成任意请求头字段,如在配置文件中需要获取http请求头:"Accept-Language",$http_accept_language即可
$http_cookie
$http_post
$http_referer
$http_user_agent
$http_x_forwarded_for
$sent_http_NAME #可以设置任意http响应头字段;变量名中的后半部分NAME可以替换成任意响应头字段,如需要设置响应头Content-length,$sent_http_content_length即可
$sent_http_cache_control
$sent_http_connection
$sent_http_content_type
$sent_http_keep_alive
$sent_http_last_modified
$sent_http_location
$sent_http_transfer_encoding

多虚拟主机

虚拟主机是一种特殊的软硬件技术,它可以将网络上的一台计算机分成多个虚拟主机,每个虚拟主机都可以独立对外提供 WEB 服务。

对于 Nginx,可以将多个 server 添加到 http 这个 context 中,定义多个虚拟服务器。不同 server 可以通过 server_nameaddressport 进行区分。

多虚拟主机示例

需求:在一台服务器上,根据 server_name 不同,同时对外提供 blog 和 homepage 两个服务。

由于在默认配置文件所示,通过 include 指令在 http 这个 context 中引入了 conf.d 目录下的所有配置,因此可以在 conf.d 目录下创建多个配置文件,实现虚拟主机的模块化配置。

创建 blog.conf,提供博客服务:

blog.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 {
# 监听80端口
listen 80;
listen [::]:80;

# 设置虚拟服务器的名称,用于处理Host标头字段为sannaha.moe的请求
server_name sannaha.moe;

index index.html;

# 设置将用于搜索文件的根目录
root /user/git/www/hexo/;

location / {
}

# 使用301重定向来处理404错误
error_page 404 =301 http://sannaha.moe;

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

创建 homepage.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 {
# 监听80端口
listen 80;
listen [::]:80;

# 设置虚拟服务器的名称,用于处理Host标头字段为thinklong.me的请求
server_name thinklong.me;

index index.html;

# 设置将用于搜索文件的根目录
root /data/website/homepage/;

location / {
}

# 使用301重定向来处理404错误
error_page 404 =301 http://thinklong.me;

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

反向代理

了解代理服务器

代理服务器处于客户端和目标服务器之间。客户端发起请求,代理服务器接收请求后转发给目标服务器,目标服务器收到请求并将响应数据发给代理服务器,代理服务器再将响应数据返回给客户端。

代理服务器的主要作用如下:

  • 提高响应速度:有时候客户端经代理服务器到目标服务器的链路比客户端直接连接目标服务器更快;客户端可能无法直接连接目标服务器,通过代理服务器才能实现访问;代理服务器有做缓存,对于相同的请求可以直接从代理服务器本地返回;
  • 设置防火墙:客户端的所有请求都会经过代理服务器,在代理服务器上设置的防火墙可以过滤所有认为是不安全的信息,方便管理客户端能获取的内容。

正向代理和反向代理

将客户比作顾客,代理服务器比作服务员,目标服务器比作厨师,正向、反向代理对应的场景如下:

正向代理:

  • 顾客 :“服务员,我要吃唐牛做的佛跳墙。”
  • 服务员:“好嘞,这就叫唐牛给您做!”

反向代理:

  • 顾客:“服务员,我要一份蛋炒饭。”
  • 服务员:“好嘞”。然后去找史提芬周做蛋炒饭。

正向代理和反向代理的区别:

  • 正向代理:客户端提出想要获得目标服务器的服务,代理服务器将客户端的请求转发给目标服务器。即代理服务器的代理角色是客户端,去和目标服务器交互;
  • 反向代理:客户端提出服务需求,代理服务器选择一个目标服务器去实现该需求。即代理服务器代理的角色是目标服务器,去和客户端交互。

反向代理示例

需求:反向代理服务器,将前缀字符串匹配到 /read/doc/ 的请求转发到目标服务器 sannaha.moe,将 php 请求转发到本地的 8080 端口。

要将请求传递给 HTTP 服务器,需要在 location 中指定 proxy_pass 指令:

reverse-proxy.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
listen [::]:80;
server_name reverse-proxy.thinklong.me;

# 转发匹配到/read/doc/的请求到sannaha.moe
location /read/doc/ {
proxy_pass http://sannaha.moe/archive/;
}

# 转发php请求到127.0.0.1,并指定端口为8080
location ~ \.php {
proxy_pass http://127.0.0.1:8080;
}
}

反向代理的目标服务器地址可以是域名或 IP,可以指定端口。

对于像第一个 proxy_pass 这样在地址后还跟了 URI 的,请求的 URI 中与 location 参数匹配的部分将被替换。例如,/read/doc/page.html 的请求将被代理到http://sannaha.moe/archive/page.html。如果指定的地址后没跟 URI,或无法确定要替换 URI 的哪些部分,则会传递请求的完整 URI。

负载均衡

负载均衡的作用

当网站的访问量达到一定程度后,单台服务器无法满足大量用户的访问,需要将多台服务器组成集群,通过 Nginx 作反向代理来响应用户的请求,可以在性能低的主机分配少一些的负载,在性能高的主机上分配更多的负载,充分利用主机的性能,降低服务器的总压力。

负载均衡示例

要配置负载均衡,首先创建一个 upstream,在其中列出用于分发客户端请求的多个服务器,然后在一个或多个 proxy_pass 中引用 upstream,实现反向代理和负载均衡。

Nginx 的 upstream 默认是以轮询的方式分发请求(平均分配),但也支持使用 weight 实现权重的手动分配。

  1. 启动三个 Tomcat 服务,端口分别为 808080818082,模拟三台服务器;
  2. 修改 Nginx 配置文件,在 upstream 中配置上三个 Tomcat 服务地址,使用 weight 手动分配权重;
  3. proxy_pass 中配置反向代理;
load-balancing.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream blog-servers {
server s1.sannaha.moe weight=2;
server s2.sannaha.moe weight=1;
server s3.sannaha.moe weight=1;
}

server {
listen 80;
server_name sannaha.moe;

location / {
proxy_pass http://blog-servers;
index index.html;
}
}

注意:用户的请求会按照权重分发到不同服务器上,在一次点击行为中,可能会向服务器 A 请求 a.cssa.html、向服务器 B 请求 b.js、向服务器 C 请求 c.jsp ,因此要求每个服务器上都能够完整地提供用户需要的资源。如果某个服务器无法提供用户请求的资源,就会出现 404。

问题

ipv6 only 问题

1
2
3
$ nginx -t
nginx: [emerg] duplicate listen options for [::]:443 in /etc/nginx/conf.d/homepage.conf:7
nginx: configuration file /etc/nginx/nginx.conf test failed

https://github.com/certbot/certbot/issues/5550


参考资料

Nginx - Beginner’s Guide
Nginx - Admin Guide

  • 本文作者: SANNAHA
  • 本文链接: https://sannaha.moe/Nginx/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!