做Web开发,Nginx是绕不开的工具。它是目前世界上最流行的Web服务器之一,据统计,全球超过百分之三十的网站在使用Nginx。我从入行开始就用Nginx,从最基础的静态文件服务,到反向代理,到负载均衡,到缓存加速,Nginx陪伴我经历了无数个项目。

为什么选择Nginx而不是Apache?主要有几个原因:第一,性能好,Nginx采用异步非阻塞的事件驱动模型,高并发下表现远超Apache的多进程模型;第二,资源占用少,Nginx跑在1核1G的小服务器上也能轻松应对几千并发;第三,配置灵活,Nginx的配置文件简洁直观,反向代理、负载均衡、缓存、重写等功能配置起来都很方便;第四,社区活跃,Nginx的模块生态丰富,遇到问题很容易找到解决方案。

今天就来分享我在生产环境中使用Nginx的实战经验,从最基础的反向代理到负载均衡,从静态文件缓存到Gzip压缩,从安全加固到性能优化。

基础配置:静态文件服务

最基础的Nginx配置是静态文件服务。比如把一个静态网站的文件放在/var/www/html目录下,Nginx配置如下:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

这个配置很简单:监听80端口,域名是example.com,网站根目录是/var/www/html,默认首页是index.html。location /匹配所有请求,try_files尝试查找请求的文件,如果找不到返回404。

但生产环境不能这么简单,还需要加一些优化配置。比如开启Gzip压缩、设置静态文件缓存、添加安全头、配置404页面等。

反向代理:把请求转发给后端

Nginx最常用的功能之一是反向代理。比如后端跑了一个PHP-FPM或Node.js服务,监听在127.0.0.1:9000,Nginx监听80端口接收请求,然后转发给后端服务处理。

反向代理的配置如下:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:9000;
        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;
    }
}

proxypass指定后端服务的地址,proxyset_header设置转发给后端的请求头。这里有几个重要的头:

  • Host:原始请求的域名,后端服务需要根据这个域名来判断是哪个网站(尤其是多站点部署时)
  • X-Real-IP:客户端的真实IP,因为后端服务看到的是Nginx的IP,需要通过这个头获取真实客户端IP
  • X-Forwarded-For:代理链上的所有IP,每经过一个代理就追加一个
  • X-Forwarded-Proto:原始请求的协议(http或https),后端需要知道原始协议来生成正确的链接

还有一些超时配置,生产环境建议加上:

proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;

这些配置控制了Nginx和后端通信的超时时间和缓冲区大小,合理设置能避免一些奇怪的问题。

负载均衡:多台后端分摊流量

当单台后端服务器扛不住流量时,就需要负载均衡了。Nginx的负载均衡配置很简单,先定义一个upstream组,包含多台后端服务器,然后proxy_pass指向这个组。

upstream backend {
    server 192.168.1.10:9000 weight=1;
    server 192.168.1.11:9000 weight=1;
    server 192.168.1.12:9000 weight=2 backup;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

upstream里的每一行是一台后端服务器,weight是权重,权重越大分到的请求越多。backup表示备用服务器,只有当其他服务器都不可用时才会启用。

Nginx默认的负载均衡策略是轮询(round-robin),按顺序把请求分给每台服务器。还有其他策略:

  • ip_hash:根据客户端IP的hash结果分配,同一个IP的请求始终分到同一台后端,适合需要保持会话的场景
  • least_conn:把请求分给当前连接数最少的后端,适合请求处理时间差异大的场景
  • fair(第三方模块):根据后端响应时间分配,响应快的分到更多请求

配置iphash很简单,在upstream里加一行iphash就行:

upstream backend {
    ip_hash;
    server 192.168.1.10:9000;
    server 192.168.1.11:9000;
}

还有一个重要的配置是健康检查。Nginx默认会对后端服务器做简单的健康检查——如果某台服务器连接失败或超时达到一定次数,就会暂时把它标记为不可用,不再往它转发请求。可以用maxfails和failtimeout参数调整:

upstream backend {
    server 192.168.1.10:9000 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:9000 max_fails=3 fail_timeout=30s;
}

这表示如果某台服务器在30秒内失败了3次,就把它标记为不可用,30秒后再尝试重新连接。

静态文件缓存和Gzip压缩

静态文件(图片、CSS、JS)不需要经过后端处理,Nginx可以直接返回,并且设置浏览器缓存,减少重复请求。

location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot)$ {
    expires 7d;
    add_header Cache-Control "public, no-transform";
    access_log off;
}

这个配置匹配常见的静态文件扩展名,设置Expires头为7天,让浏览器缓存7天。access_log off表示不记录这些文件的访问日志,减少磁盘IO。

Gzip压缩能大幅减小文本文件的体积,加快传输速度:

gzip on;
gzip_min_length 1k;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_vary on;

gzip on开启压缩,gzipminlength设置最小压缩文件大小(小于1k的文件压缩收益不大),gzipcomplevel设置压缩级别(1-9,级别越高压缩率越高但越耗CPU,一般设5比较均衡),gzip_types指定需要压缩的MIME类型。

HTTPS配置

现在HTTPS已经是标配了,Nginx配置HTTPS也很简单。首先需要申请SSL证书(可以用Let's Encrypt免费证书),然后配置:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
        proxy_pass http://127.0.0.1:9000;
        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 https;
    }
}

# HTTP跳转到HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

sslcertificate和sslcertificatekey指定证书和私钥的路径,sslprotocols指定支持的TLS版本,ssl_ciphers指定加密套件。最后配置一个80端口的server,把HTTP请求301跳转到HTTPS。

安全加固

生产环境的Nginx还需要做一些安全加固:

第一,隐藏Nginx版本号。默认情况下,Nginx的错误页和响应头里会显示版本号,这会给攻击者提供信息。在http块里加一行:

server_tokens off;

第二,限制请求体大小。防止用户上传超大文件把服务器撑爆:

client_max_body_size 10m;

第三,禁止访问敏感文件。比如.git目录、配置文件、备份文件等:

location ~ /\. {
    deny all;
}
location ~ ~$ {
    deny all;
}

第四,配置基本的防CC攻击。可以用limitreqzone限制单个IP的请求频率:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

    server {
        location / {
            limit_req zone=one burst=20 nodelay;
        }
    }
}

这个配置限制每个IP每秒最多10个请求,突发最多20个,超过的请求直接返回503。能有效防止简单的CC攻击。

总结

Nginx是一个非常强大的工具,上面介绍的只是它最常用的功能。从静态文件服务到反向代理,从负载均衡到缓存加速,从HTTPS到安全加固,Nginx几乎能满足Web服务器的所有需求。

学习Nginx没有捷径,多看官方文档、多在实践中摸索、遇到问题多查资料,慢慢就能熟练掌握。希望这篇文章能帮到正在学习Nginx的你。