1 概述
nginx经验汇总
2 安装与部署
2.1 安装
brew install nginx
mac 安装方式,配置目录在/usr/local/etc/nginx
sudo apt-get install nginx
ubuntu 安装方式,配置目录在/etc/nginx
2.2 启动与关闭
# 启动
sudo nginx
# 刷新配置
sudo nginx -s reload
# 停止
sudo nginx -s stop
mac的方式
# 启动
sudo service nginx start
# 刷新配置
sudo service nginx reload
# 停止
sudo service nginx stop
ubuntu的方式
2.3 静态文件
server{
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/OfficialSite;
index index.html;
location / {
index index.html;
try_files $uri $uri/ =404;
add_header Cache-Control "private, no-cache";
}
}
静态文件可以说是入门用法了,直接用try_files就可以了。另外,默认服务器,可以用default_server,和任意匹配server_name的_指令。
你可以根据需要自行调整Cache-Control头信息的值。这里是一些常用的值:
"public, max-age=3600":允许公共缓存,缓存时间为1小时。
"private, no-cache":禁止公共缓存,但允许私有缓存,需要每次向服务器验证缓存是否过期。
"no-store":禁用缓存,每次都要从服务器请求资源。
"no-cache":允许缓存,但需要从服务器验证缓存是否过期。
add_header Cache-Control "public, max-age=3600"; # 设置缓存时间为1小时
add_header Cache-Control "private, no-cache"; #禁止公共缓存,但允许私有缓存,需要每次向服务器验证缓存是否过期。
注意Cache-Control的写法,来自ChatGPT
2.4 docker安装
curl -sSL https://raw.githubusercontent.com/bitnami/containers/main/bitnami/nginx/docker-compose.yml > docker-compose.yml
下载docker-compose.yml,看这里
# Copyright VMware, Inc.
# SPDX-License-Identifier: APACHE-2.0
version: '2'
services:
nginx:
image: docker.io/bitnami/nginx:1.25
restart: always
volumes:
- C:\Users\fishe\Util\nginx\conf\mylocalhost.conf:/opt/bitnami/nginx/conf/server_blocks/mylocalhost.conf
ports:
- '80:80'
将配置文件进行映射,并配置好端口
upstream erp {
server 192.168.1.8:8000;
keepalive 8;
}
server {
listen 80 default;
listen [::]:80;
server_name mylocalhost.com;
location / {
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_pass http://erp;
}
}
注意,ip地址不能写127.0.0.1,因为nginx运行在docker环境中
docker compose up -d
3 日志输出
log_format timed_combined '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' '$request_time $upstream_response_time';
access_log /var/log/nginx/access.log timed_combined;
error_log /var/log/nginx/error.log;
调优的第一步是获取性能输出文件,找出瓶颈在哪里。添加以上配置到nginx.conf文件,日志中就会输出请求的总处理时间(request_time),以及后端的cgi的处理时间(upstream_response_time)
4 压缩文件
gzip on;
gzip_disable "msie6";
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;
添加以上配置到nginx.conf,以打开gzip压缩,对于静态文件,和大json文件而言,这个方法很有效
5 连接数量
ulimit -n
在命令行中,输入这一句获取单个进程的最大打开文件数量。由于在linux中,这个数值为1024,这意味着nginx最多只能同时处理1024个请求。
* soft noproc 10240
* hard noproc 10240
* soft nofile 10240
* hard nofile 10240
在/etc/security/limits.conf文件添加以上命令即可让所有用户的的单个进程数量提高到10240。注意,该数值应该与机器的配置相关。
6 长连接
即使将最大连接数量提高了,在大并发的环境下,nginx也可能因为连接文件有限而被迫等待。这可能是因为nginx与后端cgi的连接是短连接的方式,每处理完一个请求就断开,导致大量的连接不断使用新建和丢弃,大大浪费了连接资源。
netstat -an | awk '/^tcp/ {++y[$NF]} END {for(w in y) print w, y[w]}'
wrk -t8 -c100 -d30s "http://www.baidu.com"
netstat -an | awk '/^tcp/ {++y[$NF]} END {for(w in y) print w, y[w]}'
为了看一下是不是由于这个问题导致的,我们先执行netstat查看TIME_WAIT状态的socket资源有多少,然后用wrk长连接来压测nginx,最后再次用netstat来查看TIME_WAIT状态的socket资源有多少。如果压测以后,TIME_WAIT状态大幅飙升,这就意味着有很多socket资源被短连接浪费了。
upstream yinghao {
server localhost:9595;
keepalive 100;
}
server {
listen 80;
listen [::]:80;
server_name yinghao.fishedee.com;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://yinghao;
}
}
解决办法是在nginx的sites-enabled下建立upstream的连接池,然后在proxy_pass来指定连接池来获取socket资源,注意,为了让长连接生效,需要配置http_version为1.1,以及配置正确的Connection的Header。
db.SetMaxIdleConns(config.MaxIdleConnection)
当然,长连接不仅仅是在nginx与cgi之间,也有可能是cgi与数据库之间。所以cgi与数据库的连接也最好使用长连接的方式,在golang写的cgi中,配置好MaxIdleConnection为一个非0值就可以了,例如是100。
import (
"github.com/garyburd/redigo/redis"
)
func NewRedisPool(redisURL string) *redis.Pool {
return &redis.Pool{
MaxIdle: redisMaxIdle,
Dial: func() (redis.Conn, error) {
c, err := redis.DialURL(redisURL)
if err != nil {
return nil, fmt.Errorf("redis connection error: %s", err)
}
//验证redis密码
if _, authErr := c.Do("AUTH", RedisPassword); authErr != nil {
return nil, fmt.Errorf("redis auth password error: %s", authErr)
}
return c, err
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
if err != nil {
return fmt.Errorf("ping redis error: %s", err)
}
return nil
},
}
}
同样的,在golang连接redis的时候,也要使用redis连接池的方式,配置好MaxIdle和IdleTimeout即可。
type Transport struct {
// DisableKeepAlives, if true, disables HTTP keep-alives and
// will only use the connection to the server for a single
// HTTP request.
//
// This is unrelated to the similarly named TCP keep-alives.
DisableKeepAlives bool
// MaxIdleConns controls the maximum number of idle (keep-alive)
// connections across all hosts. Zero means no limit.
MaxIdleConns int // Go 1.7
// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
// (keep-alive) connections to keep per-host. If zero,
// DefaultMaxIdleConnsPerHost is used.
MaxIdleConnsPerHost int
// MaxConnsPerHost optionally limits the total number of
// connections per host, including connections in the dialing,
// active, and idle states. On limit violation, dials will block.
//
// Zero means no limit.
//
// For HTTP/2, this currently only controls the number of new
// connections being created at a time, instead of the total
// number. In practice, hosts using HTTP/2 only have about one
// idle connection, though.
MaxConnsPerHost int // Go 1.11
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration // Go 1.7
}
同样地,如果golang的cgi与其他的服务器连接,也需要配置好MaxIdleConns,MaxIdleConnsPerHost,MaxConnsPerHost等参数,以最大限度地复用原有的链接,既不会不断频繁地创建连接,也不会因为并发数太多而导致等待。
7 HTTPS
7.1 公认证书
upstream backendserver {
server localhost:9598;
keepalive 8;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /home/folder/abc.com.cer;
ssl_certificate_key /home/folder/abc.com.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
server_name abc.com;
location / {
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_pass http://backendserver;
}
}
server {
listen 80;
listen [::]:80;
server_name abc.com;
#rewrite ^(.*) https://abc.com$1 permanent;
return 301 https://$host$request_uri;
}
对于公认的证书,可以使用以上的方法来配置nginx。注意将ssl on的配置去除,新版本的nginx不需要这个配置。
注意,证书更改后需要停止服务器重启才能生效,不能只是reload配置。
7.2 自签证书
可以看这里。自签证书主要是为了本地的测试开发用,方便,而且可以用任意的域名。
# 生成RSA密匙对
openssl genrsa -des3 -out domain.key 1024
# 提取出私有钥匙中没密码包含的版本
openssl rsa -in domain.key -out domain_nopass.key
# 生成SSL证书请求
openssl req -new -key domain.key -out domain.csr
# 生成SSL证书链
openssl x509 -req -days 365 -in domain.csr -signkey domain.key -out domain.crt
# 查看SSL证书的有效期
openssl x509 -in domain.cer -noout -dates
一个SSL证书,需要包括几个步骤:
- 生成RSA钥匙对,包括公有钥匙与私有钥匙(默认有一层密码做外部加密),.key格式
- 根据RSA钥匙对生成服务器用的无密码私有钥匙,.key格式
- 根据RSA钥匙对生成SSL证书请求,.csr格式,包括公有钥匙,加密算法,请求者签名。这个文件一般是申请者提交给CA的
- 根据RSA钥匙对生成SSL证书链,.crt格式,服务器可用的公有整数。这个文件一般是CA颁发给申请者的。
server{
listen 443 ssl;
server_name testclient.com;
ssl_certificate /usr/local/etc/nginx/conf/server.crt;
ssl_certificate_key /usr/local/etc/nginx/conf/server.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_pass http://localhost:9596;
}
}
nginx配置和普通的公认证书一样。
8 WebSocket
server {
listen 80;
server_name push.abc.com;
location / {
proxy_pass http://localhost:8080;
# WebScoket Support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
对于代理后端的WebSocket应用,我们需要以上这样的配置。
9 FAQ
9.1 Permission denied
2021/06/05 20:38:18 [crit] 27810#0: *28 stat() "/var/www/abc/" failed (13: Permission denied), client: 14.157.94.16, server: _, request: "GET / HTTP/1.1", host: "47.118.30.60"
当nginx的错误日志报出Permission denied错误时,你先试试那个server的问题。然后一路用sudo -u user stat xxxx来测试是哪个目录出的问题。
另外,目录的问题出错了,一般是因为缺少x权限导致的。解决方法是先将nginx的执行用户(一般为nginx用户)加入到所在目录的用户组,然后对所有目录的全路径加入x权限。例如,对/var加x权限,对/var/www/加x权限,对/var/www/abc/加x权限。最后还有一种隐蔽的问题,当/var/www/abc/是一个软链,实际指向的是/home/abc/kk目录时,你就要记得对/home加入x权限,对/home/abc加入x权限,对/home/abc/kk目录加入x权限。
10 总结
无
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!