在 SSL Lab 中达到 A+评分的 Nginx 配置

前情

曾经在 这里 讲述了如何用 acme.sh 申请 RSA 证书并开启 Https

本文参考了 How to get an ‘A+’ in SSL Labs Server Test with NginX configuration 所以也可以认为是一个大致的汉化

但是原文发布于 2018/10/8 某些地方可能已经过时

ZjbGYn.webp

可以看到 SSL Lab 的评判有四方面 这里一个个说

Certificate

This section is fairly easy to get 100% on. Use a well known or trusted CA(Certificate Authority) and make sure your certificate and chain are in the correct order. The most important certificate configuration setting is the fingerprint hashing algorithm. Ensure that you have created a SHA256-signed certificate and not SHA1. Until recently, most certificates were signed using SHA1. However, its becoming dangerously insecure as computing power advances. In its place, new certificates should be signed using SHA256.

简单来说就是只需要有一个受信任的 CA 签发、加密算法不是 SHA1 的证书即可

所以这里只需要使用 acme.sh 生成证书就可以达到 100%

Protocol Support

Disable SSLv3
By default Nginx still enables SSLv3, which has been vulnerable to the POODLE attack since October 2014. SSL Labs rightly limits your server’s SSL score to C if SSLv3 is enabled, so this is the first thing to change.
Using only TLSv1.2
If you want to score 100% in this section, you will have to use only TLSv1.2 as your supported SSL protocols. However, most of the older clients will not able to load your site.
Using TLSv1.1&v1.2
Using TLSv1.1 & v1.2 will get you a score of 95% in this section, but some of the older clients will still not be able to load your site.
Use TLS1.0~1.2
Although using this suite will give you a score of 90%, this would be the best compatible option suggested by me, and you will still be able to score an overall ‘A+’. Don’t use protocols older than TLS1.0 since it’s been deprecated.

这里原作者给出了几个解决方案

但是首先 禁用 SSLv3

默认情况下 Nginx 还是会启用 SSLv3 但是此协议被 证实是有漏洞

仅启用 TLS v1.2

这样可以达到 100% 但是某些老的客户端会有兼容性问题

同时启用 TLSv1.1 和 TLS v1.2

达到 95% 同时保持某些老客户端的兼容

同时启用 TLS v1.0 TLSv1.1 和 TLS v1.2

这样会有 90% 同时保持最大的兼容性

我的建议

但其实 TLS v1.0 和 TLSv1.1 现已被 SSL Lab 打上了黄色 所以我建议同时启用 TLS v1.2 和 TLS v1.3

如何开启 TLS v1.3? 在 CentOS 7 上编译 Nginx 使其启用 TLS v1.3

Nginx 配置如下

1
ssl_protocols TLSv1.2 TLSv1.3;

Key Exchange

To increase the Key Exchange score, we need to increase the size of the key used in the DH exchange. Nginx uses OpenSSL’s default 1024 bit key size as an input to the DH key exchange, resulting in a 1024 bit key. Ideally, we should be using a key number smaller than our SSL certificate which is commonly 2048 or even 4096 bits.

要提高Key Exchange的分数 就要提高用于 DH 交换的密钥的位数 Nginx 默认使用 1024 位的密钥来作 DH 交换

所以要提高分数 就需要将其提升至 2048 位甚至是 4096 位

执行以下代码来生成

1
openssl dhparam -out /etc/nginx/dhparam.pem 4096

生成过程可能会长达几分钟 所以你想的话也可以降低位数到 2048 然后需要配置 Nginx

1
ssl_dhparam /etc/nginx/dhparam.pem;

Cipher Strength

这里原文写了一大堆算法 对于想了解的可以直接去看原文

我这里只给出 Nginx 配置

1
2
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE+3DES:RSA+3DES;

HTTP Strict Transport Security

经过以上操作 你会在 SSL Lab 里达到 A 的评级了 想要更进一步 就需要开启 HSTS

By enabling HSTS, the browsers will not allow users to attempt any connection using HTTP and always enforce the use of HTTPS. This means that, after a first visit from a client to the server, subsequent visits to any HTTP pages will be converted by the browser to HTTPS before any request is made. This saves us some round trip time and increases security, especially against many SSL stripping attacks. HSTS also prevents the constant need for you to redirect clients from HTTP to HTTPS and many other configuration errors that are easy to make.

简单来说 加入了这个 Header 访问了一次该域名 以后浏览器就会强制对此域名的所有连接都进行 HTTPS 访问

在 Nginx 配置里加入

1
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

max-age=31536000
The 31536000 seconds are roughly around a year. So, the browser will remember for a year that it should always visit your website through HTTPS. If, for any reason, you want to stop supporting HTTPS for your website, you will have a problem with browsers that have already received this header. Would you redirect any HTTPS traffic back to HTTP, such browsers would redirect again to HTTPS resulting in an infinite loop. Therefore, if you are just playing around with SSL and aren’t sure you will keep it for your website, don’t enable HSTS for that website.

31536000 秒即大概一年的时间 浏览器会记住这么长时间

includeSubDomains
The includeSubDomains clause tells the browser to also enable HSTS for sub-domains. So remove this, if not all your sub-domains are ready for HTTPS.

看英文都很好理解啦 包括所有子域名

preload
With the first HTTP request, a browser is still potentially vulnerable. Using “preload” clause, we tell browsers not to make even one HTTP request to the server. This is done by submitting your website to the HSTS preload list. By submitting your website to the HSTS preload list, you tell browsers to never visit your website through HTTP. Addition to the preload list will have semi-permanent consequence and is irreversible, so only use this option once you’re comfortably set up running HTTPS with no problems.

对于初次的连接 浏览器还是会使用 HTTP 加入这个参数并且提交你的域名至 HSTS preload list 即可

注意:HSTS preload list 是不可逆转且半永久的 所以提前之前请慎重考虑

always
The “always” after the header contents tell NginX to provide this header with every possible response code, instead of only for the “200, 201, 204, 206, 301, 302, 303, 304, or 307” status codes.

即把此头部应用在所有的响应内容中 不止 200 201 204 等

Improve Performance

Even though we already achieved the A+ rating, there is some more we can still improve. These changes won’t affect your scoring but are necessary to improve performance.

这时 SSL Lab 里已经能评上 A+了 但还有些能提升性能但不降分的操作能做

OCSP Stapling

With each certificate a browser receives, it performs a request to a Certificate Authority to check whether the certificate has expired. This causes extra waiting time and it decreases security. Instead of the browser checking the status of the provided certificate, the server can be made to check this on a regular interval using the Online Certificate Status Protocol (OCSP). The OCSP responses are signed by your Certification Authority, so browsers will be able to trust them, even if they come directly from your server.

简单来说 OCSP 用于检测证书是否过期 但这就造成了额外的等待时间

OCSP Stapling 则解决了上述的问题 服务端主动通过 OCSP 获得证书状态 并随着证书一起发送给客户端 这样客户端就可以跳过自己去验证的步骤 以提高 SSL 握手的效率

1
2
3
4
ssl_stapling             on;
ssl_stapling_verify on;
resolver 119.29.29.29 114.114.114.114 valid=300s;
resolver_timeout 10s;

SSL Session Caching (Session Resumption)

To minimise the impact of the SSL overheads on your server, you can reduce the number of handshakes that a client needs to perform. The handshake is the most expensive part of serving content over SSL. With session resumption, the server specifies some information to the client during the initial TLS handshake. In a second connection, the client can then use this information to skip the entire TLS handshake and immediately have a secure connection setup. We can enable the SSL cache to remove the need for a handshake on subsequent or parallel connections.

简而言之 SSL Session 也要缓存 这样就会减少握手次数 从而提升性能

1
2
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

后记&相关链接

相关链接

How to get an ‘A+’ in SSL Labs Server Test with NginX configuration

Nginx SSL 安全配置最佳实践

看完上面两个会更容易理解我给出的配置

自用配置

分享我自用的一套通用配置

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
server {
listen 443 http2 fastopen=3 reuseport ssl default;
server_tokens off;
server_name *.burgertown.tk;

ssl_certificate "/path/to/rsa/fc";
ssl_certificate_key "/path/to/rsa/key";

ssl_certificate "/path/to/ecc/fc";
ssl_certificate_key "/path/to/ecc/key";

ssl_dhparam "/root/ssl/dhparam";
ssl_session_cache shared:SSL:10m;
ssl_session_tickets on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 119.29.29.29 114.114.114.114 valid=300s;
resolver_timeout 10s;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE+3DES:RSA+3DES;
}

add_header X-Frame-Options DENY;
add_header Referrer-Policy "no-referrer";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Access-Control-Allow-Origin *;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
add_header Content-Security-Policy "upgrade-insecure-requests";
add_header Cache-Control "max-age=36000";