外出先のフリーWi-Fiを安全に使いたい時や、自宅・社内ネットワークへセキュアにアクセスしたい時、VPNがあると非常に便利ですよね。
最近はOpenVPNやWireGuardなどが人気ですが、今回は各デバイスに「専用のクライアントアプリ」をインストールしなくて済む「IKEv2」方式で構築してみます。さらに、クライアント端末への面倒な「証明書の事前インストール」も不要とし、IDとパスワードを入力するだけでMac、Windows、iPhone、Androidなどに標準搭載されている機能からサクッと接続できるのが最大の魅力です。
この記事では、自分がUbuntuサーバーとStrongSwanを使ってIKEv2 VPNサーバーを構築する際の手順を備忘録としてまとめています。
※なお、最近のStrongSwan公式では swanctl を用いた新しい設定方式へ移行しつつありますが、今回はあえてネット上に情報量が多く、直感的に設定しやすい従来からの strongswan-starter(ipsec.conf を使う方式)を採用して構築していきます。
Webサーバーを立てずにLet’s Encryptの証明書を自動更新する方法や、iOS(iPhone)特有の証明書の罠、さらにはWindowsやAndroidも含めた各OSで安定して接続するための「暗号化スイート(ike/esp)の最適化」といったハマりやすいポイントについても触れていますので、同じような環境を構築しようとしている方の参考になれば嬉しいです。
1. 前提条件とポートの開放
今回はVPN接続専用の環境として、新たに「さくらのVPS(さくらインターネット)」を借りました。作業の前提となる構成は以下の通りです。
- サーバー: さくらのVPS
- OS: Ubuntu Server (20.04 / 22.04 / 24.04等)
- ドメイン: サーバーのIPアドレスに紐付いた独自ドメイン(例:
vpn.example.com)
また、作業を始める前にファイアウォール(ufwなど)で以下のポートを開放しておきます。
- TCP 80番: Let’s Encryptの証明書取得および自動更新用
- UDP 500番: IKEv2の通信制御用
- UDP 4500番: NAT越えのIPsec通信用
※以降、ドメイン名(vpn.example.com)はご自身の環境に合わせて読み替えてください。
Ubuntu標準のファイアウォール(ufw)を使用している場合、以下のコマンドでポートを開放できます。(※さくらのVPSのパケットフィルターなど、コントロールパネル側にもファイアウォール設定がある場合は、そちらも同様に開放しておいてください)
sudo ufw allow 80/tcp
sudo ufw allow 500/udp
sudo ufw allow 4500/udp
2. StrongSwanのインストール
まずはメインとなるVPNサーバーソフトウェア「StrongSwan」と、関連するプラグイン一式をインストールします。前述の通り、今回は設定ファイル(ipsec.conf)が扱いやすい strongswan-starter を明示的に指定して導入します。
sudo apt update
sudo apt install strongswan strongswan-starter strongswan-pki libcharon-extra-plugins libcharon-extauth-plugins libstrongswan-extra-plugins
これで本体のインストールは完了ですが、安全な通信を行うためにはSSL/TLS証明書が必要になるため、次はその準備を進めます。
3. Let’s Encrypt証明書の取得と更新の自動化
無料で使える「Let’s Encrypt」を使って証明書を取得します。
わざわざApacheやNginxなどのWebサーバーを立てるのは手間に感じるので、Certbotの「Standaloneモード」を使い、必要な瞬間だけ一時的なサーバーを立ち上げて取得・更新する形をとります。
# Certbotのインストール
sudo apt install certbot
ここで証明書を取得しますが、iOSデバイスから接続する予定がある場合は少し注意が必要です。
💡 【備忘】iOS(iPhone/iPad)からの接続に関する特有の罠
現在、Let’s Encryptで普通に証明書を取得すると「ECDSA形式」というより軽量な証明書が発行されます。しかし、iOSのVPN機能(IKEv2)のセキュリティ要件との兼ね合いなのか、サーバー側がECDSA証明書を使っていると、iPhoneからの接続が(エラーメッセージ等も出ずに)弾かれてしまう現象に遭遇することがあります。
そのため、今回はコマンドで明示的に従来の「RSA形式」を指定して取得することで、この問題を回避します。
実際にRSA形式で取得する際のコマンドは以下の通りです。(※ドメイン名はご自身の環境に合わせて読み替えてください)
sudo certbot certonly --standalone -d vpn.example.com --key-type rsa
証明書の配置と自動更新(フック)の設定
StrongSwanの設定からLet’s Encryptのディレクトリを直接指定したくなりますが、Ubuntuの「AppArmor」というセキュリティ機構の制限により、権限エラーで起動できなくなってしまいます。
そのため、証明書はStrongSwanの所定のディレクトリ(/etc/ipsec.d/)にコピーして使用します。
UbuntuでCertbotをインストールすると、裏側では自動的に certbot.timer というsystemdのタイマーが稼働し、有効期限が近づくと定期的に証明書の更新処理を走らせてくれます。
さらに、Certbotには「証明書が正常に更新されたタイミングでだけ、特定のスクリプトを実行する」という便利な機能(renewal-hooks)が備わっています。/etc/letsencrypt/renewal-hooks/deploy/ というディレクトリ内にスクリプトを配置しておくだけで、更新完了時に自動でそれを実行してくれる仕組みです。
今回はこの機能を利用して、証明書が新しくなるたびに、所定のディレクトリへのコピー作業とStrongSwanの再読み込みを完全自動化しておきます。
sudo nano /etc/letsencrypt/renewal-hooks/deploy/strongswan-reload.sh
以下のスクリプトを作成します。
#!/bin/bash
# 証明書をStrongSwanのディレクトリにコピー
cp -f /etc/letsencrypt/live/vpn.example.com/fullchain.pem /etc/ipsec.d/certs/
cp -f /etc/letsencrypt/live/vpn.example.com/chain.pem /etc/ipsec.d/cacerts/
cp -f /etc/letsencrypt/live/vpn.example.com/privkey.pem /etc/ipsec.d/private/
# StrongSwanのサービスを止めずに証明書だけ再読み込みさせる
ipsec rereadall
作成後、実行権限を付与し、初回の証明書配置のために手動で1回実行しておきます。
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/strongswan-reload.sh
sudo /etc/letsencrypt/renewal-hooks/deploy/strongswan-reload.sh
これで、証明書の準備と自動化の仕組みが完成しました。
4. StrongSwanの設定
準備できた証明書を使って、StrongSwanの設定ファイル(ネットワークルールと認証情報)を編集していきます。
ipsec.conf の編集
ここで、通信の暗号化スイート(ike と esp)を明示的に指定しておきます。省略しても自動で選ばれますが、明示的に指定して末尾に ! をつけることで、「古くて弱い暗号化方式」での接続を完全に拒否し、主要OS(iOS/Mac/Windows/Android)が好む最新の安全で高速な暗号化通信だけを強制することができます。
sudo nano /etc/ipsec.conf
config setup
uniqueids=never
conn ikev2-vpn
auto=add
mobike=no
compress=no
type=tunnel
keyexchange=ikev2
ike=aes256gcm16-prfsha256-ecp256,aes256-sha256-modp2048!
esp=aes256gcm16-ecp256,aes256-sha256!
fragmentation=yes
forceencaps=yes
rekey=yes
left=%any4
leftid=@vpn.example.com
leftcert=fullchain.pem
leftsendcert=always
leftsubnet=0.0.0.0/0
right=%any4
rightid=%any
rightauth=eap-mschapv2
rightsourceip=10.10.10.0/24
rightdns=8.8.8.8,8.8.4.4
rightsendcert=never
eap_identity=%identity
reauth=no
ikelifetime=24h
lifetime=24h
dpddelay=30s
dpdtimeout=120s
dpdaction=clear
※ leftid の部分はご自身のドメイン名に合わせ、先頭に @ を付けます。
💡 【ちょっとした注意ポイント・暗号化設定(ike/esp)について】
実は今回、構築の中で一番苦労したのがこの ike と esp(暗号化スイート)の指定でした。
ここを省略したり、とりあえず何でも繋がるようにと長いリストを書いてしまうと、逆に「数時間後に突然切断される(鍵更新:rekeyの失敗)」や「WindowsやAndroidからだけなぜか繋がらない」といった謎のトラブルにハマりやすくなります。
今回設定した短い2行は、iOS、Mac、Windows、Androidのすべてが共通して対応できる、安全で高速な暗号化方式(最大公約数)だけを厳選したものです。末尾に ! をつけることで「これ以外の古い暗号は絶対に受け付けない」と強制させており、これにより全デバイスで非常に安定して接続・鍵更新ができるようになりました。もし接続が安定しない場合などは、この指定を見直してみることをおすすめします。
💡 【ちょっとした注意ポイント・実運用を安定・手軽にするこだわり】
今回の設定では、スマホやPCで快適に実運用するために、いくつかこだわりのチューニングを施しています。特に複数のOSで安定・手軽に利用するために以下の項目が重要でした。
- Let’s Encrypt(公的証明書)の採用理由
「オレオレ証明書(自己署名証明書)」でVPNを立てると、接続するすべての端末に事前に証明書ファイルを送り込んでインストール・信頼設定をするという非常に手間な作業が発生します。今回はLet’s Encryptという、初めから各OSに信頼されている公的証明書を使用することで、クライアント側の証明書インストール作業を完全にゼロにしています。 rightauth=eap-mschapv2(ID/パスワード認証)
公的証明書とこのパスワード認証(EAP)を組み合わせることで、標準的なWebサイトにログインするような感覚で、IDとパスワードのみで即座にVPN接続を開始できます。uniqueids=never(同時接続の許可)
同じユーザー名で複数台(MacとiPhoneなど)から同時に接続しても、それぞれを独立したセッションとして扱い、切断を防ぎます。mobike=no(WindowsやNAT環境への対応)
モバイル向けの回線切り替え機能をオフにしています。一見不便そうですが、これをオフにすることでWindows標準クライアントとの相性問題や、NAT環境下での不安定な挙動が劇的に改善されます。left/right=%any4
IPv4専用に固定することで、モバイル回線でのIPv6競合による通信トラブルを回避しています。reauth=no/rekey=yes/lifetime=24h
一瞬通信が切れる「再認証」を避け、裏側での「鍵更新」のみを行うことで、バッテリー消費を抑えつつシームレスな通信を維持します。
ipsec.secrets の編集
秘密鍵の指定と、接続するためのユーザー名・パスワードを設定します。接続を行う各デバイスの設定画面に、ここで設定した情報を入力することになります。
sudo nano /etc/ipsec.secrets
# 秘密鍵の指定(RSAを指定)
: RSA "privkey.pem"
# 接続用のアカウント情報(ユーザー名 : EAP "パスワード")
my_vpn_user : EAP "my_super_secret_password"
strongswan.conf の編集(通信詰まり対策と安定化)
接続の安定性を高めるために、全体設定ファイルも調整します。特にパケットサイズが大きくなることで特定のサイトが見られなくなる「パケット詰まり(MTU/MSS問題)」を防ぐ記述を追加します。
sudo nano /etc/strongswan.conf
charon {
load_modular = yes
keep_alive = 15
plugins {
include strongswan.d/charon/*.conf
# MTUとMSSの制限(パケット詰まり対策)
kernel-netlink {
mtu = 1380
mss = 1340
}
}
}
include strongswan.d/*.conf
5. ファイアウォールとパケット転送の設定
VPN端末がサーバー経由でインターネットへ抜けられるように、IPv4転送とNATの設定を行います。
sudo nano /etc/sysctl.conf
net.ipv4.ip_forward=1 を有効化し、反映させます。
sudo sysctl -p
次に、ufw でパケット転送レベルでもMSSの最適化を行い、IPsec通信を許可します。
sudo nano /etc/ufw/before.rules
# --- (ファイルの先頭付近に追記) ---
*nat
-A POSTROUTING -s 10.10.10.0/24 -o eth0 -m policy --pol ipsec --dir out -j ACCEPT
-A POSTROUTING -s 10.10.10.0/24 -o eth0 -j MASQUERADE
COMMIT
*mangle
-A FORWARD --match policy --pol ipsec --dir in -s 10.10.10.0/24 -o eth0 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
COMMIT
# --- (既存の *filter ブロック内に追記) ---
-A ufw-before-forward --match policy --pol ipsec --dir in --proto esp -s 10.10.10.0/24 -j ACCEPT
-A ufw-before-forward --match policy --pol ipsec --dir out --proto esp -d 10.10.10.0/24 -j ACCEPT
ufwを再読み込みし、サービスを再起動して完了です。
sudo ufw reload
sudo systemctl restart strongswan-starter
💡 おまけ:MTUとMSSの設定値が2箇所で違う理由
strongswan.conf(1340)と ufw(1360)で数値が違うのは、入り口で厳しめに絞り、出口で最終チェックをするという「二段構えの防衛策」をとっているからです。これにより、環境によらずパケット詰まりのない安定した通信を実現しています。
6. 各デバイスからの接続確認
サーバー側の準備ができたら、手元のデバイスから接続してみましょう。OS標準のVPN設定画面(iPhoneなら「設定 > 一般 > VPN」など)から設定を作成します。
- タイプ / VPN構成: IKEv2
- 説明: 任意の名前(例: 自宅VPN)
- サーバー: サーバーのドメイン名(例:
vpn.example.com)またはIPアドレス - リモートID:
ipsec.conf内のleftidで指定した文字列。※今回の例なら、先頭の@を除いたvpn.example.com - ローカルID: 空欄のままでOK
- ユーザー認証: ユーザー名(または証明書ではなく「設定」等)を選択
- ユーザー名 / パスワード:
ipsec.secretsで設定したもの
これで接続ボタンを押し、「接続済み」になれば成功です!
前述の通り、公的証明書(Let’s Encrypt)を正しくセットアップしているため、クライアント側で証明書をインストールしたり、プロファイルをダウンロードしたりする手間は一切ありません。IDとパスワードを入力するだけで、すぐに安全な通信が始まります。
まとめ
iOS特有の罠やWindowsとの相性、パケット詰まりなど、実運用でハマるポイントはいくつかありましたが、一度構築してしまえば専用アプリもクライアント側の証明書も不要で、主要OSすべてからID/パスワードのみで快適に繋がる最強のVPN環境が手に入ります。ぜひ試してみてください。