使用 Nginx 在 Ghost 博客上进行 SSL 重定向和身份验证

SSL redirection and authentication on Ghost blog with Nginx

我在 DigitalOcean 上有一个关于 Ghost 的博客。同样由Nginx服务,配置文件如下:

server {
    listen 443 ssl;
    server_name www.example.com;
    return 301 $scheme://example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    include snippets/ssl-params.conf;

    location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://127.0.0.1:2825;
    }
}

证书是使用 Let's Encrypt 生成的,适用于带 www 和不带 www 的域。 在 Ghost config.js 文件中,URL 中写入了采用 SSL 的规则:https://example.com/ 问题是,当我进入我的博客时,我的域没有 wwww,正确登录并使用 SSL,但是当我尝试使用 https://www.example.com 登录时,我收到 SSL 证书身份验证错误。 我真的不明白这里可能有什么问题。 我需要在使用 www 进入我的域时重定向到该域,但没有 www。这个操作我之前在其他应用节点做过,没有问题,配置代码同上。

您的第一个服务器块没有证书定义。因此,您可能希望在该 server 块中复制 ssl_certificatessl_certificate_key 语句。

如果这些是唯一的 SSL server 块,并且因为您有两个域的通用证书文件,您可以将 ssl_certificatessl_certificate_key 语句移动到 http 级别并允许它们被两个 server 块继承。

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

server {
    listen 443 ssl;
    server_name www.example.com;
    return 301 $scheme://example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    ...
}

有关更多信息,请参阅 this document

(OP 确实要求在另一个答案的评论中查看我的 nodejs 代理代码。)

在我们进入 nodejs 代理之前,我们应该检查几件事:

证书

您的证书对有 www 和没有 www 都有效吗?

如果您的证书仅在没有 www 的情况下有效(或相反),那么代理(nginx 或我的或任何其他)设置都无济于事。因为 SSL/TLS 握手发生在重定向之前。在任何重定向发生之前,浏览器会立即投诉。最终用户在浏览器中看到证书错误。

我的两者都有效,您可以查看我的博客 https://johnsiu.com and https://www.johnsiu.com。重定向发生时没有任何证书投诉。

DNS

无论有无 www,您的 DNS 设置是否正确?它们应该指向相同的 IP 地址。

我想知道这是否与 ERR_SOCKET_NOT_CONNECTED 错误有关。

Nodejs 代理

托管在 ghithub:https://github.com/J-Siu/ghost-https-nodejs-proxy

刚刚微调我的nodejs代理,最新版本如下:

// HTTPS

const fs = require('fs');
const url = require('url');
const http = require('http');
const https = require('spdy'); // http2 support
const proxy = require('http-proxy').createProxyServer();
const compression = require('compression');
const ex = require('express')();
const fqdn = '<your domain name here>';

// Fill in your certificate files
const serverKey = '<KEY file>';
const serverCrt = '<CRT file>';
//const serverCa='<CA file>';

const httpsOptions = {
 key: fs.readFileSync(serverKey),
 cert: fs.readFileSync(serverCrt),
 // ca: fs.readFileSync(serverCa),
 ciphers: [
  "ECDHE-RSA-AES256-SHA384",
  "DHE-RSA-AES256-SHA384",
  "ECDHE-RSA-AES256-SHA256",
  "DHE-RSA-AES256-SHA256",
  "ECDHE-RSA-AES128-SHA256",
  "DHE-RSA-AES128-SHA256",
  "HIGH",
  "!aNULL",
  "!eNULL",
  "!EXPORT",
  "!DES",
  "!RC4",
  "!MD5",
  "!PSK",
  "!SRP",
  "!CAMELLIA"
 ].join(':'),
};

// Ghost Proxy configuration

proxy.on('proxyReq', function (proxyReq, req, res, options) {

 // Ngix: proxy_set_header Host $http_host;
 proxyReq.setHeader('Host', req.headers.host);

 // Ngix: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxyReq.setHeader('X-Forwarded-For', req.connection.remoteAddress);

 // Ngix: proxy_set_header X-Forwarded-Proto $scheme;
 proxyReq.setHeader('X-Forwarded-Proto', 'https');

});

ex
 .use(compression())
 .use((req, res) => {
  if (req.header.host == fqdn) {
   proxy.web(req, res, { target: 'http://localhost:2368' });
  } else {
   res.writeHead(301, { 'location': 'https://' + fqdn + req.url });
   res.end();
  }
 })

// HTTPS Server

https.createServer(httpsOptions, ex).listen(443, '0.0.0.0');

// HTTP Redirect to HTTPS

http.createServer(function (req, res) {
 res.writeHead(301, { 'location': 'https://' + fqdn + req.url });
 res.end();
}).listen(80, '0.0.0.0');

它能够:

  1. 将任何 http 流量重定向到 https(我在这里没有检查 url)
  2. 将任何 https 流量重定向到正确的 fqdn(任何与我的 fqdn 不匹配的请求都会被重定向)
  3. ab 测试显示 ~16 请求/秒

我为我的单机单站点设置创建了这个,特别是为了在支持 HTTPS/HTTP2 的同时避免任何 apache/nginx 包。这可能无法完全满足您的需求,因为您正在做多个站点。

这是我的 Nginx 配置文件,我在其中使用 Ghost 为博客提供服务。该证书是使用 Let's Encrypt 生成的,并且适用于两个域。 缺点是在执行 301 重定向时出现,因为它不能正常工作,因为带有 www 的域告诉我证书无效,但是如果我尝试反向重定向,从 not www 到 www,它显示我同样的消息,但对于没有 www 的域:

server {
    listen 80;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

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

    ssl on;
    ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;

    include snippets/ssl-params.conf;

    location / {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-NginX-Proxy true;
            proxy_pass http://localhost:9002/;
            proxy_redirect off;
    }
}

目前,我正在使用Digital Ocean的一个droplet,我在其中配置了3个字段A,一个值为*的字段A指向虚拟机的IP,一个字段@也寻址IP服务器的地址,最后是一个值为 www 的字段 A,指向我的服务器的 IP。

周末的大部分时间我都在研究解决这种不便的方法,并设法找到了解决方法。 Let's Encrypt 和 Nginx 中都没有错误,都指的是我的错误配置;但是,我不能停止认为这是一个奇怪的事情,而且我的解决方案可以改进甚至不用多说,这是我的 Nginx 配置文件:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    include snippets/ssl-params.conf;

    if ($http_host = www.example.com) {
            return 301 https://example.com$request_uri;
    }

    location / {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-NginX-Proxy true;
            proxy_pass http://localhost:8080/;
            proxy_ssl_session_reuse off;
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;
            proxy_redirect off;
    }
}

为了避免拖拽错误,完全删除我的虚拟机并从头开始创建它,同时安装最新版本的Debian Nginx,即1.10.3,这使我能够使用http2,这也是一个改进。

一种解决方案是为 www 子域请求并安装第二个 SSL 证书。

步骤:

  1. 打开你的 nginx HTTP 配置文件:sudo nano /etc/nginx/sites-available/example.com.conf。复制现有的服务器块。保持第一个块不变,这样它将继续为 example.com 工作。在第二个块中,在 server_name 行中包含 www:server_name www.example.com;。保存并关闭文件。

  2. 打开您的 nginx SSL 配置文件:sudo nano /etc/nginx/sites-available/example.com-ssl.conf。保持第一个块不变,这样它将继续为 example.com 工作。在第二个块中,在 server_name 行中包含 www,这将为您提供 server_name www.example.com;。然后删除以 ssl_certificatessl_certificate_key 开头的行,因为我们不需要这些行,因为我们将为 www 子域请求新的 SSL 证书。保存并关闭文件。

  3. 为 www 子域申请 Let's Encrypt SSL 证书: sudo /etc/letsencrypt/acme.sh --issue --home /etc/letsencrypt --domain www.example.com -- webroot /var/www/ghost/system/nginx-root --reloadcmd "nginx -s reload" --accountemail exampleEmailAddress@example.com

  4. 重新打开您的 nginx SSL 配置文件:sudo nano /etc/nginx/sites-available/example.com-ssl.conf。 Return 到您在步骤 2 中设置的 www.example.com 服务器块并重新添加您的 ssl_certificate 行:ssl_certificate /etc/letsencrypt/www.example.com/fullchain.cer;ssl_certificate_key /etc/letsencrypt/www.example.com/www.example.com.key;。保存并关闭文件。

  5. sudo nginx -s reload重新加载nginx,错误应该消失了。