自宅で動かしているサーバー(Webサイトやゲームサーバーなど)を外から見られるようにしたい。でも、
- 自宅回線に固定IPがない(IPが変わる/CGNATでそもそも外から届かない)
- ルーターのポート開放はセキュリティ的に怖い
- 証明書(HTTPS)の管理が面倒
……という壁にぶつかりがちです。
この記事では、安いVPSを「入口」としてだけ使い、実体は自宅サーバーに置く構成を、実際に構築した手順そのままに紹介します。

この分担にすると、自宅ルーターのポート開放は一切不要で、回線のIPが変わっても平気です。証明書の管理も Caddy が全部自動でやってくれます。
事前に用意するもの
- VPS 1台(メモリ1GB程度の最安プランで十分です)
- 独自ドメイン(サブドメインでも可。マルチゲームだけでCaddy要素要らないなら不要。)
- 自宅サーバー(常時起動のPC)
- Tailscale アカウント(無料プランで可)
費用感は VPS 代+ドメイン代のみで、月あたり数百円〜千円強です。
手順0:DNS の A レコードを VPS に向ける
ドメイン(例:example.com)の A レコードに、VPS の公開IPを設定します。
注意点がひとつ。Cloudflare を使う場合はプロキシ(オレンジ雲)はOFF、「DNS のみ」にしてください。プロキシを有効にすると証明書や接続元IPの扱いが変わり、この構成の前提が崩れます。
手順1:Tailscale で管理経路を先に作る
VPS・自宅サーバー(・普段使うスマホやPC)に Tailscale を入れて、同じ tailnet に参加させます。以後の SSH 作業は、基本的にこの Tailscale 経由で行います。
最初に管理経路を確保しておくと、後でファイアウォールで SSH の公開を閉じても締め出されません。
手順2:VPS に frps を入れる
frp のサーバー側(frps)を VPS にインストールし、systemd サービスとして常駐させます。
/etc/frp/frps.toml の例(値は自分の環境に置き換えてください):
bindPort = 7000
auth.method = "token"
auth.token = "<長いランダム文字列>"
transport.tls.force = true
webServer.addr = "<VPSのTailscale IP>"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "<長いランダム文字列>"
allowPorts = [
{ start = 80, end = 80},
{ start = 443, end = 443},
]
tokenの長いランダム文字列は、サーバー側とクライアント側が一致していれば何でも良いです。また、userとpasswordは監視画面にログインするときに使用します。
- ダッシュボード(7500番)は Tailscale の IP でだけ待ち受ける。インターネットに管理画面を晒さない
- allowPorts は最小限に。ここに無いポートは、たとえ自宅側が要求しても frps が転送を拒否します(盾になります)
- TLS を必須(
transport.tls.force = true)にして、トンネルを暗号化する
<VPSのTailscale IP>:7500にアクセスするとログインIDとパスワードが要求され、入力すると監視画面が開きます。

ufw の設定は、公開が必要なポートだけに絞ります。同じTailnet内からのみsshを許可するようにしていますが、不要であれば記載はいりません。
sudo ufw default deny incoming
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 7000/tcp
sudo ufw allow in on tailscale0 to any port 22 proto tcp # SSHはTailscale経由のみ
sudo ufw enable
手順3:自宅PCに Caddy を入れる
自宅サーバーに Caddy をインストールし、サービスとして常駐させます(Windows なら WinSW でサービス化するのが確実です)。
Caddyfile の例:
{
email you@example.com
servers {
listener_wrappers {
proxy_protocol {
allow 127.0.0.1/32
timeout 2s
fallback_policy ignore
}
tls
}
}
}
example.com {
reverse_proxy 127.0.0.1:8080
log {
output file C:/tools/caddy/logs/access.log
format json
}
}
http://example.com {
redir https://{host}{uri} permanent
}
ここがこの構成の肝です。
- 証明書は Caddy が Let’s Encrypt から自動取得・自動更新します。VPS 側で証明書を扱う必要はありません。冒頭のメールアドレスは証明書の取得時に必要なので受信できるメールアドレスを指定します
- 後述の PROXY protocol を受けるため、
listener_wrappersにproxy_protocolを入れています。これでアクセスログに本当の接続元IPが記録されます - HTTP→HTTPS のリダイレクトは、ラッパー任せにせず明示的にサイトを定義するのが重要です(理由は「ハマりどころ」で)
手順4:自宅に frpc を入れる
frp のクライアント側(frpc)もサービスとして常駐させます。
frpc.toml の例:
serverAddr = "<VPSのグローバルIP>"
serverPort = 7000
auth.method = "token"
auth.token = "<frpsと同じトークン>"
transport.tls.enable = true
[[proxies]]
name = "web-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 80
remotePort = 80
transport.proxyProtocolVersion = "v2"
[[proxies]]
name = "web-https"
type = "tcp"
localIP = "127.0.0.1"
localPort = 443
remotePort = 443
transport.proxyProtocolVersion = "v2"
ポイント:
- 80/443 は frp の HTTP 機能(vhost)を使わず、生のTCPとして素通しします。振り分けと証明書は全部 Caddy の仕事にするためです
proxyProtocolVersion = "v2"が実IP引き継ぎの要です。frpc が接続元IPを PROXY protocol で Caddy に伝え、Caddy がそれをログとX-Forwarded-Forに使います
手順5:動作確認
- スマホを Wi-Fi から切り離し(モバイル回線で)、
https://example.comにアクセスできるか - Caddy のアクセスログに、スマホのキャリアのIP(=実IP)が記録されているか
- VPS と自宅サーバーの両方を再起動し、数分後に自動で復旧するか
- SSH の公開ポートに外から繋がらないこと、frps ダッシュボードが公開IPで開かないことを確認
注意点
実際に構築して踏んだ罠を、正直に書いておきます。
1. Caddy の:80 リダイレクトと PROXY protocol が競合する
Caddy には HTTP→HTTPS リダイレクト専用の listener wrapper がありますが、あれを使うと:80 のリスナーを wrapper が占有し、PROXY protocol の設定が効かなくなります。結果、frp 経由の HTTP が 400 で死に、HTTPS のハンドシェイクも失敗します。
対策は手順3の通り、リダイレクト用に http://example.com のサイトを明示的に定義することです(その場合はリダイレクトが 308 ではなく 301 になります)。
2. Windows Defender が frp をマルウェアと誤検知する
frpc.exe は PUA:Win32/FRProxy、frps.exe はトロイの木馬として検知されることがあります(機械学習判定)。frp はリモートアクセス系ツールなので、検知ロジックに引っかかりやすいようです。
公式リリースのチェックサムを必ず照合したうえで、インストールフォルダだけを Defender の除外に設定してください。むやみに除外範囲を広げないこと。
3. SSH セッションから起動したプロセスのサービス化
Windows の OpenSSH 経由でフォアグラウンド起動したプロセスは、ログアウト時に巻き添えで終了します(ジョブオブジェクトの仕様)。常駐させたいものは必ずサービス化(WinSW等)するか、タスクスケジューラを使ってください。
新しいWebサービスを追加する
たとえばdocker desktopで別のセルフホスティングツールを立ち上げたときはCaddyfileに追記します。
- DNSのAレコードにサブドメインを追加する
- Caddyfileに追記する(ドメインとリバースプロキシ先。例えばaaa.example.comと3000)
- powershellでRestart-Service caddyをする。frpの変更は不要
- aaa.example.comを開いて3000番ポートで待ち受けているサービスが開くか確認する
ゲームサーバーを公開する
TCP/UDP のゲームサーバー(Minecraftなど)も同じ仕組みで公開できます。
- VPS の
frps.tomlの allowPorts にゲームのポートを追加し、frps を再起動 - VPS の ufw で同じポートを許可(TCP/UDP 両方なら
sudo ufw allow 25565のようにプロトコル無記入で1ルール) - 自宅の
frpc.tomlに tcp / udp のプロキシを追加(PROXY protocol は付けません。ゲームのプロトコルには不要です)
[[proxies]]
name = "game-tcp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 25565
remotePort = 25565
[[proxies]]
name = "game-udp"
type = "udp"
localIP = "127.0.0.1"
localPort = 25565
remotePort = 25565
なお UDP は Tailscale 経由の疎通確認がしにくい(環境によってはTCPのみ)ので、検証は実機+外部回線で行うのが確実です。
まとめ
- VPS は「入口だけ」、実体と証明書は自宅側で自分で管理
- ポート開放なし・固定IPなしでも、自宅サーバーを HTTPS で公開できる
- 管理系(SSH・ダッシュボード)を Tailscale に寄せることで、攻撃対象を最小化できる
一度組んでしまえば、あとは証明書の更新も再起動後の復旧も全部自動です。自宅サーバーの公開で悩んでいる方の参考になれば幸いです。
