SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor

2台のVPSをプライベートネットワークで接続する方法

2台のVPSを安全に接続する3つの方法を解説します。プロバイダーのVPC、WireGuard、Tailscaleの違いから、サブネット、サービスのバインド、ファイアウォール、MTUまで確認できます。

2 台の VPS をプライベートネットワークで接続できますか?

はい。2 台の VPS を 1 つのプライベートネットワークに参加させると、各マシンから、パブリックインターネットではルーティングできないプライベートアドレスで相互に通信できます。避けるべき方法は、近道を使うことです。PostgreSQL を 0.0.0.0 にバインドし、ファイアウォールでポート 5432 を開け、パスワードだけを信頼する構成です。パブリック IP 上のデータベースポートは、開放してから数時間以内にスキャナーに発見されます。その後は、1 つの認証情報だけがデータとインターネットの間の防壁になります。

プライベート接続を構築する方法は 3 つあります。適した方法は、2 台のマシンがどこにあるかによって決まります。

  1. プロバイダーのプライベートネットワークまたは VPC(virtual private cloud)。無料で高速です。両方の VPS が同じプロバイダーにあり、通常は同じリージョンにある場合に限り利用できます。
  2. 自分で設定する WireGuard トンネル。暗号化され、相互に到達できる任意の 2 台のマシン間で使用できます。各側の設定は約 10 行です。
  3. Tailscale などのマネージドメッシュ、またはセルフホストの Headscale。マシンが異なるプロバイダーにある場合、一方にパブリック IP がない場合、または後で 3 台目のマシンを追加する予定がある場合に適しています。

以下では各方法の設定を説明します。その後、設定が完了して動作し始めた後に問題になりやすい項目として、サブネットの選択、サービスのバインド、ファイアウォールルール、MTU(maximum transmission unit)、再起動時の動作を取り上げます。

構築を始める前にトンネルのサブネットを決める

選択したプライベート範囲を後から変更するのは困難です。両方のマシンの設定ファイル、接続文字列、ファイアウォールルールに組み込まれるためです。どちらかのマシンがすでに使用しているネットワークと重複しない範囲を選びます。

重複してもエラーは表示されません。カーネルは最も具体的な一致に基づいてルーティングするため、問題が表面化しないまま発生します。トンネルが 172.17.0.0/24 を使用し、そのホスト上の Docker のデフォルトブリッジがすでに 172.17.0.0/16 を所有している場合、トンネル内に送るはずだったパケットがコンテナに送られます。両方のマシンで ip route show を実行し、どちらの出力にも表示されない範囲を選びます。

避けるべき範囲は次のとおりです。192.168.0.0/24192.168.1.0/24 は家庭やオフィスのルーターが割り当てる範囲です。172.17.0.0/16 は Docker のデフォルトブリッジであり、172.18 以降のネットワークは Docker Compose がプロジェクトごとに作成するネットワーク です。100.64.0.0/10 は Tailscale が使用します。また、プロバイダーが独自のプライベートネットワーク用に割り当てる範囲も避けます。小規模な構成では 10.90.0.0/24 が安全な選択肢です。以下の例でもこれを使用します。

プロバイダーのプライベートネットワークまたは VPC

ほとんどのプロバイダーでは、プライベートアドレス範囲の 2 番目のネットワークインターフェイスを追加できます。これは VPC(virtual private cloud)と呼ばれることが多い機能です。追加料金はかかりません。トラフィックはプロバイダー自身のネットワークから外に出ないため、往復遅延も短くなります。ただし、暗号化は行われません。条件は 2 つあります。2 台の VPS が同じプロバイダー上にあることと、多くのプロバイダーでは同じリージョンにあることです。

通常、このインターフェイスはアドレスが設定されていない状態でゲスト内に現れます。パネルに表示されたアドレスを割り当ててください。まずインターフェイスを確認します。

ip -brief address show
ip route show default

プライベート NIC(network interface card)は、アドレスとデフォルトルートがなく、enp7s0ens7 のような名前を持つインターフェイスです。eth0 だと決めつけないでください。/etc/netplan/60-private.yaml のように、そのインターフェイス用の netplan ファイルを作成します。

network:
  version: 2
  ethernets:
    enp7s0:
      addresses:
        - 10.20.30.11/24
sudo chmod 600 /etc/netplan/60-private.yaml
sudo netplan apply
ip -brief address show enp7s0
ping -c 3 10.20.30.12

これで、インターフェイスには設定したアドレスとともに UP が表示されるはずです。同じリージョンにある 2 台の間であれば、もう一方のマシンのプライベートアドレスへの ping は 1 ミリ秒を大幅に下回る時間で応答します。chmodnetplan apply でファイルの権限が開きすぎているという警告が表示されても無視してください。netplan ファイルには、wifi キーなどの Secret が含まれる場合があるためです。

これで防げるもの。 パブリックインターネットからのアクセスです。プライベートアドレスは外部からルーティングできないため、スキャナーからポート自体が見えません。これで防げないもの。 プロバイダーのネットワーク内にすでに存在するものからのアクセスです。到達範囲はプロバイダーによって異なるため、ドキュメントを確認してください。自分のアカウントに属するマシンからだけプライベートアドレスに到達できるプロバイダーもあれば、同じデータセンター内のすべてのマシンから到達できるプロバイダーもあります。また、トラフィックは平文で流れるため、そのセグメント上でパケットを取得できるものはデータベースの行を読み取れます。データが重要な場合は、プライベートインターフェイス上でオプション 2 を実行してください。WireGuard の Endpoint をプライベートアドレスに指定すると、短い経路上でも暗号化できます。

Option 2: 2 台の VPS 間に WireGuard トンネルを構成する

WireGuard はカーネル内で動作する Layer 3 トンネルで、Linux 5.6 以降は mainline に含まれています。そのため、Ubuntu 24.04 と Debian 13 では追加のモジュールは必要ありません。2 台のサーバー間では point-to-point リンクとして動作します。各側には peer を正確に 1 つ記述し、NAT(network address translation)や IP forwarding の設定も不要です。peer は public key と、その key が使用できるアドレスの組み合わせです。そのため、WireGuard は key だけでルーティングとアクセス制御の両方を判断できます

host A を public IP 203.0.113.10 の Web サーバー、host B を 198.51.100.20 のデータベースサーバーとします。host A にはトンネル内で 10.90.0.1 を割り当て、host B には 10.90.0.2 を割り当てます。次のコマンドを両方のマシンで実行します。

sudo apt update && sudo apt install -y wireguard
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/host.key'
sudo sh -c 'wg pubkey < /etc/wireguard/host.key > /etc/wireguard/host.pub'
sudo chmod 600 /etc/wireguard/host.key
sudo cat /etc/wireguard/host.pub

最後の行には、もう一方のマシンの設定に貼り付ける public key が表示されます。.key ファイルは、生成されたマシンから外部へ出ません。モードは明示的に設定してください。sudoumask 077 だけでは信頼できません。sudo はリダイレクトで作成されるファイルに独自の umask を適用するためです。world-readable な private key は、トンネルが存在しないのと同じです。

host A に /etc/wireguard/wg0.conf を記述します。

[Interface]
Address = 10.90.0.1/24
ListenPort = 51820
PrivateKey = <host A private key>

[Peer]
PublicKey = <host B public key>
AllowedIPs = 10.90.0.2/32
Endpoint = 198.51.100.20:51820
PersistentKeepalive = 25
host B: 完全な /etc/wireguard/wg0.conf
[Interface]
Address = 10.90.0.2/24
ListenPort = 51820
PrivateKey = <host B private key>

[Peer]
PublicKey = <host A public key>
AllowedIPs = 10.90.0.1/32
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25

どちらのファイルにも、もう一方のマシンの private key は含まれていません。各側は自身の private key と、相手側の public key を保持します。sudo cat /etc/wireguard/host.pub で public key をコピーし、.key ファイルはそれぞれ生成された場所に残してください。

Address/24 は、トンネルのサブネット全体への route を設定します。これにより、後から 3 台目のマシンを追加できます。AllowedIPs は、各側に 1 つだけ指定する /32 です。これは、相手側が所有するトンネルアドレスです。このリストは受信時にはアクセス制御リストとして機能します。復号されたパケットの送信元アドレスが peer の AllowedIPs に含まれていない場合、そのパケットは破棄されます。これにより、peer がネットワーク内の別のアドレスを装うことを防げます。両側に public IP があるため、ここでは両方に Endpoint を設定します。再起動後は、どちらのマシンからでも handshake を開始できるため、相手側が変化を検知するのを待たずにリンクが復旧します。より広い AllowedIPs、NAT、DNS の設定は 単一サーバー向けの WireGuard VPN 構成 に記載されています。この 2 ホスト間のリンクでは、そのガイドにある router 部分は不要です。

両方のマシンで port を開き、interface を起動します。

sudo ufw allow 51820/udp comment 'wireguard'
sudo systemctl enable --now wg-quick@wg0
sudo wg show
ping -c 3 10.90.0.2

wg show には、数秒前の latest handshake と、両方向で 0 ではない transfer が peer に表示されるはずです。handshake の行がない peer では、何も到達していないか、何も受け入れられていません。provider の network firewall で UDP 51820 を許可してください。これは、多くの control panel では ufw とは別の制御です。続いて、key が入れ替わっていないか確認します。[Peer] ブロックには、もう一方のマシンの public key が必要です。enable --now は、その systemctl 行で重要な部分です。手動で実行した wg-quick up wg0 は次回の再起動後に失われ、kernel upgrade でも再起動が発生するためです。

Threat model. WireGuard はすべてのパケットを認証して暗号化するため、制御していない provider network 上や、2 つの provider 間の public internet 上を通過しても traffic は非公開のままです。すでに侵害された host には効果がありません。どちらのマシン上のプロセスからでも、トンネルアドレスに到達できるためです。

VPS が異なるプロバイダーにある場合の選択肢 3: 管理型メッシュ

2 台のマシンが異なるプロバイダーにある場合、プロバイダーのプライベートネットワークは利用できません。手動で構築した WireGuard は引き続き使用できます。管理型メッシュも利用できます。これは同じ WireGuard データプレーンを使用し、アドレスの割り当て、公開鍵の配布、片側にパブリック IP がない場合の NAT 越えを行う調整サーバーを追加します。

Tailscale はこの調整サーバーを代わりに運用します。両方のマシンにインストールします。

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

sudo tailscale up は認証 URL を出力します。URL を開いてサインインすると、マシンが tailnet に参加します。続いて、両側を確認します。

tailscale status
tailscale ip -4

各マシンには 100.64.0.0/10 内のアドレスが 1 つ割り当てられ、tailscale0 というインターフェースで使用されます。tailscale status にはもう一方のマシンと、接続が直接接続かリレー経由かが表示されます。これらのアドレスは WireGuard のアドレスと同じように、そのまま使用します。

サーバーでは、2 つの設定が重要です。デフォルトではノードキーが 180 日後に期限切れになります。キーが期限切れになると、その時点で警告なしにマシンが tailnet から切断されます。そのため、各サーバーで管理コンソールの Disable Key Expiry を有効にします。アクセスルールのデフォルトでは、自分のすべてのデバイスが相互に接続できます。2 台のサーバーだけなら問題ありませんが、ラップトップが参加した時点で不適切になります。無人で構築する場合は、コンソールで認証キーを作成し、sudo tailscale up --auth-key=tskey-auth-... で渡します。これにより、マシンでブラウザー操作が不要になります。

脅威モデル。 2 台のマシン間のトラフィックはエンドツーエンドで暗号化されます。調整サーバーは両方のマシンの秘密鍵を保持しないため、データベーストラフィックを読み取れません。一方で、調整サーバーはメタデータを保持します。存在するマシン、提示された公開鍵、相互に通信できるマシンの組み合わせが対象です。これを許容できない場合は、コントロールプレーンを自分で運用します。Headscale は Tailscale のコントロールサーバーを実装したオープンソースソフトウェアです。クライアントには公式のものをそのまま使用できます。後から、マシン自体ではなく、一方のマシンの背後にあるプライベートネットワーク全体へ接続する必要が生じた場合は、サブネットルーターから経路を広告する構成にします。

サービスは 0.0.0.0 ではなくプライベートアドレスにバインドします

サービスがパブリックインターフェースでも応答する場合、プライベートネットワークは役に立ちません。待ち受け状態を確認します。

sudo ss -tlnp

0.0.0.0:5432 または *:5432 は、そのマシンが持つすべてのアドレスでポートが開いていることを示します。パブリックアドレスも含まれます。10.90.0.2:5432 は、トンネル内からのみ開いていることを示します。PostgreSQL では、postgresql.conf に次の設定を追加します。

listen_addresses = '10.90.0.2, 127.0.0.1'

次に、pg_hba.conf で接続を許可する相手を、そのピアだけに限定します。

host    appdb    appuser    10.90.0.1/32    scram-sha-256

サービスを再起動し、sudo ss -tlnp を再度実行してアドレスが変わったことを確認します。MySQL または MariaDB では、/etc/mysql/mysql.conf.d/mysqld.cnfbind-address = 10.90.0.2 が相当する設定です。

コンテナでは、この問題が特に起きやすくなります。Docker はデフォルトで全アドレスにポートを公開し、ufw がフィルタリングしない独自の iptables ルールを書き込むためです。ポートマッピングでは、トンネルのアドレスを指定します。

services:
  db:
    image: postgres:17
    ports:
      - "10.90.0.2:5432:5432"

単純な "5432:5432" では、データベースがパブリック IP で公開されます。その場合、ufw はポートを denied と報告し続けます。これは、ufw が deny と報告しても公開された Docker ポートに到達できる理由です。公開ポートをトンネルアドレスにバインドすれば解決します。両方のサービスを同じホストで実行する場合は、データベースを ports: から完全に分離します。

トンネル以外を信頼しないファイアウォールルール

必要なルールは、「このインターフェース上のこのポートだけを許可し、それ以外では許可しない」というものです。

sudo ufw allow in on wg0 to any port 5432 proto tcp comment 'db from tunnel'
sudo ufw status verbose

ufw status verboseでは、5432 が on wg0 だけで許可され、デフォルトの受信ポリシーは引き続き deny になっていることを確認できます。WireGuard は wg0 に到着したパケットが、正しい暗号鍵を持つピアから送信されたことをすでに検証しています。そのため、インターフェースルールは送信元アドレスルールよりも正しい状態を維持しやすくなります。これらの設定の基盤となるデフォルト拒否ポリシーについては、VPS 向け ufw ファイアウォールの基本で説明しています。Tailscale ではインターフェースが tailscale0 なので、ルールは sudo ufw allow in on tailscale0 to any port 5432 proto tcp になります。

プロバイダーのプライベートネットワークには信頼できるトンネルがないため、ルールで送信元アドレスを指定する必要があります: sudo ufw allow from 10.20.30.11 to any port 5432 proto tcp。この設定では、そのセグメント上でその送信元アドレスを使用できるすべての通信元を信頼します。そのため、他の 2 つと比べて、選択肢 1 だけでは安全性が低くなります。

もう一方のマシンから経路をテストします。

sudo apt install -y netcat-openbsd
nc -vz 10.90.0.2 5432

succeeded! は、経路全体が機能していることを示します。Connection refused は、そのアドレスでサービスが待ち受けていないことを示すため、bind の設定が誤っています。接続が応答しないままタイムアウトした場合は、ファイアウォールがパケットを破棄しているため、ルールが誤っています。この違いから、次に開くべきファイルを判断できます。両方を編集せずに済みます。

カプセル化されたリンクで大きなクエリがハングする理由: MTU

カプセル化には数バイトのオーバーヘッドがあります。WireGuard はパケットを外側の IP ヘッダー、UDP ヘッダー、独自の 16-byte ヘッダー、16-byte の認証タグで包むため、トンネルが運べるパケットは基盤リンクが運べるパケットより小さくなります。MTU が高すぎる場合、症状は分かりにくくなります。ping は動作し、psql は接続した後、大きな結果を返すクエリだけが永遠にハングします。小さいパケットは通過しますが、大きいパケットは破棄されます。送信元に理由を伝える ICMP メッセージが返らないこともあります。

ChartBytes left inside a WireGuard tunnel, by underlying path
The data behind this chart
[
  {
    "label": "1500 path, IPv4 endpoint",
    "path_mtu": 1500,
    "wireguard_overhead": 60,
    "tunnel_mtu": 1440
  },
  {
    "label": "1500 path, IPv6 endpoint",
    "path_mtu": 1500,
    "wireguard_overhead": 80,
    "tunnel_mtu": 1420
  },
  {
    "label": "1450 path, IPv4 endpoint",
    "path_mtu": 1450,
    "wireguard_overhead": 60,
    "tunnel_mtu": 1390
  },
  {
    "label": "1400 path, IPv6 endpoint",
    "path_mtu": 1400,
    "wireguard_overhead": 80,
    "tunnel_mtu": 1320
  }
]

これらの数値は測定値ではなく、パケットごとのオーバーヘッドが固定されていることに基づく算術値です。IPv4 エンドポイントを使用する通常の 1500-byte の経路では、WireGuard は 60 bytes(外側の IP が 20 bytes、UDP が 8 bytes、WireGuard が 16 bytes、認証タグが 16 bytes)のオーバーヘッドを追加します。そのため、データに使用できるのは 1440 bytes です。IPv6 エンドポイントでは外側のヘッダーが 20 bytes 大きくなるため、使用できるサイズは 1420 に減ります。この 2 つ目の数値が、wg-quick でインターフェイスの MTU を経路 MTU から 80 引いた値に設定する理由です。1420 なら、どちらのエンドポイントファミリーでも安全です。プロバイダー側のネットワークでもカプセル化される場合は、さらに上限が下がります。たとえばアンダーレイが 1450 の VXLAN では 3 行目の 1390 まで下がり、最後の行の 1400-byte 経路では 1320 になります。

推測せずに測定してください。host A から実行します。

ping -M do -s 1392 -c 3 10.90.0.2

-M do はフラグメンテーションを禁止し、-s はペイロードサイズを設定します。そのため、1392 bytes のペイロードに ICMP ヘッダーの 8 bytes と IP ヘッダーの 20 bytes を加えると、ちょうど 1420 になります。応答が返れば、1420 は使用可能です。サイズがインターフェイスの許容値を超えると、ping は直ちに失敗し、到達した MTU を示すローカルエラーを返します。

ping: local error: message too long, mtu=1420

1420 未満の特定のサイズで応答が止まる場合は、両方の設定の [Interface] セクションに、成功したサイズを設定して再起動します。

MTU = 1380

Tailscale は独自に tailscale0 を 1280 に設定します。これは IPv6 経路が運べる最小値なので、このオプションを調整する必要はありません。

データベースの処理速度はトンネルによって低下しますか?

コストの主因は暗号化ではありません。WireGuard はカーネル内で動作し、現行の VPS の CPU では、その処理負荷はネットワークに比べて小さいものです。主な要因は距離です。各クエリは応答を受け取るまでに 1 回分の往復遅延を待つため、30 ms のリンク経由で 50 個の小さなクエリを順番に実行するページでは、データベースの処理内容に関係なく、ネットワーク待ちだけで 1.5 秒かかります。

移行前に、両方のマシンで測定してください。ホスト B で iperf3 -s を実行し、その後ホスト A から次を実行します。

ping -c 20 10.90.0.2
iperf3 -c 10.90.0.2

アプリケーションとデータベースは、同じリージョン、できれば同じプロバイダーのプライベートネットワーク内に配置し、互いに近づけてください。プロバイダーをまたぐトンネルは、遅延が問題にならない組み合わせに適しています。たとえば、アプリケーションサーバーとバックアップ用ホストの間で、日中に何千もの小さなクエリを処理するのではなく、夜間に 1 回の大きな転送を行う構成です。

片側を再起動するとどうなるか

再起動すると、手動で構築している間には現れない起動順序の問題が明らかになります。

トンネルが復旧しない。 起動時に開始されるのは、有効化された unit だけです。systemctl is-enabled wg-quick@wg0 は両方のマシンで enabled を出力する必要があります。Tailscale パッケージは tailscaled を有効化するため、インストール後は systemctl is-enabled tailscaledenabled を出力します。

トンネルより先にサービスが起動し、bind に失敗する。 10.90.0.2 で listen するよう設定したデータベースは、wg0 がまだ存在しないと起動に失敗します。その時点では、マシンにそのアドレスがないためです。PostgreSQL のログには、原因が明確に記録されます。

LOG:  could not bind IPv4 address "10.90.0.2": Cannot assign requested address
FATAL:  could not create any TCP/IP sockets

インターフェースの後にサービスを起動するよう指定します。まず systemctl list-units 'postgresql*' で unit 名を確認し、drop-in を作成します。

sudo install -d /etc/systemd/system/postgresql@17-main.service.d
sudo tee /etc/systemd/system/postgresql@17-main.service.d/after-wg.conf >/dev/null <<'EOF'
[Unit]
After=wg-quick@wg0.service
Wants=wg-quick@wg0.service
EOF
sudo systemctl daemon-reload

別の方法として net.ipv4.ip_nonlocal_bind = 1 もあります。これにより、マシンに存在しないアドレスにもプロセスが bind できます。クラッシュは止まりますが、インターフェースが存在するまでネットワークトラフィックは届きません。そのため、起動順序の指定を優先してください。

アプリケーションが再接続しない。 リモート側が再起動すると、トンネル経由で確立していたすべての TCP 接続が切断されます。接続を一度もテストしない接続プールは、切断済みの接続を払い出します。その結果、アプリケーションは最初のクエリで server closed the connection unexpectedly または connection reset by peer により失敗し、再起動するまで失敗し続けることがあります。リンクが切断されないと仮定せず、プールのヘルスチェックを有効にしてください(SQLAlchemy では pool_pre_ping=True、その他の環境では同等の test-on-borrow オプションを使用します)。ホスト上のコンテナにも同じ注意が必要です。compose スタックは、restart policy で指定されている場合にのみ再起動後に復旧します

FAQ

異なるプロバイダーの VPS 2 台をプライベートネットワークで接続できますか?

できます。ただし、プロバイダーのプライベートネットワークは、そのプロバイダー内のマシンにしか到達できません。2 台のパブリック IP アドレス間に WireGuard を設定するか、Tailscale などの管理型メッシュネットワークを使用します。どちらも通信を暗号化します。ここではパケットがパブリックインターネットを経由するため、暗号化が重要です。2 つのデータセンター間の距離に応じたラウンドトリップ時間が発生するため、データベースを遠隔側へ移す前に ping で測定してください。

プロバイダーのプライベートネットワークは暗号化されていますか?

いいえ。インターネットからルーティングできないアドレスが提供されるだけです。到達範囲もプロバイダーによって異なります。自分のアカウントのマシンだけがそのアドレスへ接続できる場合もあれば、同じデータセンター内のすべてのマシンが接続できる場合もあります。プロバイダーのドキュメントを確認し、データが機密性を持つ場合は、プライベートインターフェース上で WireGuard を実行してください。短い経路を維持しながら、暗号化を追加できます。

後で競合しないようにするには、どのプライベートサブネットを使用すべきですか?

どちらのマシンでも現在使用していない /24 を選びます。たとえば 10.90.0.0/24 です。ホームルーターやオフィスルーターが割り当てる 192.168.0.0/24 および 192.168.1.0/24、Docker が使用する 172.17.0.0/16 以降、Tailscale が使用する 100.64.0.0/10、さらにプロバイダーが独自のプライベートネットワーク用に割り当てる範囲は避けてください。両方のマシンで ip route show を実行し、どちらにも表示されない範囲を選びます。

トンネル経由の接続をデータベースが拒否するのはなぜですか?

トンネル経由の ping が成功し、nc -vz 10.90.0.2 5432Connection refused を返す場合、データベースはトンネルのアドレスで待ち受けていません。sudo ss -tlnp を実行し、ポートの横に表示されるアドレスを確認してください。127.0.0.1:5432 はネットワークからの接続を受け付けません。接続がハングしてタイムアウトする場合は別の問題です。ファイアウォールルールがパケットを破棄しています。そのため、wg0 で対象ポートへの受信を許可するルールを追加してください。

2 台の VPS 間のリンクに IP 転送と NAT は必要ですか?

いいえ。転送とマスカレードは、他のマシンに代わってトラフィックをルーティングするマシンで使用します。2 ホスト間のリンクで運ばれるのは、2 台自身を宛先とするトラフィックだけです。そのため、net.ipv4.ip_forward は 0 のままにし、NAT ルールは追加しません。一方のマシンの背後にある別のネットワークへ、もう一方のマシンから接続する必要がある場合に限り、転送が必要です。

#networking#wireguard#private-network#tailscale#multi-server