Tailscale、便利ですよね。WireGuardの面倒な設定を意識せずに、離れた場所のデバイス同士がまるで同じLANにいるように繋がる。個人の自宅サーバーやちょっとした検証用途では、これ以上ないくらい手軽です。
ただ、社内で本格的に使おうとすると2つ引っかかりました。
- 無料プラン(Personal)のユーザー数上限。執筆時点で最大6ユーザーで、将来30名規模を見込むと収まりません。
- 社内のアクセス基盤を外部SaaSに預けきりにすることへの、ちょっとした抵抗感。料金体系や仕様変更に左右されるレイヤーではあってほしくない、という気持ちです。
そこで、Tailscale互換のコーディネーションサーバーをセルフホストできるOSS 「Headscale」 に移行してみることにしました。本記事はその構築メモです。GCPの1台のVMに、Terraform + Docker Compose で立てています。
Headscale とは
Tailscaleは、鍵交換やノード管理を担う「コーディネーションサーバー」をSaaSとして提供しています。Headscaleはこの部分をOSSで置き換える実装で、クライアントは公式の Tailscale アプリをそのまま使えます。
| Tailscale (SaaS) | Headscale (セルフホスト) | |
|---|---|---|
| コントロールプレーン | Tailscale社が運用 | 自社で運用 |
| クライアント | 公式アプリ | 公式アプリ(同じ) |
| ユーザー管理・認証 | Tailscale経由 | 自前(OIDC等) |
| データの所在 | Tailscale管理 | 自社管理 |
使い勝手はTailscaleのまま、コントロールプレーンだけ自社に持ってくる、というイメージです。
VPNがあると何ができるか
社内VPNがあると、SSHやファイル共有などを「同じネットワーク上のIPに繋ぐだけ」で扱えます。ポートを公開せずに済むのが地味に効きます。
| やりたいこと | VPNでできること |
|---|---|
| 社内マシンへ SSH | ポート非公開のまま、ACLで許可した人だけ到達 |
| DB等のTCPへ直接接続 | 公開せず内部IPに繋ぐ |
| オフィスLAN(NAS・プリンタ) | サブネットルーター経由で到達 |
| GCP外・他クラウドの機器 | 同じネットワークとしてメッシュ接続 |
通信はWireGuardベースで暗号化され、可能な範囲でクライアント同士のP2P直結になります。直結できないときだけリレー(後述のDERP)を経由します。
全体アーキテクチャ
1台のGCE VM上で、リバースプロキシ(Caddy)とHeadscaleをDocker Composeで動かすだけの構成です。外部に公開するのは 80/443 のみにしています。
インフラは Terraform、アプリは Docker Compose で管理しているので、terraform apply でVMを作り直せます。
ステップ1: Terraform でインフラ層
ファイアウォールは「Webは全開放、SSHは管理トンネルの固定レンジのみ」と明確に分けます。
# 公開: HTTP(ACME + リダイレクト) と HTTPS のみ
resource "google_compute_firewall" "allow_web" {
name = "infra-gateway-allow-web"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["infra-gateway"]
}
# SSH は Google IAP の範囲からのみ。公開SSHはしない
resource "google_compute_firewall" "allow_iap_ssh" {
name = "infra-gateway-allow-iap-ssh"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["35.235.240.0/20"] # IAP の固定レンジ
target_tags = ["infra-gateway"]
}VMには最小権限のサービスアカウントを紐付けます。ポイントは Vertex AI などGCPサービスへの認証を「鍵ファイルなし」で行うことです。VMに紐づくサービスアカウントの権限(ADC: Application Default Credentials)を使えば、ダウンロード可能な鍵ファイルを一切作らずに済みます。
ダウンロードできる鍵ファイルは「持ち出せる平文の機密」になります。VMにSA(サービスアカウント)を紐付けてADCで認証すれば、盗む対象そのものが存在しません。
ステップ2: Headscale + Google Workspace OIDC
認証は Google Workspace の OIDC を使い、example.com ドメインのアカウントだけログインできるよう制限します。
# headscale config.yaml(抜粋)
server_url: https://headscale.example.com
listen_addr: 0.0.0.0:8080 # TLS は Caddy が終端するので HTTP で待ち受け
oidc:
issuer: https://accounts.google.com
client_id: ${HEADSCALE_OIDC_CLIENT_ID}
client_secret: ${HEADSCALE_OIDC_CLIENT_SECRET}
scope: ["openid", "profile", "email"]
allowed_domains:
- example.com # 会社ドメインのアカウントのみ許可OIDCを使う最大のメリットは、ユーザーを手動で作らなくていいことです。社員がクライアントから初回ログインすると、Headscaleが自動でユーザーを登録します。
# Mac から(公式 Tailscale クライアント)
tailscale up --login-server=https://headscale.example.com
# → ブラウザが開き、Googleアカウントでログイン → 自動登録完了アクセス制御は ACL でデフォルト拒否
VPNで一番怖いのは「繋いだら何でも触れる」状態です。最初からデフォルト拒否にしておきます。
{
"groups": {
"group:admin": ["yamada-taro"],
"group:staff": []
},
"acls": [
// 管理者は全アクセス
{ "action": "accept", "src": ["group:admin"], "dst": ["*:*"] },
// 一般社員は Mac mini の SSH(22) だけ、のように絞る
// { "action": "accept", "src": ["group:staff"], "dst": ["tag:macmini:22"] }
]
}ステップ3: Caddy で自動HTTPS
Caddy はLet's Encryptの証明書を自動取得・自動更新してくれます。今回は80番が開いているので、最も素直な HTTP-01 チャレンジを使います。
{
email infra@example.com
}
headscale.example.com {
header Strict-Transport-Security "max-age=31536000; includeSubDomains"
reverse_proxy headscale:8080
}これで、HTTPS終端・HTTP→HTTPSリダイレクト・証明書の自動更新まで揃います。
活用例: Mac mini をまるごとリモート操作
最初のユースケースとして、社内に置いた Mac mini をVPN経由でリモート操作できるようにしました。Mac miniには公開ポートを一切開けず、VPN内からのみアクセスします。
① CLI操作(SSH)
# VPN内のMac miniのIPへ直接SSH(ポート公開なし)
ssh user@100.x.x.x② GUI操作(画面共有 / VNC)
macOS標準の「画面共有」も、VPN越しにそのまま使えます。Finderの「サーバへ接続」から vnc://100.x.x.x で繋げば、デスクトップをGUIで操作できます。
Finder → 移動 → サーバへ接続 → vnc://<Mac miniのVPN内IP>
SSHも画面共有(VNCの5900番)も、インターネットには公開していません。VPN内のIPにだけ通信が通り、ACLで許可した人に絞れます。ポートを開けずに外から社内のMacを操作できる、というのが地味に便利でした。
画面共有(VNC)はトラフィックが大きめなので、P2P直結が効く環境だと快適です。リレー経由だと描画がもたつくことがあります。
まとめ
1台のVMに Headscale + Caddy を載せて Terraform で管理する。最小構成はこのくらいシンプルでした。Tailscaleの手軽さはそのままに、ユーザー数の上限やSaaS依存からは離れられたので、今のところ移行してよかったと思っています。
オフィスLANへの接続など、これからやりたいことは残っていますが、まずは動くものができました。社内VPNをセルフホストで検討している方の参考になれば幸いです。

