服务器迁移手记之nginx配置


最近终于有了点时间了,很是把之前bug多多的博客服务器重新鼓捣了一番,现在工作暂时告一段落,把服务器配置稍微做一下记录。相比之前的服务器还是做了不少与时俱进的调整的。安全和性能和原先相比都有所提升,同时网页本身也与现在版本更新中流行一样:更换了更加高大上的界面(笑)。

回到服务器配置上,安全和性能上都分别提升了什么呢?详细的内容后面会分开了细说,这里只讲亮点:IPv6支持,全站https加密并且带有完全前向安全性,以及HTTP/2支持。

这里只介绍nginx服务器的配置,关于如何配置环境,或者安装Wordpress,网上已经有相当多的资料,就不赘述了。我使用的环境是64位centOS6.7与LEMP(Linux+Nginx+MySQL+php)。


安全

隐藏不必要的信息

nginx默认在响应头中会透露自己的版本号,比如server:nginx 1.x.x,但是有部分的安全漏洞只在特定版本中出现。所以隐藏版本号可以略微提升安全性。要做到这点很简单,在nginx.conf中添加这一行即可:

server_tokens   off;

这样返回的header中将不再包括版本号,而只是server:nginx

同理,一些web语言或者框架也会泄露服务器信息,比方说在我现在这个环境下,php默认输出的x-powered-by就会泄露php以及它的版本号。不过也可以相当轻易的隐藏这一信息。在php.ini中添加如下设定即可

expose_php = off

这样子不但php的版本号会隐藏,服务器返回的header中连x-powered-by都会完全消失。

设置安全的响应头

一个安全的响应头远远不止不泄露版本信息那么简单。我添加了这样的头部:

add_header  Strict-Transport-Security  "max-age=31536000; includeSubDomains;";
add_header  X-Frame-Options  SAMEORIGIN;
add_header  X-Content-Type-Options  nosniff;
add_header  Public-Key-Pins 'pin-sha256="e2yT9ofIHn8Tj/dZhAWAPGbQieuno6irQMb0D83J/rw="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000; includeSubDomains';
add_header  Cache-Control no-cache;
add_header  X-XSS-Protection 1;

第一个头部,HTTP Strict Tranport Security, 简称HSTS。这个头部的作用是告诉浏览器,在指定的时间(max-age)内,始终通过https访问服务器上的网站。哪怕用户手动输入http请求,或者点击了访问本站的http链接,浏览器都会在本地替换为https请求。而includeSubDomains则是为了多域名证书准备的,如果你的证书不支持多域名,不加这个选项也可以。
事实上,不使用HSTS也可以达到类似的效果,在以前,我们会配置类似下面的配置文件,把http请求通过301重定向的方式转移到https上:

server_name gnattu.ml;
return 301 https://$server_name$request_uri;

但是301重定向毕竟是301重定向,它需要向服务器发送请求,而HSTS的转换是在浏览器本地进行的,换句话说,HSTS效率更高。而且由于是本地进行替换,也避免了不那么安全的80端口被劫持的风险。有关HSTS的更多信息可以访问这里

第二个头部,x-frame-options,用来指定此网页是否允许被 iframe 嵌套,有三个选项:

  • DENY:不允许被任何页面嵌入;
  • SAMEORIGIN:不允许被本域以外的页面嵌入;
  • ALLOW-FROM uri:不允许被指定的域名以外的页面嵌入(Chrome现阶段不支持);

有什么好处呢?其他人可以劫持你的dns,然后用ifame嵌套你的网页,再然后植入一点鼠标点击或者键盘记录插件。由于网页的外观完全一致,所以访问者很难发现其中的猫腻,而这个选项就很好的解决了这个问题。使用SAMEORIGIN的原因是因为wordpress的后台有很多的iframe,比如主题和插件浏览器,deny了就根本看不到了。更多信息访问这里

第三个头部,X-Content-Type-Options互联网上的资源有各种类型,通常浏览器会根据响应头的Content-Type字段来分辨它们的类型。例如:”text/html”代表html文档,”image/png”是PNG图片,”text/css”是CSS样式文档。然而,有些资源的Content-Type是错的或者未定义。这时,某些浏览器会启用MIME-sniffing来猜测该资源的类型,解析内容并执行。但是这个过程并不安全,因为攻击者甚至可以让原本应该解析为图片的请求被解析为JavaScript。而这个头部的作用就是禁止这样的资源类型猜测。

第四个头部, 就是使用率极低却能比较有效防范中间人攻击的HTTP Public Key Pining(HPKP)技术。在目前的网络环境中,任何一家受信任的 CA 都可以签发任意网站的站点证书,这些证书在浏览器看来,都是合法的,哪怕这些证书是由”伪造或不正当手段获得的”。而HPKP 技术给予我们主动选择信任 CA 的权利。它的工作原理是通过响应头或者 标签告诉浏览器当前网站的证书指纹,以及过期时间等其它信息。未来一段时间内,浏览器再次访问这个网站必须验证证书链中的证书指纹,如果跟之前指定的值不匹配,即便证书本身是合法的,也必须断开连接。这样可以杜绝由受信任的CA向第三方非法签发你的网站证书的风险。

那么问题来了,pin-sha256该如何设定呢?证书链一般有三个部分:根证书,中间证书,站点证书。中间证书有可能不止一个。但是你要用哪个做指纹都没有问题。用站点证书做的指纹安全性最好,因为站点证书的指纹永远不会重复。但是缺点也很明显,一旦不得不更换证书,老用户就面临无法访问的风险,除非你有有效的备用证书。用根证书生成指纹安全性最差,因为每个根证书都对应很多中间证书,攻击者只要攻破其中一个,就可以签出能被 HPKP 策略信任的站点证书。中间证书确实是安全性和易用性的综合,所以比较推荐使用中间证书来生成指纹。但是指纹至少要有两个,其中一个是备用证书,用来在不得不更换证书的时候作为正常访问的保证,例如原来的 CA 突然倒闭,或者被黑了导致他们签发的证书不再被信任。备用证书的指纹可以使用其他CA的中间证书来生成,即使你自己并没有在使用这个CA的证书也没有关系。那么如何获取证书的指纹呢?

两个方法:用OpenSSL,或者用SSL Labs提供的服务

OpenSSL法:终端输入如下命令

openssl dgst -sha256 -binary public.key | openssl enc -base64

就会返回你的证书指纹。

SSL Labs法:直接访问网址,测试你的网站,然后找到证书中的SHA-256字段,便是你的证书指纹。

max-ageincludeSubDomains的用法和含义与HSTS相同。

第五个头部,容易产生误解,并不是禁止缓存,而是请求数据时都要向服务器验证缓存有效性。可以抵御篡改缓存的一些攻击,同时也可以让访问者更快速的看到更新后的网页。

第六个头部,属于一个现代浏览器内置功能,要求浏览器启用XSS(跨站脚本攻击)保护。有关XSS的进一步内容可以访问这里了解。

https配置

前面铺垫了那么多,其实这个才是真正的重头大戏。配置了正确的证书并且启用了https加密的站点在数据传输的过程中就不会被第三方解密或者篡改。当然,如果配置不当,依然会有不那么安全的加密方式被启用,这样https的最大价值就并没有被发挥出来。但是https能保证的仅仅只是传输时的安全性,并不保证在连接建立前的时候连接会被篡改(DNS污染)或者阻断(封锁443端口)这样的恶意行为。
我的配置是这样的:

ssl_certificate  /etc/nginx/ssl/server.crt;
ssl_certificate_key  /etc/nginx/ssl/server.key;
ssl_dhparam  /etc/nginx/ssl/dhparams.pem;
ssl_ciphers  ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:DES-CBC3-SHA;
ssl_prefer_server_ciphers  on;
ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;

前两项就是定义证书里的公钥和私钥的路径,这个不用多讲人人都会。第三项便是Diffle Hellman密钥交换的所需的“Diffie Hellman Group”。这种密钥交换方式可以确保即使处在有人监听的环境下密钥也不会泄露。如何配置参考这个网站

后面就是对于整个网站安全性至关重要的ssl_ciphers设定。得当的设置可以给你的网站提供完全前向安全性(Perfect Forward secrecy)。而设置可以直接参考我的(对于所有浏览器具有完全前向安全性,并且与http/2完全兼容(http/2禁用了几百种cipher,即使有部分曾经被认为是安全的)。也可以参考这个网站,不过这个网站的默认安全级别非常高,一些低版本客户端并不支持,例如 IE9-、Android2.2- 和 Java6-。如果需要支持这些老旧的客户端,需要点一下网站上的「Yes, give me a ciphersuite that works with legacy / old software」链接。

ssl_prefer_server_ciphers on;可以确保在SSL握手时优先采用服务器的cipher以及cipher的顺序,排在前面的cipher优先级会比排在后面的cipher高。

最后一项是设定SSL加密协议,现阶段除了这三个版本以外没有必要添加其他的版本,除非你真的十分在意WindowsXP上的ie6用户,他们只能使用老旧的SSLv3协议,而这个协议早就被证实是不安全的了。不包括SSLv3会导致ie6访问网站时服务器直接关闭连接。不过考虑到ie6早已经被大多数网站列入黑名单,所以这不是特别大的问题。

实施了这样的配置后,我的站点在SSL Lab的测试中获得了A+的安全等级详细报告(由于同时支持ipv4与ipv6,所以会有两个地址,随便点哪个都没有问题)
a+

–安全篇完–


性能

优化TCP

sendfile        on;
tcp_nopush     on;
tcp_nodelay     on;

sendfile on;用来开启linux的内核空间文件发送,而不需要进行额外的读写操作,减少开销。
tcp_nopush on;在启用sendfile后才生效,效果是数据包会累计到一定大小之后才会发送,提高网络效率。
tcp_nodelay on;禁用Nagle算法,尽快发送数据。和nopush配合使用的效果为先填满包,再尽快发送。
参考:NGINX OPTIMIZATION: UNDERSTANDING SENDFILE, TCP_NODELAY AND TCP_NOPUSH

压缩纯文本

现在压缩纯文本普遍使用的是Gzip技术,可以将文本文件的体积压缩到原来的三分之一到四分之一。

    gzip  on;
    gzip_vary          on;

    gzip_comp_level    6;
    gzip_buffers       16 8k;

    gzip_min_length    1000;
    gzip_proxied       any;
    gzip_disable       "msie6";

    gzip_http_version  1.0;

    gzip_types         text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

这段设置还是挺好理解的,有关gzip的资料也很多。这里说一下gzip_vary的作用。某些有 BUG 的缓存服务器,会忽略响应头中的 Content-Encoding,从而可能给不支持压缩的客户端返回缓存的压缩版本,或者被请求过未经压缩的版本后只缓存了未压缩的版本,导致支持压缩的客户端也只能收到未经压缩的版本。而这项设置的用途是在header中增加Vary: Accept-Encoding从而使缓存服务器分别缓存不同的版本。

另外,msie6的有效对象为IE4-6,而在这些浏览器上关闭压缩的原因是因为较低版本的IE有bug,可能导致客户端cache完全失效。可以参见这篇文章

启用http/2

http/2的好处和性能提升不需要我在这里细说,随便找一个靠谱的搜索引擎,就能得到无数令人眼前一亮的信息。或者你也可以通过Akamai的这个站点亲自体验一下在极端环境下的http2的优越性。

开启的配置相当简单,在serverlisten字段中添加http2即可

server {
    listen 443 http2 ssl reuseport default_server;
......
}

注意你的nginx版本需要是1.9.x或者更高同时在编译时需要增加--with-http_v2_module--with-http_ssl_module才能够启用http2支持。可以在终端输入nginx -V来查看你的nginx的版本和编译器信息。

优化https

https天生就比http慢,这是毫无疑问的。而配置不当的https服务器只会慢上加慢。优化https可以让服务器不要那么慢,至少不要显著影响响应速度。

    ssl_session_cache        shared:SSL:10m;
    ssl_session_timeout      60m;

    ssl_session_tickets      on;

    ssl_stapling             on;

前三行设置都是用来保存TLS会话的。 把辛辛苦苦获取证书、校验证书、TLS 握手的结果保存下来供后续使用。不同的是cache保存在服务器上,ticket保存在客户端。大部分浏览器都支持cache的方式,ticket就没那么普及。不过都打开没有什么坏处。

第四行是打开OCSP stapling。很多浏览器会在线验证证书有效性,验证完成之前整个连接都会被挂起,但是验证证书的过程不会很快。这个功能就是在服务器返回数据的时候自动获取OCSP并返回一个有关证书有效性的信息。一般而言服务器的网络环境会更好,也因此OCSP查询会更快。详细说明看这个网站

Leave a Reply

Your email address will not be published. Required fields are marked *