🐚

SSH で通信を運ぶ。ポート転送とプロキシ

に公開
2025/12/06
2件

注: 動作確認は 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 をブラウザで確認する

remote.example
$ pnpm vite dev

  VITE v7.1.9  ready in 1025 ms
 
  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

ローカルの :3000 -> リモートの localhost:5173 に転送します。

local
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 から使う

local
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
remote.example
psql postgres://postgres:password@localhost:5000
# -> local にある postgres に接続できる

例2: VPS 経由でローカルのサーバーを公開する

事前準備として -R が任意のアドレスで listen できるように sshd_config を編集します。

public.remote.example
$ cat /etc/ssh/sshd_config | grep "GatewayPorts"
GatewayPorts clientspecified

適当なサーバーを開いておきます。 GNU netcat を使っていますが、他の netcat だとオプションが違う可能性があります。

local
# 注意: インターネットに公開します!
echo 'Hello World!' | nc -l -p 3002

リモートの 0.0.0.0:3001 -> ローカルの localhost:3002 に転送します。すなわち、 localhost:3002 がグローバルに公開されます。

local
ssh -R 0.0.0.0:3001:localhost:3002 user@public.remote.example -N

どこからでもアクセスできるようになります。

any device
$ echo 'Hello local!' | nc public.remote.example 3001
Hello World!

プロキシ

ssh は -D フラグで簡易的な SOCKS5 プロキシとして使うことができます。
SOCKS5 は、通信を中継するための汎用プロキシプロトコルです。

構文

       -D [bind_address:]port

例: curl をプロキシする

local
ssh -D 1080 user@remote.example -N
local
# 比較対象: プロキシなし
$ 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するかどうかです。

sue@hiyo% ssh -R 0.0.0.0:5963:192.0.2.1:8888 localhost netstat -an | grep 5963
tcp4       0      0 *.5963                 *.*                    LISTEN
sue@hiyo% ssh -R '*':5963:192.0.2.1:8888 localhost netstat -an | grep 5963
tcp4       0      0 *.5963                 *.*                    LISTEN
tcp6       0      0 *.5963                 *.*                    LISTEN
sue@hiyo% ssh -R :5963:192.0.2.1:8888 localhost netstat -an | grep 5963
tcp4       0      0 *.5963                 *.*                    LISTEN
tcp6       0      0 *.5963                 *.*                    LISTEN
sue@hiyo% 

ssh(1)のman pageにも

An empty bind_address, or the address ‘*’, indicates that the remote socket should listen on all interfaces.

とありますし、IPv4に依存した 0.0.0.0 よりは * や空の指定の方が開発側の意図した使い方だと思います。

1
aster-voidaster-void

ご指摘ありがとうございます!その通りですね。修正しました。