技術教程 · VPN 除錯實戰
推薦商品
🛒 前往蝦皮購買 ➔
OpenVPN TCP 443 打通全紀錄

OpenVPN 走 TCP 443 打通全紀錄

從踩坑到成功的五層 troubleshooting 實戰指南
最後更新:2026/7/18
分享到X 分享到LINE
📑 目錄
  1. 網路拓撲與背景
  2. 第一層坑:NAS 系統服務佔用 Port 443
  3. 第二層坑:Docker Port Mapping + iptables DNAT
  4. 第三層坑:非對稱路由(Asymmetric Routing)
  5. 第四層坑:conntrack + Docker FORWARD DROP
  6. 第五層坑:Policy Routing 範圍過廣
  7. DNS / DDNS 設定
  8. 持久化設定一覽表
  9. 除錯教訓

1. 網路拓撲與背景

家裡有兩台路由器在同一個 LAN 上運作,分別是中華電信數據機與 MikroTik hEX。這個架構是所有後續問題的根源。

設備IP角色
中華電信數據機192.168.1.1第一個 WAN 閘道,NAS 預設閘道
MikroTik hEX192.168.1.2第二個 WAN 閘道,PPPoE 撥接
NAS(飛牛 trim-fa91)192.168.1.51Docker host,跑 OpenVPN 容器
關鍵背景
NAS 上已有 openvpn 容器跑在 UDP 1194(正常運作中)。目標是新增一個 openvpn-tcp 容器,讓它走 TCP 443,以便在受限網路環境下也能連入。

核心陷阱:兩台 WAN 路由器共存於同一 LAN,封包的去程與回程走不同路徑,造成 非對稱路由(Asymmetric Routing),這是後續第三層坑的根源。

⚠️ 關鍵陷阱
NAS 的預設閘道指向數據機(192.168.1.1),而不是 MikroTik(192.168.1.2)。這意味著從 MikroTik 進來的封包,回應會走數據機出去——非對稱路由就此產生。

2. 五層坑(依除錯順序)

Layer 1 NAS 系統服務佔用 Port 443

問題症狀

容器啟動後,外部連 192.168.1.51:443 無回應,或回應的是 NAS 的 Web UI 而不是 OpenVPN。

原因

TerraMaster OS 的 nginx 已經綁定 TCP 443,用來提供 NAS 管理介面。這是系統級服務,有完整性保護,手動修改 nginx 後會自動還原。

💡 解法思路
既然改不了 nginx,就在它之前用 iptables NAT 攔截流量。在 PREROUTING 鏈中設定 DNAT,讓目的地為 443 的封包在到達 nginx 之前就被導走。
iptables — 搶佔 443 端口
# 在 PREROUTING 階段攔截 TCP 443 流量,導向 Docker 容器
iptables -t nat -A PREROUTING -p tcp --dport 443 \
  -j DNAT --to-destination 172.17.0.9:1194

Layer 2 Docker Container Port Mapping + iptables DNAT

問題症狀

設定 DNAT 後,從外部仍無法建立連線,或者連線建立後立即中斷。

原因

Docker 容器內部的 OpenVPN 監聽在 1194(不是 443),需要同時處理兩件事:

docker run — 啟動 TCP 版 OpenVPN
docker run -d \
  --name openvpn-tcp \
  --restart unless-stopped \
  --cap-add=NET_ADMIN \
  -v /vol1/docker/openvpn/config:/etc/openvpn \
  -p 8443:1194/tcp \
  kylemanna/openvpn \
  /bin/sh -c "openvpn --config /etc/openvpn/server-tcp.conf"
💡 為什麼用 8443?
容器映射到宿主機的 8443 而非直接用 443,是因為 nginx 佔著 443。實際流量是:外部 443 → iptables DNAT → 容器 IP 172.17.0.9:1194。
iptables DNAT — 把 443 導入容器
# 確認容器 IP
docker inspect -f '{{.NetworkSettings.IPAddress}}' openvpn-tcp
# 輸出:172.17.0.9

# 設定 DNAT:外部 443 → 容器 172.17.0.9:1194
iptables -t nat -A PREROUTING -p tcp --dport 443 \
  -j DNAT --to-destination 172.17.0.9:1194

# 確認規則
iptables -t nat -L PREROUTING -n --line-numbers

Layer 3 非對稱路由(Asymmetric Routing)— 最難搞

問題症狀

TCP 三次握手中的 SYN 能送到容器,但 SYN-ACK 回不去。用 conntrack -L 查看會看到連線卡在 SYN_RECV 狀態。

診斷 — 觀察 conntrack
conntrack -L -p tcp --dport 443
# 看到大量 SYN_RECV 狀態,沒有 ESTABLISHED

原因

封包流向:

  1. 客戶端 → MikroTik(外部 IP)→ NAS(經 DNAT)→ 容器 ✓
  2. 容器 → NAS → 數據機(因為預設閘道是 192.168.1.1)→ 外部 ✓
  3. 但 MikroTik 收不到回應封包(因為回程走了數據機),連線建立失敗
⚠️ 這是最關鍵的一步
非對稱路由導致 MikroTik 的 conntrack 記不到回程封包,SYN-ACK 雖然送出去了,但 MikroTik 認為那是「未知連線的回應」而直接丟棄。

解法:CONNMARK + Policy Routing

核心思路:在封包進來時就記住「這個封包是從 MikroTik 進來的」(用 CONNMARK 打標記),回程時根據標記強制走 MikroTik 出去。

步驟 1:建立策略路由表
# 建立路由表 100,指向 MikroTik
ip route add default via 192.168.1.2 table 100

# 建立路由規則:從 192.168.1.2 進來的封包,查表 100
ip rule add from 192.168.1.2 table 100
步驟 2:用 iptables mangle 打 CONNMARK
# 進站:從 MikroTik 進來的 TCP 443 封包,打上 mark 1
iptables -t mangle -A PREROUTING -p tcp --dport 443 \
  -s 192.168.1.0/24 -j CONNMARK --set-mark 1

# 出站:有 mark 1 的連線回應,強制走路由表 100(MikroTik)
iptables -t mangle -A POSTROUTING -p tcp --sport 443 \
  -j CONNMARK --restore-mark
步驟 3:設定回程路由
# 確認 masquerade 設定,讓回程封包正確 NAT
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 \
  -o eth0 -j MASQUERADE

# 確認 IP forward 已開啟
sysctl net.ipv4.ip_forward
# 應輸出:net.ipv4.ip_forward = 1

Layer 4 conntrack Strict Tracking + Docker FORWARD DROP

問題症狀

TCP 三次握手勉強完成(SYN → SYN-ACK → ACK),但後續的資料封包全部被丟棄。OpenVPN 無法建立 tunnel。

原因

兩個問題同時存在:

  1. conntrack 嚴格追蹤模式:Linux 預設的 TCP conntrack 要求封包嚴格遵循狀態機,任何不完整的連線會被自動清除。
  2. Docker 的 DOCKER-USER 鏈:Docker 預設在 DOCKER-USER 鏈設了 DROP 規則,非 Docker 管理的流量會被擋掉。
解法 1:放行 DOCKER-USER
# 在 DOCKER-USER 鏈開頭加入 ACCEPT 規則
iptables -I DOCKER-USER -i eth0 -p tcp --dport 443 \
  -j ACCEPT

# 確認規則位置(必須在 DROP 規則之前)
iptables -L DOCKER-USER -n --line-numbers
解法 2:放宽 conntrack 追蹤
# 將 TCP 連線追蹤改為寬鬆模式
# (允許不完整狀態機的封包通過)
echo 1 > /proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal

# 永久生效
echo "net.netfilter.nf_conntrack_tcp_be_liberal = 1" \
  >> /etc/sysctl.conf
sysctl -p
⚠️ 安全提醒
nf_conntrack_tcp_be_liberal 會降低 conntrack 的嚴謹度,可能增加一些安全風險。在家庭 NAS 環境下影響不大,但生產環境需謹慎評估。

Layer 5 Policy Routing 範圍過廣

問題症狀

前面的設定完成後,OpenVPN 連線成功了!但奇怪的是,NAS 上其他 Docker 容器之間的通訊也開始出問題。

原因

第三層的策略路由設定(ip rule add from 192.168.1.2 table 100)太過廣泛。所有從 MikroTik 進來的封包都被強制走了路由表 100,包括發往 Docker 網路(172.16.0.0/12)的封包。

解法:精準限定路由範圍
# 刪除過廣的規則
ip rule del from 192.168.1.2 table 100

# 新增精準規則:只對非 Docker 內網的封包套用策略路由
# Docker 網路:172.16.0.0/12
ip rule add from 192.168.1.2 to 172.16.0.0/12 lookup main
ip rule add from 192.168.1.2 to ! 172.16.0.0/12 table 100

# 確認規則順序(數字越小優先級越高)
ip rule show
💡 觀念釐清
Docker 容器之間的通訊走 Docker 內建的虛擬網路,不需要經過外部路由器。政策路由只應作用於「從外部進來的流量」,Docker 子網路的封包應走 main 表(預設路由)。

3. DNS / DDNS 設定

VPN 連線的目標位址從 NAS 的 IP(數據機的公網 IP)改為 MikroTik 的公網 IP(PPPoE 撥接取得)。

項目設定
DNS 名稱vpn.ouwinner.com
A Record從數據機公網 IP → MikroTik PPPoE IP
更新方式SSH 到 MikroTik 讀取 PPPoE IP,更新 Cloudflare DNS
更新頻率每 5 分鐘(crontab)
DDNS 自動更新腳本
#!/bin/bash
# /usr/local/bin/update-vpn-ddns.sh
# 從 MikroTik 讀取 PPPoE 公網 IP,更新 Cloudflare DNS

MIKROTIK_IP="192.168.1.2"
CF_API_KEY="your_cloudflare_api_key"
CF_ZONE_ID="your_zone_id"
CF_RECORD_ID="your_record_id"
DNS_NAME="vpn.ouwinner.com"

# SSH 到 MikroTik 取得 PPPoE IP
PUBLIC_IP=$(ssh admin@${MIKROTIK_IP} \
  "/ip address get [find interface=pppoe-out1] address" \
  | cut -d'/' -f1)

if [ -z "$PUBLIC_IP" ]; then
  echo "$(date): Failed to get IP from MikroTik" >> /var/log/vpn-ddns.log
  exit 1
fi

# 更新 Cloudflare A Record
curl -s -X PUT \
  "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${CF_RECORD_ID}" \
  -H "Authorization: Bearer ${CF_API_KEY}" \
  -H "Content-Type: application/json" \
  --data "{\"type\":\"A\",\"name\":\"${DNS_NAME}\",\"content\":\"${PUBLIC_IP}\",\"ttl\":120,\"proxied\":false}" \
  >> /var/log/vpn-ddns.log

echo "$(date): Updated ${DNS_NAME} → ${PUBLIC_IP}" >> /var/log/vpn-ddns.log
Cron 排程
# 每 5 分鐘自動更新
*/5 * * * * /usr/local/bin/update-vpn-ddns.sh

4. 持久化設定一覽表

以下所有設定在重開機後都需要自動生效,否則 VPN 會斷線。

內容位置 / 方式
443→容器 DNAT
DOCKER-USER 放行
Policy Routing
conntrack 設定
/etc/rc.local
或 systemd service
openvpn-tcp 容器設定 docker run 參數
容器名:openvpn-tcp
加上 --restart unless-stopped
DDNS 自動維護腳本 /usr/local/bin/update-vpn-ddns.sh
+ crontab */5 * * * *
Client 端設定檔 /vol1/docker/openvpn/client1-tcp443.ovpn
/etc/rc.local — 完整開機腳本
#!/bin/bash
# === OpenVPN TCP 443 持久化設定 ===

# 等待 Docker 就緒
sleep 10

# Layer 1 & 2: iptables DNAT(搶佔 443 → 容器)
iptables -t nat -A PREROUTING -p tcp --dport 443 \
  -j DNAT --to-destination 172.17.0.9:1194

# Layer 4: DOCKER-USER 放行
iptables -I DOCKER-USER -i eth0 -p tcp --dport 443 \
  -j ACCEPT

# Layer 4: conntrack 寬鬆模式
echo 1 > /proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal

# Layer 3: CONNMARK 策略路由
ip route add default via 192.168.1.2 table 100 2>/dev/null
ip rule add from 192.168.1.2 to ! 172.16.0.0/12 table 100 2>/dev/null
iptables -t mangle -A PREROUTING -p tcp --dport 443 \
  -s 192.168.1.0/24 -j CONNMARK --set-mark 1
iptables -t mangle -A POSTROUTING -p tcp --sport 443 \
  -j CONNMARK --restore-mark

# Layer 5: Docker 內網走 main 表
ip rule add from 192.168.1.2 to 172.16.0.0/12 lookup main 2>/dev/null

# 確認 IP forward
sysctl -w net.ipv4.ip_forward=1

exit 0

5. 除錯教訓

  1. Port 443 不是你的,先搞定佔用者。在 NAS 或伺服器上跑服務,永遠先用 ss -tlnp | grep :443 確認誰佔著 443。改不了就用 iptables NAT 在前面攔截。
  2. 雙 WAN 路由器 = 非對稱路由。家裡有兩台路由器提供上網時,封包進出走不同路徑是最常見也最難察覺的問題。conntrack -L 看到 SYN_RECV 卡住就要想到這個。
  3. CONNMARK + Policy Routing 是解決非對稱路由的標準解法。用 iptables mangle 在封包進站時打標記,再用 ip rule 根據標記決定出站路由。這不是 hack,是 Linux 進階路由的正式功能。
  4. Docker 的 iptables 很霸道。Docker 會自動管理 DOCKER-USER / DOCKER 鏈,預設會 DROP 非 Docker 管理的流量。新增服務時一定要在 DOCKER-USER 鏈加上 ACCEPT 規則,而且要放在 DROP 規則之前。
  5. 政策路由要精準,不要一刀切。ip rule 設得太廣會影響 Docker 內網通訊。記得用 to ! 172.16.0.0/12 排除 Docker 子網路,或者反過來,對 Docker 子網路加一條走 main 表的規則。
🎯 總結
整個除錯過程的核心邏輯:先解決端口衝突(iptables DNAT),再解決路由問題(CONNMARK + policy routing),最後處理 Docker 的網路隔離(DOCKER-USER + conntrack settings)。每一層都有其獨立的解法,但需要全部正確配置才能跑通。

💬 留言板

登入 Google 帳號即可留言