[前回の Tailscale の話](/blog/tailscale-intro/)では、VPS の SSH(22番)を公開インターネットから外し、自宅や出先からは仮想 LAN 経由だけ届くようにしました。ただ、それだけでは足りない場面がありました。ファイルを上げ下げしたい相手や端末が、いつも Tailscale に入っているとは限らないことです。バックアップ用の受け渡し、一時的なアップロード、VPN を入れにくい環境からの転送——そういう用途が残ります。そこで取ったのが、「通常ログイン用の SSH は閉じたまま、SFTP だけ別ポートで公開する」という分離です。
やりたかったこと
やりたかったのは次の3点です。
- シェルログイン(SSH)は Tailscale 経由だけにする
- ファイル転送(SFTP)は、必要な相手がインターネットから届ける
- その公開面は、できるだけ小さく・壊れにくくする
「全部閉じる」か「全部開ける」かの二択ではなく、用途ごとに入口を分けるのがポイントでした。SSH と SFTP は同じ sshd の機能ですが、役割は違います。ログイン用の鍵穴と、荷物の受け渡し口を同じ扉にしない、という感覚に近いです。自分が普段入る道は狭く、ファイルだけ置く道は別にしておく。この切り分けが、あとから説明する専用ユーザーや chroot ともつながっています。
構成のイメージ
いまの運用はざっくりこうなっています。
| 用途 | ポート | 待ち受け | 認証 | |------|--------|----------|------| | 通常ログイン(SSH) | 22 | Tailscale 側のみ | 鍵 | | ファイル転送(SFTP) | 20022 | 公開 NIC(接続元 IP を制限) | 専用ユーザー+パスワード |
ポイントは、メインの SSH と SFTP 用の待ち受けを分けることです。SFTP 専用の sshd 設定と systemd サービスを用意し、ポート 20022 だけ別プロセスで受けています。メイン側の設定では、SFTP 用ユーザーでのシェルログインを拒否します。公開側に出すのは「中に入れる鍵」ではなく「荷物を置く窓口」だけ、という分離です。ポート番号は環境に合わせて選べますが、22 と混ぜないことが大事です。同じ扉の別錠、ではなく、別の窓口として扱うほうが運用も説明もしやすいです。
公開面を小さくする工夫
SFTP を開けると、どうしても攻撃面は増えます。なので、公開するなら次をセットにしました。
専用ユーザー(シェル不可)
一般ユーザーを流用せず、SFTP 専用アカウントを作ります。ログインシェルは使えないようにし、ForceCommand internal-sftp でファイル転送以外の操作を封じます。普段の作業用アカウントと入口を混ぜないだけで、事故の影響範囲がかなり変わります。
chroot で見える世界を狭める
接続後に見えるディレクトリを、アップロード用の領域だけに閉じ込めます。サーバー全体のホームや設定ファイルが覗けない状態にしておくと、「万一パスワードが漏れても、見える場所が狭い」という安心が残ります。
ここで一度ハマったのが、chroot のルート自体は書き込み不可にする必要があるというルールです。ルートを書き込み可能にしておくと sshd が拒否します。実際にファイルを置く場所は、その内側の upload のようなサブディレクトリにします。接続直後からそのディレクトリに入れるよう -d /upload を指定すると、利用者側も迷いにくいです。
接続元 IP を絞る
ポートを開けただけでは、スキャナからのノイズがすぐ来ます。UFW で 20022 を「自分の拠点 IP だけ」に制限しました。クラウド側のセキュリティグループも、可能なら同じ方針に揃えると安心です。IP が変わったときはルール更新が必要になりますが、その手間より「世界中からパスワード認証を試される」負担のほうが大きいと判断しました。
実際にぶつかったこと
公開 IP から届かない
サーバーの UFW では開いているのに、外からつながらないことがあります。原因は VPS 側のファイアウォール(セキュリティグループ)でした。ホスト OS とクラウドの両方で穴が開いていないと、パケットは届きません。「片方だけ開けたつもり」は、個人 VPS でよくある落とし穴です。
つながるが書けない
SFTP でログインできても、アップロードが失敗することがあります。原因は前述の chroot ルールです。ルートを書き込み可能にしてしまうか、書き込み可能な場所を -d で案内していないか、のどちらかが多いです。権限を直すときは、「誰が・どのパスに・何をできるか」を一段階ずつ確認すると早いです。
再起動後に SFTP だけ落ちる
OS 更新のあとに再起動すると、SFTP 用サービスだけ起動に失敗したことがありました。原因はランタイムディレクトリ(/run/sshd)の不足です。専用サービスの unit に RuntimeDirectory=sshd を足して復旧しました。メインの ssh は動いているのに SFTP だけ死ぬ、という状態は気づきにくいので、再起動後チェックに入れておくと安心です。
この設計の得失
うれしい点は、SSH を閉じたままファイル受け渡しの導線を残せることです。Tailscale 未導入の端末や、一時的な受け渡しでも、窓口を一つ用意しておけば足ります。専用ユーザーと chroot、IP 制限までセットにすると、「開いているけれど狭い」状態を作れます。
一方で、パスワード認証の公開ポートは残りリスクです。強いパスワード、接続元の制限、不要になったら閉じる、という運用が前提になります。IP 制限は回線変更に弱いので、拠点が増えるなら VPN 側に寄せたほうが楽な場面もあります。自分の用途では、頻度の低い受け渡し口として割り切っています。完璧なゼロリスクではなく、「SSH を晒すよりマシで、運用できる範囲の妥協」です。
まとめ
SSH を Tailscale だけに閉じたあとも、SFTP を別ポート・別ユーザー・chroot・IP 制限で公開する、という分離は現実的でした。全部を同じ入口にしない。公開するなら見える世界と接続元を狭くする。再起動後まで含めて動くか確認する。この3つを押さえると、個人 VPS でも「閉じたいものと残したいもの」を両立しやすい、というのが今回の結論です。次に似た構成を組むときは、最初から「ログイン用」と「転送用」を別サービスとして設計しておくと、あとからの締め直しが楽になります。