
利用 Nginx 四層 SNI 預讀透傳 TLS 流量,讓對外連線受阻的節點能透過同內網的健康主機,免憑證透明連外。
我在 OCI 部署了 S1、S2 兩台伺服器。S1 存取公網正常,但 S2 對部分 HTTPS 服務發出的請求卻會逾時或遭到拒絕。相同的目標、相同的請求,僅因出口節點不同,結果便完全不同。
問題可能出在網路,或是目標伺服器設有黑名單與白名單。我曾嘗試更換 S2 的公網 IP,但成本較高,結果也無法保證。若將服務搬回 S1,則會浪費 S2 現有的資源;若在 S1 額外部署應用程式代理,又會增加維運複雜度。
面對這個網路問題,我不想再額外導入其他網路服務,為什麼同一家雲端服務商的伺服器,對外連線的結果卻截然不同?這個 IP 是否受到了差別待遇?
S2 的對外連線故障並非目標服務停機。較可能的情況是,S2 的出口外網 IP 遭目標端攔截,或特定路徑上的網路路由受到限制。
在同屬 Oracle Cloud Infrastructure(OCI)同一區域的內網環境中,測試結果呈現明顯反差。我在 S1 上請求外部目標 HTTPS 服務時,TCP 三向交握與 TLS 協商都能在數十毫秒內順利完成;但在 S2 發出完全相同的請求時,主控台卻在等待數十秒後顯示連線逾時(Connection timed out)。
正在渲染 Mermaid 圖表...
這就像寄出一封信:郵局運作正常,信件內容也完全合法;但收件單位可能設定了過濾規則,也可能是特定區域的投遞路徑發生異常。只要收件方從信封上看到 S2 的寄件地址,信件便可能直接石沉大海;反之,若改用信譽良好的 S1 地址寄出,通訊便能立即恢復。
目前已觀察到以下現象:
雲端廠商(例如 OCI)僅能保障雲端主機至公網閘道的基礎連通性,無權也無法干預第三方網站的網路安全策略或公網路由選擇。
遇到這類故障時,支援工程師常誤以為「伺服器無法存取外部服務,只要請機房客服開工單就能解封」。實際上,當 OCI 的 Security List 安全清單與 Route Table 路由表均允許出站流量(0.0.0.0/0)時,即可確認 OCI 端的設定並未明確阻擋該出站流量。
正在渲染 Mermaid 圖表...
外部服務端的封鎖或路由異常通常更為複雜,並可能涉及多種非對稱因素:
即使向 OCI 提交工單,技術支援最多也只能確認機房閘道是否正常運作。機房維運人員無法要求外部網站修改其存取策略,更無法改變外部自治系統(AS)的路由方向。持續將時間耗費在工單往返溝通上,無法縮短服務中斷的時間。
較理性的解法,是從既有資源中尋找替代路徑。既然 S1 與 S2 位於同一個虛擬雲端網路(VCN)內部,兩台主機之間的私有內網通訊完全暢通,且不需支付內網流量費用,那麼便可讓 S1 擔任中繼節點。由 S1 的出口 IP 代替 S2 對外發送請求,即可在不變更既有基礎設施的前提下,恢復網路連通性。
S1 不需要持有目標網站的憑證,也能將 S2 的 HTTPS 連線準確送往目標網域。關鍵在於讀取 TLS 交握中的伺服器名稱指示(Server Name Indication,SNI),再原樣轉送 TCP 位元組串流。
可以將 S1 想像成只查看信封地址的轉運站。相似之處在於,它會從 ClientHello 讀取網域名稱,但不會拆開加密內容;不同之處在於,它仍會由 S1 主動建立一條獨立的 TCP 連線,而非單純轉寄同一個封包。
Nginx 的 ngx_stream_ssl_preread_module 提供此能力。TLS ClientHello 通常會以明文攜帶 SNI 網域名稱。Nginx 在 stream 四層串流代理中啟用 ssl_preread on 後,便能依據該網域名稱決定上游位址。後續的 TLS 交握與 HTTP 內容都會直接透傳,S1 不持有憑證私鑰,也無法解密請求內容。
這常被誤認為等同於 Nginx 的 HTTPS 反向代理。實際上,這是 TCP 透傳:反向代理會終止 TLS,因此需要憑證;此處不終止 TLS,只讀取交握前段的 SNI 欄位。
發行版套件是否包含此模組,無法一概而論。許多主流 Linux 的 Nginx 預編譯套件已提供 stream 與 SSL preread 能力,但仍應在 S1 實機確認,而非假設其「預設存在」。
將以下片段放入 S1 的 Nginx 頂層 stream 設定中。<S1可用DNS解析器> 必須替換為 S1 可存取的 DNS 位址。
proxy_pass 使用 SNI 變數,因此 Nginx 必須透過 resolver 在執行階段解析目標網域。若未設定,變數形式的上游網域可能無法解析,導致連線失敗。變更設定後,請執行 nginx -t,再平滑重新載入服務。
正在渲染 Mermaid 圖表...
代價相當明確:在 S1 的同一個 IP 上,TCP 443 連接埠會由 stream 監聽獨占。若 S1 原本透過該 IP 託管一般 HTTPS 網站,網站便無法再直接監聽此連接埠。可行的做法是改用另一個公網 IP,或將本機網站遷移至其他連接埠;無法讓 HTTP 模組與 stream 模組同時繫結至同一個 IP:443。
此方案也依賴用戶端送出可見的 SNI。對於未使用 SNI 的舊版用戶端,或啟用加密 ClientHello(ECH)而使 Nginx 無法取得網域名稱的情境,便無法依此動態選擇目標。
S2 不需要安裝代理用戶端。只要讓目標網域在 S2 本機解析為 S1 的私有網路位址,同時維持 URL 中的網域名稱不變即可。
之後,S2 存取 https://target.example.com 時,連線實際會進入 S1 的 443 連接埠;但 TLS 中的 SNI 與憑證驗證名稱仍為 target.example.com。S1 讀取該名稱後,會向實際目標建立新的對外 TCP 連線,並雙向轉送位元組串流。
因此,目標伺服器在 TCP 連線層看到的來源 IP,會是 S1 的公網出口 IP,而不是 S2 的公網 IP。S2 的位址不會出現在這條 TCP 工作階段中。不過,若應用層主動攜帶身分資訊(例如用戶端自行加入 X-Forwarded-For),則屬於另一條資訊通道;本方案只會替換網路連線的來源位址。
在 S2 上驗證時,仍應使用原始網域名稱,而不是 S1 的 IP:
應檢查三件事:TLS 交握是否完成、憑證網域是否符合目標網域,以及 HTTP 狀態碼是否符合預期。此外,也應限制只有 S2 能存取 S1 的 443 連接埠;否則,任何能連上 S1 的主機,都可能將它當成通往任意網域的 TCP 出口。
利用 Nginx 四層 SNI 預讀透傳 TLS 流量,讓對外連線受阻的節點能透過同內網的健康主機,免憑證透明連外。
【核心優勢】
【邊界與代價】
【適用場景】 節點因公網 IP 遭封鎖、單向封包遺失(packet loss)或 BGP 路由故障導致外部通訊逾時,且同內網中存在可正常連外的輔助節點。
【最佳實踐與避坑指南】