Ubuntu日和

【第55回】UbuntuとWireGuardで自宅にVPNを導入しよう!

Mac版WireGuardの画面

 ここ数年は働き方改革やコロナ禍の影響もあり、業務上でVPNを使用する機会が増えた人も多いのではないだろうか。そんな背景もあり、VPNといえばリモートワークのお供というイメージがあるかもしれない。だが業務だけでなく、自宅サーバー勢にとってもVPNの導入はメリットがある。

 たとえば自宅内で運用しているサーバーに、外出先からもアクセスしたいというケースはよくあるだろう。この場合、サービスごとにルーターのポートを解放して、サーバーに転送するのが一般的だ。だがこの方法にはいくつか問題がある。

 まずそもそも論として、インターネット越しに利用することを想定していないサービスや、公開できないサービスには使えない[^1]。仮にインターネット越しに利用できるサービスであったとしても、外部からのアクセスを受け付ける以上、不正アクセス対策には十分に気をつける必要が出てくる。またこの方法では、同じポートを複数のサービスで使うことができない。つまり用途が異なるWebサーバーを2台動かしたい場合、80番や443番ポートの奪い合いになってしまうのだ。

 だがVPNを使えば、インターネットを経由して端末をLANに接続できる。つまり自宅外にいながら、あたかも自宅内にいるかのようにサービスを利用できるというわけだ。こう聞くと、とても便利そうな気がしてきたのではないだろうか。

 今回はUbuntuと「WireGuard」というソフトウェアを使い、自宅内にVPNサーバーを構築する方法を紹介しよう。

VPNとは

 まずVPN(Virtual Private Network)について簡単に説明しておこう。Virtual Private Networkとは「仮想の専用線」を意味する言葉だ。そしてわざわざ「仮想の」と言うからには、当然「仮想ではない」専用線というものが存在する。

 専用線とは、インターネットのような公衆のネットワークを介さずに拠点同士を直接接続する、専用の回線のことだ。たとえば東京のオフィスと大阪のオフィスを、専用回線で直結することをイメージしてみてほしい。専用線のメリットは、文字通り回線を占有できるため、インターネットよりも高いセキュリティや、高品質な通信が期待できる点だ。だが簡単に予想できると思うが、専用の設備が必要となるため、非常に大きなコストがかかる。また当然だが、モバイル端末などからは利用できない。

 そこで登場するのが「仮想の」専用線であるVPNだ。VPNはインターネット上に、プライベートなネットワークを仮想的に構築する技術の総称だ。この仮想的なネットワークは、通信を行なう両者で占有され、その通信は暗号化される。これによってインターネットを使用しているにもかかわらず、第三者による盗聴などを防ぎ、セキュアな通信を実現できるというわけだ。VPNはインターネットを利用するため、別途回線を敷設する必要がない。

 また機材面においては、VPNに対応した機材(ルーターなど)を接続先に設置するだけでよいため、非常に低コストで導入できる。

 VPNに接続された端末同士は、あたかも同じネットワークに接続しているように振る舞う。つまり自宅のPCやモバイル端末を、オフィスのLANに接続しているかのように扱えるわけだ。

 クラウドサービスのメリットは利用する場所を選ばないことなのだが、実際の業務においては、残念ながらまだまだ「オフィスにいないと仕事ができない」というケースが多い。たとえば業務に必要なデータが社内のファイルサーバーにしかなかったり、客先のシステムにアクセスするためには、オフィスの固定IPアドレスが必要だったりといった具合だ。そこで、自宅内のPCをオフィスに接続してリモートワークを行なうといった目的で、VPNは広く利用されている。

 実際に総務省による「テレワークセキュリティガイドライン」においても、VPNは「オフィス内と同等のセキュリティレベルを確保し、オフィス内にいるのと同等の業務が可能な方式」と紹介されている。

WireGuardとは

 VPNを構築するためには当然だが、端末が接続するためのVPNサーバーが必要となる。これには市販のVPN対応ルーターなどを使うのが一般的だが、これをUbuntuサーバーと、VPNソフトウェアである「WireGuard」を使って実現しようというのが今回のテーマだ。

 今回紹介するWireGuardは、複数存在するVPN実装の1つだ。もちろんオープンソースであり、現在ではLinuxカーネルにマージされていて、Linuxのカーネルモジュールとして動作する。そのためLinuxベースのシステムであれば、非常に導入しやすいVPNだと言えるだろう。

 こう聞くとLinux専用のように思われてしまうかもしれないが、Windows/macOS/iOS/Android向けのクライアントも用意されており、利用するだけならば環境を選ばないため安心してほしい。

 WireGuardは、従来煩雑になりがちだったVPNをより簡単に、具体的にはSSHサーバーと同程度の手間でセットアップできることを目標としている。詳しい手順は後述するが、実際にサーバーとクライアントで公開鍵を交換し、サーバー管理者がIPアドレスを払い出すだけでセットアップできる。そしてこれだけシンプルなシステムでありながらも非常に高速であり、OpenVPNやIPsecよりも高いパフォーマンスを謳っている。

 ここからはWireGuardサーバーとクライアントをそれぞれ設定し、VPNコネクションを張るまでをステップバイステップで解説する。WireGuardはサーバーとクライアント双方で鍵ペアを作成し、それを交換して設定ファイルに記述するという都合上、サーバーでの作業とクライアントでの作業が並行して発生する。そのため手順が少し分かりにくいかもしれないが、手順や設定ファイルの内容を取り違えないよう気をつけて作業しよう。コマンドの実行例の前には、どちらで実行するのかが分かりやすいよう、「(server)」「(client)」の記述を追加しているため、これも参考にしてほしい。

 おおまかな手順を図にまとめてみた。最初にざっくりと作業の流れを把握しておくといいだろう。

WireGuardのセットアップの流れ

WireGuardサーバーのインストールと鍵ペアの生成

 まずはサーバーのインストールだ。今回WireGuardサーバーにはUbuntu 24.04 LTSのサーバー版を利用した。言うまでもないがサーバーは、IPアドレスを固定化しておこう。netplanによるIPアドレスの固定化は、【第29回】で解説している。なお筆者の例では、WireGuardサーバーのIPアドレスは「192.168.1.20」としたので、自分の環境に合わせて適宜読み替えてほしい。

 WireGuardは以下のコマンドでインストールできる。

(server)$ sudo apt install -y wireguard

 また詳しくは後述するが、以下のコマンドを実行してカーネルのパラメータを変更しておこう。これはパケットの転送を明示的に許可する設定だ。

(server)$ sudo sed -i.bak -e 's/#\(net.ipv4.ip_forward=1\)/\1/' /etc/sysctl.conf
(server)$ sudo sysctl -p

 続いて、サーバーが利用する鍵ペアを生成しよう。まず「wg genkey」コマンドを使って、秘密鍵を生成する。生成された秘密鍵は、後述する設定ファイルに記述すればいいため、ここで別のファイルとして保存しておく必要はない。

 だが万一内容を失念してしまった場合、鍵の再生成とクライアントへの公開鍵の再配布という作業が発生してしまい、面倒になる。そこで「/etc/wireguard/server.key」というファイルに保存しておくことにした。

 続いて秘密鍵から公開鍵を生成する。これには秘密鍵ファイルの内容を「wg pubkey」コマンドにパイプしてあげればいい。すると公開鍵が出力されるので、これを「/etc/wireguard/server.pub」として保存することにした。最後に秘密鍵ファイルと公開鍵ファイルの両方のパーミッションを600とし、第三者に盗み見られないようにしておこう。

(server)$ wg genkey | sudo tee /etc/wireguard/server.key
(server)$ sudo cat /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pub
(server)$ sudo chmod 600 /etc/wireguard/server.{key,pub}

WireGuardクライアントのインストールと鍵ペアの生成

 今度はクライアント側のインストールだ。クライアントにはUbuntu 24.04 LTSのデスクトップ版を利用した。クライアントもサーバーと同じく、wireguardパッケージをインストールすればいい。もちろんクライアント側では、IPアドレスの固定やパケット転送の許可は不要だ。

(client)$ sudo apt install -y wireguard

 クライアント側でも秘密鍵と公開鍵を生成する。ファイル名を変えた以外は、サーバー側の手順と同一だ。

(client)$ wg genkey | sudo tee /etc/wireguard/client.key
(client)$ sudo cat /etc/wireguard/client.key | wg pubkey | sudo tee /etc/wireguard/client.pub
(client)$ sudo chmod 600 /etc/wireguard/client.{key,pub}

 WireGuardでは、サーバーの設定ファイルにはクライアントの公開鍵を、クライアントの設定ファイルにはサーバーの公開鍵を記述する必要がある。なのでサーバーとクライアントの双方で鍵ペアの生成が完了したら、お互いに公開鍵を交換しておこう。企業などでは、情シスの担当者とやりとりする必要があるだろう。個人で設定している場合は、何らかの方法で、それぞれのマシンに公開鍵をコピーしておこう。

WireGuardサーバーの設定

 準備ができたので、サーバーに具体的な設定を行なおう。WireGuardの設定ファイルは、「⁠/etc/wireguard」ディレクトリ内に、「作成するインターフェイス名.conf」という名前で作成するのが一般的だ。今回はインターフェイス名を「wg0」とした。

 また同時に、WireGuardのネットワークに割り当てるIPアドレスのレンジ、サーバーのインターフェイスと、クライアントに割り当てるIPアドレス、サーバーが待ち受けるポート番号を決めておこう。筆者はネットワークに「10.0.0.0/24」を割り当てた。そしてサーバーのインターフェイスには「10.0.0.1⁠」⁠、クライアントのインターフェイスには「10.0.0.2」のIPアドレスを割り当てることにした。そして待ち受けるポート番号は「51000」だ。

 必要な情報が集まったので、「/etc/wireguard/wg0.conf」というファイルを、以下の内容で作成しよう。

[Interface]
PrivateKey = 2LZDhfbmeNj5B0XXXXXXXXXXXXXXXXXXXXX=  ← ここに「サーバー」の「秘密鍵」をコピペする
Address = 10.0.0.1                                 ← サーバーのインターフェイスに割り当てるIPアドレス
ListenPort = 51000                                 ← サーバーが待ち受けるポート番号
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp1s0 -j MASQUERADE

[Peer]
PublicKey = twXWXixw5FAUIHdXXXXXXXXXXXXXXXXXXXXX=  ← ここに「クライアント」の「公開鍵」をコピペする
AllowedIPs = 10.0.0.2/32                           ← このクライアントに割り当てるIPアドレス

 PostUpとPostDownは、WireGuardが起動する前後に実行するコマンドの指定だ。実はデフォルトでは、WireGuardサーバーに接続してきた端末は、WireGuardのネットワーク(ここでは10.0.0.0/24の空間)でしか通信ができない。だたVPNの主目的は、組織内部のLANに接続したり、そこからインターネットへ出ていくことだろう。なのでWireGuardネットワークから外に出られないのでは意味がない。そこでWireGuardの起動時にiptablesコマンドを実行し[^2]、IPマスカレードを行なう設定を追加しているのだ(PostDownは終了時の後片付けだ)。先ほどカーネルパラメータを変更し、パケットの転送を許可したのは、この設定を行なうためだったというわけだ。

 iptablesコマンドは少々複雑だが、「FORWARD -i wg0」の部分にWireGuardのインターフェイスを、「POSTROUTING -o enp1s0」の部分に転送先のNICの名前を指定すればいい。NICの具体的な名前は、networkctlコマンドなどで確認しておこう。

(server)$ networkctl
IDX LINK   TYPE     OPERATIONAL SETUP
  1 lo     loopback carrier     unmanaged
  2 enp1s0 ether    routable    configured

2 links listed.

 なおVPNサーバーをLAN内で動かす場合は、ルーターに静的ルーティングの設定が必要になる。だがそこの解説は本記事の領分を越えてしまう上、ルーターの機種ごとに異なるため、割愛させていただく。基本的には家庭内のWebサーバーをインターネットに公開する時などと同様に、WireGuardのUDPポート(ここでは51000番)をルーターで解放し、サーバーの固定IP(本記事の例では192.168.1.20)に向けてルーティングを設定すればいい。

WireGuardクライアントの設定

 続いてクライアント側の設定を行なおう。サーバーと同じく「/etc/wireguard/wg0.conf」というファイルを、以下の内容で作成しよう。基本的にはサーバーの設定と「Interface」と「Peer」が逆になっていると思ってもらえればいい。

[Interface]
PrivateKey = +BV7K1NCTY9KXXXXXXXXXXXXXXXXXXXXXXXXXXXX ← ここに「クライアント」の「秘密鍵」をコピペする
Address = 10.0.0.2                                    ← サーバーから割り当てられたクライアントのIPアドレス

[Peer]
PublicKey = y0sZcvHx3YwckXXXXXXXXXXXXXXXXXXXXXXXXXXXX ← ここに「サーバー」の「公開鍵」をコピペする
EndPoint = 203.0.113.XXX:51000                        ← VPNのエンドポイントとなるサーバーのIPアドレスとポート番号
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24              ← WireGuardを経由して通信する先のIPアドレス

 公開鍵と秘密鍵、ポート番号やIPアドレスについては、説明するまでもないだろう。ただしクライアントとサーバー、公開鍵と秘密鍵は取り違えやすいため、気をつけて設定してほしい。クライアントのInterfaceに設定するIPアドレスは、サーバーのPeerのAllowedIPsに指定したものと揃える必要がある。

 ここで新しく登場する設定項目が「EndPoint」だ。これは文字通り、VPNのエンドポイント(クライアントが接続する先)を指定する。具体的にはインターネットからサーバーに到達可能なIPアドレスとポート番号だ。家庭内にあるサーバーであれば、大抵の場合はプロバイダからルーターに割り当てられたIPアドレスになるだろう。

 「AllowedIPs」は、クライアントがWireGuardを経由して通信する先のIPアドレスだ。ここではWireGuardネットワークである「10.0.0.0/24」と、筆者の家庭内LANである「192.168.1.0/24」を指定している。クライアントは、このアドレス宛ての通信が発生すると、WireGuardインターフェイスを通じてVPNにトラフィックを流そうとする。なのでここに「0.0.0.0/0(すべてのIP)」を指定すると、文字通りすべての通信がVPN経由で行なわれるようになる。

 冒頭で述べたように、客先のシステムにアクセスするためには、オフィスの固定IPアドレスが必要だというケースはよくあるだろう。そういう時はこの設定を行なえば、インターネットに出ていくあらゆる通信において、VPN先のネットワークを経由することができる。

WireGuardサーバーの起動

 サーバー、クライアント双方の設定が完了したら、サーバーを起動しよう。以下のコマンドを実行して、WireGuardのサービスを有効化する。

(server)$ sudo systemctl enable --now wg-quick@wg0

 WireGuardはwg-quickというラッパースクリプトを経由して起動するのだが、よく見るとsystemdのユニット名に「@」が含まれているのが分かるだろう。これはsystemdのユニットファイルに、引数を与えるための書き方だ。WireGuardでは引数としてインターフェイス名を渡しており、これによって同じユニットファイルを、複数のインターフェイスで使い回すことができるというわけだ。

 起動が完了したら、networkctlコマンドでwg0インターフェイスの状態を確認してみよう。以下のようにStateがroutableになっており、設定ファイルに記述したIPアドレスが割り当てられていればOKだ。

(server)$ networkctl status wg0
 ● 3: wg0
                   Link File: /usr/lib/systemd/network/99-default.link
                Network File: n/a
                       State: routable (unmanaged)
                Online state: unknown
                        Type: wireguard
                        Kind: wireguard
                      Driver: wireguard
                         MTU: 1420 (max: 2147483552)
                       QDisc: noqueue
IPv6 Address Generation Mode: none
    Number of Queues (Tx/Rx): 1/1
                     Address: 10.0.0.1

Jul 02 11:20:36 noble systemd-networkd[446]: wg0: Link UP
Jul 02 11:20:36 noble systemd-networkd[446]: wg0: Gained carrier

VPNへの接続

 ここまででWireGuardのセットアップは完了だ。それではクライアントからサーバーに対し、VPNコネクションを張ってみよう。今回は、筆者の自宅LAN内に構築したWireGuardサーバーに対し、ルーターでUDPの51000番ポートを解放し、スマホのテザリングを経由して、ノートPCから接続してテストを行なった。

 クライアント上で、root権限で「wg-quick up」コマンドを実行しよう。引数としてWireGuardのインターフェイス名(今回はwg0)を指定する。設定に間違いがなければ、これだけで接続は完了する。

(client)$ sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.0.2 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 192.168.1.0/24 dev wg0
[#] ip -4 route add 10.0.0.0/24 dev wg0

 ipコマンドで経路を調べてみよう。見ての通り、10.0.0.0/24(WireGuardネットワーク)と192.168.1.0/24(LAN)向けのパケットは、wg0インターフェイスを経由するようルートが設定されていることが分かる。

(client)$ ip r
default via 192.168.222.248 dev wlp3s0 proto dhcp src 192.168.222.108 metric 600
10.0.0.0/24 dev wg0 scope link                                 ← wg0を経由するWireGuardネットワーク向けのルートが追加されている
192.168.1.0/24 dev wg0 scope link                              ← wg0を経由してLANにアクセスするためのルートが追加されている
192.168.222.0/24 dev wlp3s0 proto kernel scope link src 192.168.222.108 metric 600

 この状態で、LAN内のコンピューターに対してpingを打ってみよう。またファイルサーバーやWebアプリが動いているのであれば、実際にアクセスしてみてもいいだろう。

(client)$ ping 192.168.1.5
PING 192.168.1.5 (192.168.1.5) 56(84) bytes of data.
64 bytes from 192.168.1.5: icmp_seq=1 ttl=63 time=704 ms
64 bytes from 192.168.1.5: icmp_seq=2 ttl=63 time=337 ms
64 bytes from 192.168.1.5: icmp_seq=3 ttl=63 time=352 ms
(...略...)
ファイルブラウザからLAN内のファイルサーバーの共有フォルダにアクセスしてみた。スマホ回線経由なため少々重たいが、問題なくデータを参照できた
LAN内で動かしているJellyfinにもブラウザからアクセスしてみた。こちらも問題なくMP3の再生ができた

 サーバーで「wg」コマンドを実行すると、現在のコネクションの様子が表示される。コネクションが確立し、実際にデータの転送が行なわれていることが分かる。

(server)$ sudo wg
interface: wg0
  public key: y0sZcvHx3YwcXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  private key: (hidden)
  listening port: 51000

peer: twXWXixw5FAUIHdEzNXXXXXXXXXXXXXXXXXXXXXXXXXX
  endpoint: 133.159.XXX.XXX:47820
  allowed ips: 10.0.0.2/32
  latest handshake: 1 minute, 37 seconds ago
  transfer: 1.31 MiB received, 26.92 MiB sent

 コネクションを切断するには、クライアントで「wg-quick down」コマンドを実行しよう。もちろんupの時と同様に、引数にはインターフェイス名を指定する。業務で利用しているようなVPNコネクションは、つなぎっぱなしにしておくのは好ましくない[^3]。特にすべてのインターネットアクセスをVPN経由にしている場合はなおさらだ。使い終わったらすみやかに切断しておこう。

(client)$ sudo wg-quick down wg0

GUIからWireGuardを使う

 デスクトップ環境からVPNにつなぐのであれば、いちいちターミナルからコマンドを実行するのは面倒くさいだろう。実はUbuntu 24.04 LTSでは、GUIから簡単にWireGuardに接続できるようになっているので、こちらの機能を利用するのがおすすめだ。

 まずUbuntuの設定から「ネットワーク」を開き、新しいVPN設定を作成しよう。

「VPN」という項目にある「+」ボタンをクリックする

 すると「VPNを追加」というダイアログが表示される。ここで「WireGuard」を選択し、ダイアログに必要な情報を入力していってもよいのだが、今回は既に設定ファイルを作成済みのため、「/etc/wireguard/wg0.conf」をそのまま流用しよう。ただし一般ユーザーでは「/etc/wireguard」ディレクトリの中身を読むことができない。そこでいったんホームディレクトリに、このファイルをコピーしておこう。

(client)$ sudo cp /etc/wireguard/wg0.conf ~/

 「ファイルからインポート」を選択し、ホームディレクトリにコピーしたwg0.confを読み込めば、それだけで設定は完了だ。

インポートが完了したら、コピーしたwg0.confは消しておくといいだろう004.png

 VPNを追加すると、デスクトップ右上のシステムメニューに「VPN」の項目が新しく追加される。あとはWi-FiやBluetoothのオン/オフと同様に、このボタンをクリックするだけで、VPNコネクションの生成や切断が可能になるというわけだ。

VPNのボタンが新しく表示されるようになった状態

 このように、WireGuardは簡単に使い始めることができる。また冒頭で述べたように、Windows/macOS/iOS/Androidでも利用可能だ。そしてVPNサーバー自体は大したマシンスペックを必要としないため、それこそRaspberry PiのようなSBCでも問題なく運用できる。自宅内でさまざまなサーバーを動かしている人や、そろそろVPNを導入しないとなーと考えている企業の情シスの人などは、ぜひ本記事を参考に、WireGuardを試してみてほしい。


[^1]: たとえばバージョン3.0以前のSMB(Windowsファイル共有)などだ。以前のSMBはインターネットで利用することを前提に設計されていなかったため、SMB 3.0に対応していない古いNASなどをインターネットから利用したいと思ったら、VPNなどを併用し、セキュアな通信経路を確保する必要がある。

[^2]: 現在のUbuntuにおけるパケットフィルタリングは、内部的にはiptalbesの後継であるnftablesが担っている。とはいえ昔ながらのiptablesコマンドも依然として利用できるため、馴染みの深いiptablesコマンドを使う例を紹介した。ここはもちろんnftables(nftコマンド)の記法に置き換えても問題ない。

[^3]: 筆者が以前の職場で社内インフラ担当をしていた時には、「すみません、自宅からVPNを繋いだまま紳士向けのサイトを見てしまいました……」といった報告(あるいは懺悔)を受けたことが何度かある。みんなも気をつけよう。