SSH で通信を運ぶ。ポート転送とプロキシ
注: 動作確認は OpenSSH でしかしていません。他の SSH 実装では動かない可能性や挙動が違う可能性があります。
書いてあること
- まえがき
- ローカルポートフォワーディング (local -> remote)
ssh -L - リモートポートフォワーディング (remote -> local)
ssh -R - プロキシ
ssh -D - あとがき
まえがき
SSH はリモートシェルを開くためのツールとして広く知られています。
しかし SSH には、リモートシェル以外のトンネリング機能もあります。たとえば、
- リモートのサービスを手元で動いているかのように扱う
- 手元の開発用サービスを一時的にリモートに公開する
- サーバー経由で外部サイトへアクセスする
といった動作を、SSH のみで実現できます。
またこうした用途ではシェルを開く必要がないため、-N を指定して通信転送だけを行うのが一般的です。
man 引用
-N Do not execute a remote command. This is useful for just forwarding ports. Refer to the description of SessionType in ssh_config(5) for details.
ローカルポートフォワーディング
ローカルのポートにアクセスしたとき、リモートに転送します。
構文
-L [bind_address:]port:host:hostport
-L [bind_address:]port:remote_socket
-L local_socket:host:hostport
-L local_socket:remote_socket
例: リモートで動いている dev server をブラウザで確認する
$ pnpm vite dev
VITE v7.1.9 ready in 1025 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
ローカルの :3000 -> リモートの localhost:5173 に転送します。
ssh -L 3000:localhost:5173 user@remote.example -N
# 実行しながらブラウザで `http://localhost:3000` を開くと、 dev server にアクセスできます。
リモートポートフォワーディング
リモートのポートにアクセスしたとき、ローカルに転送します。
構文
-R [bind_address:]port:host:hostport
-R [bind_address:]port:local_socket
-R remote_socket:host:hostport
-R remote_socket:local_socket
-R [bind_address:]port
例1: 手元で動かしているサービスを VPS から使う
docker run --rm -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=password postgres
# リモートの localhost:5000 -> ローカルの localhost:5432 に転送
ssh -R 5000:localhost:5432 user@remote.example -N
psql postgres://postgres:password@localhost:5000
# -> local にある postgres に接続できる
例2: VPS 経由でローカルのサーバーを公開する
事前準備として -R が任意のアドレスで listen できるように sshd_config を編集します。
$ cat /etc/ssh/sshd_config | grep "GatewayPorts"
GatewayPorts clientspecified
適当なサーバーを開いておきます。 GNU netcat を使っていますが、他の netcat だとオプションが違う可能性があります。
# 注意: インターネットに公開します!
echo 'Hello World!' | nc -l -p 3002
リモートの 0.0.0.0:3001 -> ローカルの localhost:3002 に転送します。すなわち、 localhost:3002 がグローバルに公開されます。
ssh -R 0.0.0.0:3001:localhost:3002 user@public.remote.example -N
どこからでもアクセスできるようになります。
$ echo 'Hello local!' | nc public.remote.example 3001
Hello World!
プロキシ
ssh は -D フラグで簡易的な SOCKS5 プロキシとして使うことができます。
SOCKS5 は、通信を中継するための汎用プロキシプロトコルです。
構文
-D [bind_address:]port
例: curl をプロキシする
ssh -D 1080 user@remote.example -N
# 比較対象: プロキシなし
$ curl https://ifconfig.me
(このデバイスの外向き IP アドレス)
# プロキシする
$ curl --socks5 localhost:1080 https://ifconfig.me
(リモートサーバーの外向き IP アドレス)
curl ではなく実際のブラウザで SOCKS5 を使う方法はもう少し複雑ですが、この記事の主要な点ではないため省略します。 AI にでも聞いてみましょう。
あとがき
不正確な点・わかりにくい点などあればぜひコメントしてください!
Discussion
どこからでもという事ならばbind addressは
0.0.0.0ではなく*や空の指定にした方がいいかと。違いはIPv6でもListenするかどうかです。
ssh(1)のman pageにもとありますし、IPv4に依存した
0.0.0.0よりは*や空の指定の方が開発側の意図した使い方だと思います。ご指摘ありがとうございます!その通りですね。修正しました。