Nginx 常用配置

配置跨域

下述的 add_header 末尾都可以加上了 always,它表示不管 HTTP 返回状态码是多少都会使 add_header 生效,有些时候服务端可能会返回 4XX 的 HTTP 状态码,这时候如果少了 always 会导致 add_header 失效,从而导致浏览器报跨域错误。

1
2
3
4
5
6
7
8
9
10
11
12
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Origin' always;

if ($request_method = 'OPTIONS') {
return 204;
}

...
}

或者使用通配符,允许所有头部、所有域、所有方法跨域,存在安全隐患(不推荐使用)

1
2
3
4
5
6
7
8
9
10
11
12
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Headers' '*' always;
add_header 'Access-Control-Allow-Methods' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;

if ($request_method = 'OPTIONS') {
return 204;
}

...
}

通过 CURL 命令测试配置是否生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ curl -I  http://www.example.com/slider.e37972.js

# 测试结果
HTTP/1.1 200 OK
Server: Nginx/2.2.3
Date: Tue, 25 Dec 2018 17:59:53 GMT
Content-Type: application/javascript
Content-Length: 53386
Last-Modified: Tue, 25 Dec 2018 17:56:47 GMT
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS
Access-Control-Allow-Headers: Accept,Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Origin
Accept-Ranges: bytes

删除指定的 Heder

相关指令:

  • proxy_set_header is to set a request header
  • add_header is to add header to response
  • proxy_hide_header is to hide a response header

如果要替换响应中已经存在的 Header,仅仅使用 add_header 是不够的,因为它将堆叠值(堆叠来自服务器和自己添加的 Header),所以必须分两步执行操作:

  • 删除 Header:proxy_hide_header Access-Control-Allow-Origin;
  • 添加自定义 Header:add_header Access-Control-Allow-Origin "*" always;

假设需要删除响应中已存在的跨域 Header,然后往响应中添加自定义的跨域 Header,配置示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location / {
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Allow-Credentials;

add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Origin' always;

proxy_pass http://example.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header REMOTE-HOST $remote_addr;
}

配置 GZip 压缩

1
2
3
4
5
6
7
8
9
10
11
http {
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 3;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain text/css text/xml text/javascript application/json application/x-javascript application/xml application/xml+rss application/javascript;

...
}
1
2
3
4
5
6
7
8
9
10
11
# 通过curl命令测试配置是否生效
$ curl -I -H "Accept-Encoding: gzip, deflate" http://www.example.com/slider.e37972.js

# 测试结果
HTTP/1.1 200 OK
Server: Nginx/2.2.3
Date: Tue, 25 Dec 2018 17:54:06 GMT
Content-Type: application/javascript
Last-Modified: Tue, 25 Dec 2018 17:52:31 GMT
Connection: keep-alive
Content-Encoding: gzip

Nginx 反向代理配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http {
upstream applications {
server 192.168.68.43:8081 weight=1;
server 192.168.68.45:8082 weight=1;
}

server {
listen 80;
server_name localhost;

location / {
proxy_pass http://applications;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-Port $remote_port;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}

配置 Web 静态资源的浏览器端缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
root /home/wwwroot/hexo-blog;

location ~ .*\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|eot|ttf|woff|woff2)$
{
expires 7d;
}

location ~ .*\.(?:js|css)$
{
expires 7d;
}

location ~ .*\.(?:htm|html)$
{
# 根据具体的项目业务,决定是否需要配置浏览器端的静态页面缓存,以下配置表示是不缓存任何Html页面
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}

...
}

Nginx 利用 Referer 指令实现防盗链

语法说明

1
2
3
4
5
6
7
8
9
10
语法: valid_referers none | blocked | server_names | string …;
配置段: server, location
指定合法的来源'referer', 他决定了内置变量$invalid_referer的值,如果referer头部包含在这个合法网址列表中,这个变量被设置为0,否则设置为1. 不区分大小写。

参数说明:
none "Referer" 为空
blocked "Referer"不为空,但是里面的值被代理或者防火墙删除了,这些值都不以http://或者https://开头,而是"Referer: XXXXXXX"这种形式
server_names "Referer"来源头部包含当前的server_names(当前域名)
arbitrary string 任意字符串,定义服务器名或者可选的URI前缀.主机名可以使用*开头或者结尾,在检测来源头部这个过程中,来源域名中的主机端口将会被忽略掉
regular expression 正则表达式,~表示排除https://或http://开头的字符串.

两种配置案例

1
2
3
4
5
6
7
8
9
10
11
12
# 配置案例一:限制来源只能是none、blocked、主机域名、百度、谷歌、必应

location ~* \.(gif|jpg|png|webp)$ {

valid_referers none blocked *.baidu.com *.google.com *.bing.com server_names ~\.baidu\. ~\.google\. ~\.bing\.;

if ($invalid_referer) {
return 403;
}

root /opt/www/image;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置案例二:屏蔽所有来自指定域名(例如搜狗)的访问

location ~* \.(gif|jpg|png|webp)$ {

valid_referers sogou.com *.sogou.com ~\.sogou\.;

# 此处必须是匹配空字符串
if ($invalid_referer = ''){
return 403;
}

root /opt/www/image;
}

测试配置是否生效

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
# 通过curl命令测试配置是否生效,强烈建议额外测试referer字段为空字符串或者无此字段的请求
$ curl -v -k -I --referer https://www.sogou.com https://example.com

* About to connect() to www.example.com port 443 (#0)
* Trying www.example.com...
* Connected to www.example.com (14.215.177.38) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_RSA_WITH_AES_256_CBC_SHA256
* Server certificate:
* subject: CN=www.example.cn
* start date: 3月 19 00:00:00 2019 GMT
* expire date: 3月 18 12:00:00 2020 GMT
* common name: www.example.cn
* issuer: CN=TrustAsia TLS RSA CA,OU=Domain Validated SSL,O="TrustAsia Technologies, Inc.",C=CN
> HEAD / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.example.com
> Accept: */*
> Referer: https://www.sogou.com
>
< HTTP/1.1 403 Forbidden
HTTP/1.1 403 Forbidden
< Server: Nginx/2.2.3
Server: Nginx/2.2.3
< Date: Thu, 02 Jan 2020 23:38:13 GMT
Date: Thu, 02 Jan 2020 23:38:13 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 612
Content-Length: 612
< Connection: keep-alive
Connection: keep-alive

<
* Connection #0 to host www.example.com left intact

Nginx 放宽 GET 请求中 URL 的最大长度限制

  • client_header_buffer_size 的默认值: client_header_buffer_size 1k
  • large_client_header_buffers 的默认值: large_client_header_buffers 4 4k
1
2
3
4
5
6
7
8
9
http {
include mime.types;
default_type application/octet-stream;

client_header_buffer_size 512k;
large_client_header_buffers 4 1m;

...
}

Nginx 反向代理响应超时解决方案

Nginx 访问出现 504 Gateway Time-out,这一般是由于程序执行时间过长导致响应超时,例如程序需要执行 90 秒,而 Nginx 最大响应等待时间为 30 秒,这样就会出现超时。

1
2
3
4
5
location / {
proxy_connect_timeout 1800s; #Nginx跟后端服务器连接超时时间(代理连接超时)
proxy_send_timeout 1800s; #后端服务器数据回传时间(代理发送超时)
proxy_read_timeout 1800s; #连接成功后,后端服务器响应时间(代理接收超时)
}

Nginx 反向代理设置响应端口

Nginx 默认反向代理后的端口为 80,因此存在取被代理后的端口为 80 的问题,这就可能会导致业务逻辑出错;主要原因是在 Nginx 的配置文件中, 配置 Host 属性时没有设置响应的端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 8080;

location /ime-server {
proxy_pass http://ime-server/ime-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

...
}

在如上的配置内容中,Host 属性只配置了 $host,没有对应的 port,这就导致在被代理的地方取得错误的端口,以 Java 代码为例:

1
2
3
4
String scheme = httpRequest.getScheme();
String serverName = httpRequest.getServerName();
int port = httpRequest.getServerPort();
String requestURI = scheme+"://"+serverName+":"+port+"/ime-server/rest/"+serviceName+"/wmts";

这时,Java 代码取得的 port80,即使 Nginx 监听的端口为 8080。此时需要修改 Nginx 的配置文件,将 Host 属性后面的配置内容改为 $host:$server_port,配置示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 8080;

location /ime-server {
proxy_pass http://ime-server/ime-server;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

...
}

Nginx 反向代理指定 404 页面

Nginx 配置了反向代理后,若希望统一指定反向代理后的 404 页面,可以使用 proxy_intercept_errorserror_page 指令实现,更多内容可参考这里

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

proxy_intercept_errors on;

error_page 404 /404.html;

# 或者
# error_page 404 http://cloud.example.com;

upstream www.example.com {
server localhost:8080;
}

server {
listen 80;
server_name www.example.com;

location / {
proxy_pass http://www.example.com;
index index.html index.htm;
}
}
}

Nginx 关闭日志

  • 关闭访问日志: access_log off;
  • 关闭错误日志: error_log /dev/null;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
worker_processes  4;

error_log /dev/null;

...

http {
include mime.types;
include /usr/local/nginx-1.24.0/conf/conf.d/*.conf;
default_type application/octet-stream;

access_log off;

...
}

Nginx 443 端口绑定多个 SSL 证书

Nginx 支持 SNI(Server Name Indication),它允许客户端在发起 SSL 握手请求时提供要访问的域名。当 Nginx 为不同的域名配置不同的 SSL 证书时,它可以根据这个域名来选择合适的 SSL 证书进行处理。

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 443 ssl;
server_name example1.com;

ssl_certificate /path/to/example1.com.crt;
ssl_certificate_key /path/to/example1.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

# 其他配置...
}

server {
listen 443 ssl;
server_name example2.com;

ssl_certificate /path/to/example2.com.crt;
ssl_certificate_key /path/to/example2.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

# 其他配置...
}

在上述配置示例中,Nginx 监听 443 端口,并根据不同的 server_name 来应用不同的 SSL 证书。这样,即使是在同一个 443 端口,Nginx 也能根据不同的域名提供不同的安全服务。