ElcamyTECH
Articles
GoogleCloud

Headscale × GCP で社内VPN基盤を作る — Terraform で立ち上げるまで

TechHeadscaleTailscaleGoogleCloudGoogle CloudTerraformVPN2026/06/07

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は管理トンネルの固定レンジのみ」と明確に分けます。

hcl
# 公開: 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 ドメインのアカウントだけログインできるよう制限します。

yaml
# 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が自動でユーザーを登録します。

bash
# Mac から(公式 Tailscale クライアント)
tailscale up --login-server=https://headscale.example.com
# → ブラウザが開き、Googleアカウントでログイン → 自動登録完了

アクセス制御は ACL でデフォルト拒否

VPNで一番怖いのは「繋いだら何でも触れる」状態です。最初からデフォルト拒否にしておきます。

hujson
{
  "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 チャレンジを使います。

caddyfile
{
	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)

bash
# 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をセルフホストで検討している方の参考になれば幸いです。

関連記事

Solution

AIエージェント・Dify構築支援

AIエージェント開発・Dify構築・PoC・社内研修まで
ワンストップで支援。まずはお気軽にご相談ください。

Elcamy

Technology Partners

DifyGoogle Cloud Partner