我想知道 nginx 是否能够在同一端口处理 http 和 https 请求。[*]

这就是我想做的事情。我正在运行一个处理 http 请求的 Web 服务器 (lighttpd) 和一个通过 https 为文档树的特定部分提供服务的 C 程序。这两个进程在同一台服务器上运行。

在防火墙级别,我只能有一个端口将流量转发到此服务器。 因此,我想要做的是在此服务器上设置 nginx,以便它侦听单个端口上的请求,然后:

A)重定向所有 * 请求,以便它们转到 localhost:8080(lighttpd 正在监听的位置)

B) 如果用户请求以 https://myhost.com/app 开头的 URL,它会将该请求发送到 localhost:8008(C 程序)。请注意,在这种情况下,远程浏览器和 nginx 之间的流量必须加密。

您认为这可能吗?如果可能,该怎么做?

我知道如何使用两个不同的端口来实现这一点。我面临的挑战是只使用一个端口来实现这一点(不幸的是,我无法控制这个特定环境中的防火墙配置,因此这是我无法避免的限制)。使用通过 ssh 进行反向端口转发等技术来绕过防火墙也行不通,因为这应该适用于只有 Web 浏览器和 Internet 链接的远程用户。

如果这超出了 nginx 的能力,您知道有其他产品可以满足此要求吗?(到目前为止,我使用 lighttpd 和 pound 进行设置都失败了)。我还希望避免使用 Apache(尽管如果它是唯一可能的选择,我愿意使用它)。

提前致谢,亚历克斯

[*] 需要说明的是,我指的是通过同一端口处理加密非加密的 HTTP 连接。加密是通过 SSL 还是 TLS 完成并不重要。

2

  • HTTPS 请求默认转到端口 443,因此即使您可以使其正常工作(我认为通过一些黑客技术就可以实现),您也需要使用作为链接(或)。


    – 

  • 好的,我是 Server Fault 的新手,我不知道是否可以删除我自己的问题。由于问题似乎没有表述得足够清楚,所以我将提出一个新问题。无论如何,非常感谢所有为这个问题提供有用建议的人。


    – 


9 个回答
9

根据维基百科关于状态代码的文章,当 http 流量发送到 https 端口时,Nginx 有一个自定义错误代码(错误代码 497)

根据,您可以定义一个 URI,该 URI 将显示特定错误。

因此,我们可以创建一个 URI,当出现错误代码 497 时,客户端将被发送到该 URI。

nginx.conf

    #lets assume your IP address is 89.89.89.89 and also that you want nginx to listen on port 7000 and your app is running on port 3000

    server {
        listen 7000 ssl;
     
        ssl_certificate /path/to/ssl_certificate.cer;
        ssl_certificate_key /path/to/ssl_certificate_key.key;
        ssl_client_certificate /path/to/ssl_client_certificate.cer;

        error_page 497 301 =307 https://89.89.89.89:7000$request_uri;

        location / {
            proxy_pass http://89.89.89.89:3000/;

            proxy_pass_header Server;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Protocol $scheme;
        }
    }

但是,如果客户端通过除 GET 之外的任何其他方法发出请求,该请求将转换为 GET。因此,为了保留客户端通过的请求方法;我们使用错误处理重定向,如

这就是我们使用301 =307重定向的原因。

使用此处显示的 nginx.conf 文件,我们可以让 http 和 https 监听同一个端口

对于那些可能正在搜索的人:

ssl on;和添加error_page 497 $request_uri;到您的服务器定义。

2

  • 9
    对 HoverHells 答案的一点改进:将ssl on;和添加error_page 497 =200 $request_uri;到您的服务器定义中。这会将状态代码更改为 200。


    – 

  • 这个服务故障解释了该解决方案为何有效。


    – 

从 1.15.2 开始终于可以正确执行此操作。请参阅信息。

在您的 nginx.conf 中添加如下块(在 http 块之外):

stream {
    upstream http {
        server localhost:8000;
    }

    upstream https {
        server localhost:8001;
    }

    map $ssl_preread_protocol $upstream {
        default https;
        "" http;
    }

    server {
        listen 8080;
        listen [::]:8080;
        proxy_pass $upstream;
        ssl_preread on;
    }
}

然后,您可以创建常规服务器块,但监听以下不同的端口:

server {
    listen 8000;
    listen [::]:8000;
    listen 8001 ssl;
    listen [::]:8001 ssl;
...

这样,流块能够预读并检测它是否是 TLS(在此示例中为端口 8080),然后代理将其本地传递到正确的服务器端口。

1

  • 效果很好;在 ubuntu 18.04 上,必须按照此处的说明安装 nginx 主线包:


    – 

如果您真的想聪明一点,可以使用连接代理来嗅探传入数据流的前几个字节,并根据字节 0 的内容切换连接:如果它是 0x16(SSL/TLS“握手”字节),则将连接传递到 SSL 端,如果它是字母字符,则执行正常 HTTP。

是的,这是可能的,但需要修补 nginx 源代码(HoverHell 有无需修补的解决方案)。Nginx 将此视为错误配置,而不是有效配置。

变量 $ssl_session_id 可用于区分普通连接和 SSL 连接。

针对 nginx-0.7.65 的补丁:

--- src/http/ngx_http_request.c-orig    2011-05-03 15:47:09.000000000 +0200
+++ src/http/ngx_http_request.c 2011-05-03 15:44:01.000000000 +0200
@@ -1545,12 +1545,14 @@

    c = r->connection;

+    /* disable plain http over https port warning
     if (r->plain_http) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "client sent plain HTTP request to HTTPS port");
         ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
         return;
     }
+    */

#if (NGX_HTTP_SSL)

服务器配置:

server {
    listen 80;
    index index.html;

    location / {
        root html;
        if ($ssl_session_id) {
            root html_ssl;
        }
    }

    ssl on;
    ssl_certificate cert.crt;
    ssl_certificate_key cert.key;
}

我认为没有任何东西可以在一个端口上处理两种不同的协议……

我很好奇为什么你只能转发一个端口,但除此之外……这并不理想,但如果我处于你的位置,我会通过 https 提供一切服务。

2

  • 嗨,Wil,谢谢你的回答!我认为通过 https 提供所有服务可能是一种选择,尽管我希望能够按照我描述的方式进行设置。如果前端 Web 服务器(充当反向代理)可以建立正常的 http 会话,然后在不更改端口的情况下将其升级到 https,也许可以做到这一点。我认为 RFC2817(使用 HTTP/1.1 升级到 TLS)中描述了此行为,但我不确定 nginx 或其他 Web 服务器是否知道如何处理该标准。


    – 

  • 我没有时间阅读整个 RFC(并且不确定我是否足够聪明来理解它!)但您是在谈论安全会话建立之前的标准协商还是完全不同的会话?我想我理解得更多一些 – 服务器在两个端口上提供服务,并且是代理引用请求 – 听起来很酷,但我从未见过这样做。也许解决方案可能是在一个端口上创建一个安全站点,并拥有一个简单地继承/导入其他网站的整个虚拟目录?它不能解决所有问题,但可能有效 :S


    – 

您无法通过同一端口同时支持 HTTP 和 HTTPS,因为连接的两端都希望使用某种语言,而它们还不够聪明,无法判断另一端是否在说其他语言。

正如您对 Wil 的回答的评论所建议的那样,您可以使用 TLS 升级(我相信较新的 nginx 版本支持它,尽管我还没有尝试过),但这不是运行 HTTP 和 HTTPS,这只是运行带有 TLS 升级的 HTTP。问题仍然是浏览器支持——大多数浏览器(仍然)不支持它。但是,如果您的客户端数量有限,那么这是一种可能性。

3

  • 1
    加密和未加密的 HTTP 流量可以通过单个端口处理。我想知道的是,使用 nginx 或其他产品(如 lighttpd)作为反向代理是否可以实现这一点。这种设置很可能可以由 Apache 处理,但我忘了在最初的问题中提到我宁愿不使用 Apache(尽管如果 Linux 平台上没有其他选择来实现这一点,我会这样做)。


    – 

  • 正如我在回答中所说,“我相信较新的 nginx 版本支持 [TLS 升级],尽管我还没有尝试过”。如果你需要我为你阅读手册,那么你就没那么幸运了。


    – 

  • 如果我给人的印象是我需要有人帮我读手册,我很抱歉。似乎问题(以及有关它的问题)没有得到足够准确的描述(我的错误),导致对我所问或需要的内容有不同的解释。所以我决定就这个问题提出一个新问题,并尽量避免对问题或有关它的具体问题产生任何可能的混淆。无论如何,感谢您抽出时间并分享您的见解。


    – 

我不确定它是如何实现的,但是 CUPSD 在端口 631 上响应 http 和 https。如果 nginx 现在无法做到这一点,也许他们可以学习 CUPS 团队是如何实现的,但是 CUPS 属于 GPL,所以如果 nginx 确实想实现这样的功能而在其他地方找不到代码,他们可能不得不考虑更改其许可证。

理论上,您可以拥有一个可通过 HTTP 访问的网页,该网页能够在 https:443 上打开以连接到任何想要的地址。WebSocket 初始握手是 HTTP。因此,是的,有可能让一个看起来不安全的页面实际上能够进行安全通信。您可以使用来实现这一点。