OCI Always FreeでSecurity List・iptablesが正しくてもtcpdumpにSYNが届かない場合の切り分け手順とCloudflare Tunnelによる回避
概要
Oracle Cloud Infrastructure(OCI)Always Free の Compute インスタンス(VM.Standard.A1.Flex, Ubuntu 24.04 aarch64)で、SSH(22番)以外のポートに外部から到達できない事象に遭遇した。Security List・iptablesとも設定は正しく、tcpdumpで検証した結果、VMのNICにパケット自体が届いていないことを確認した。本記事ではその切り分け手順と、Cloudflare Tunnelによる回避策をまとめる。
前提環境
- OCI Always Free、VM.Standard.A1.Flex(ARM, aarch64)
- Ubuntu 24.04 LTS
- Node.js製アプリ(WebSocketサーバー、ポート8787で待受)
- 別途、code-server(VS Code Web版、ポート8080)でも同様の事象を確認
症状
- SSH(22番)は外部から問題なく接続可能
- それ以外の任意のポート(8080, 5000等でテスト)は、外部からのアクセスが全てタイムアウト
- アプリケーション側にも接続ログが一切残らない
切り分け手順
1. アプリがbindしているアドレスを確認
sudo ss -tlnp | grep 8787
127.0.0.1:8787ではなく0.0.0.0:8787で待ち受けていることを確認する。ここが127.0.0.1のままだと、そもそも外部インターフェース向けの通信を受け付けない。
2. OS側ファイアウォール(iptables)を確認
OCIのUbuntuイメージは初期状態でSSH以外の通信をブロックするiptablesルールが入っている。
sudo iptables -L INPUT -n --line-numbers
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
2 ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
3 ACCEPT 1 -- 0.0.0.0/0 0.0.0.0/0
4 ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22
5 ACCEPT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8787
6 REJECT 0 -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
末尾のcatch-all REJECTより前に、対象ポートのACCEPTルールが来ていることが重要。順番が逆だと、REJECTが先にマッチして弾かれる。無ければ挿入する。
sudo iptables -I INPUT <REJECT行の番号> -p tcp --dport 8787 -j ACCEPT
sudo netfilter-persistent save
netfilter-persistent saveを忘れると、再起動時にルールが消える。
3. OCIコンソール側のSecurity List / NSGを確認
インスタンスの「ネットワーキング」タブ→アタッチされたVNIC→サブネット→Security List(またはNSGが付いていればそちら)で、Ingress Rulesに対象ポートの許可があるか確認する。
- Source CIDR:
0.0.0.0/0 - IP Protocol:
TCP - Destination Port Range: 対象ポート
OCIではSecurity ListとNSGはOR条件で評価される(どちらか一方が許可していれば通る)。NSGの方が制限的でも、Security Listが許可していれば通過するので、NSGの存在だけを理由に諦めないこと。
また、編集しているSecurity Listが、実際にそのインスタンスのVNICが属するサブネットにアタッチされているものと一致しているかも、念のため確認しておくとよい(複数VCNがある環境では取り違えやすい)。
4. tcpdumpで実際にパケットが届いているか確認
ここまでの設定が全て正しいにも関わらず接続できない場合、tcpdumpでVMのNIC上のパケットを直接確認する。
sudo tcpdump -i any port 8787 -nn
この状態で、VM内部からではなく、外部の端末から対象ポートへアクセスする。VM内部(同じホストのSSHセッション等)から自分のパブリックIP宛てにアクセスするとヘアピンNAT扱いになり、正しい検証にならないので注意。
- 何も表示されない場合:iptables・Security Listより手前(OCIのネットワーク基盤側)でパケットが破棄されている。tcpdumpはNetfilterのフィルタより前段でパケットを捕捉するため、iptablesでREJECTされる場合でも通常は表示される。何も表示されないというのは、OSにすら到達していないことを意味する
- SYNは見えるがSYN-ACKが出ない場合:OS側の応答生成に問題がある(アプリのbindアドレス、またはstatelessなSecurity Listルールでの戻り経路不足等)
今回の事象では、Security List・iptablesとも設定が正しいことを確認した上で、tcpdumpに一切パケットが表示されないケースだった。VNICやSecurity Listの紐付けも再確認したが誤りはなく、OCI側のネットワーク基盤で原因を特定するには至らなかった。
回避策:Cloudflare Tunnel
受信ポートを開ける方向での解決を諦め、VM側から外向きに接続を確立する方式に切り替えた。
インストール(ARM64)
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/
cloudflared --version
クイックトンネルでの検証
アカウント登録・ドメイン設定なしで、即座に動作確認ができる。
cloudflared tunnel --url http://localhost:8787
実行すると、以下のようなヘルスチェックログの後にURLが発行される。
+--------------------------------------------------------------------------------------------+
| Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): |
| https://xxxx-xxxx-xxxx-xxxx.trycloudflare.com |
+--------------------------------------------------------------------------------------------+
このURLに外部からアクセスすると、正常にVM上のアプリまで到達することを確認した。WebSocketのアップグレードリクエストも問題なく通過する。ブラウザの開発者ツールで直接WebSocket接続を試す場合は以下のようなコードで確認できる。
const ws = new WebSocket('wss://xxxx-xxxx-xxxx-xxxx.trycloudflare.com');
ws.onopen = () => console.log('接続成功');
ws.onerror = (e) => console.log('エラー', e);
ws.onclose = (e) => console.log('切断', e.code, e.reason);
本番運用に向けて(named tunnel)
クイックトンネルは起動のたびにURLが変わるため、常時稼働用途には向かない。ドメインをCloudflareに追加した上で、named tunnelを使うと固定ホスト名を割り当てられる。
cloudflared tunnel login
cloudflared tunnel create <tunnel-name>
cloudflared tunnel route dns <tunnel-name> <hostname>
~/.cloudflared/config.ymlでingressルールを設定し、sudo cloudflared service installでsystemd常駐化すれば、再起動後も自動的に接続が復帰する。
まとめ
- OCI Always Freeで、Security List・iptablesとも正しく設定しているのに特定ポートだけ外部から到達できない場合、
tcpdumpでVMのNIC到達可否を確認すると切り分けが早い - パケット自体が届いていない場合、OSやコンソール側の設定確認では原因特定が困難
- Cloudflare Tunnelを使えば、受信ポートを一切開けずにHTTP/WebSocketサービスを公開できるため、上記のような原因不明のブロックに対する実用的な回避策になる