大部分由AI编写,本人实测可用。记录配置 + 这次调试踩过的全部坑。
这个 VPN 能做什么
- [x] 在外用手机连家里 / 公司的 Debian 服务器
- [x] 客户端访问内网(
10.0.0.0/16)走 VPN,其他流量直连(分流,不影响刷视频速度) - [x] Apple / Windows / Android 用各自系统自带的 VPN 客户端就能连(也可装 strongSwan App)
- [x] ZeroSSL 真证书,90 天自动续期,不用管
- [x] 用户名 + 密码登录,加用户改一行 secrets 即可
整体架构
手机/笔记本(外网)
│
│ IKEv2 加密隧道 (UDP 500 / 4500)
▼
家里路由器 (公网IP, 10.0.0.1)
│ 端口转发 UDP 500 / 4500 -> Debian
▼
Debian 服务器 (内网 10.0.0.X)
├── strongSwan: 给客户端分配 10.10.10.X 的 VPN IP
├── 告诉客户端"只有 10.0.0.0/16 走 VPN"(分流)
└── iptables MASQUERADE: VPN 流量伪装成自己出去前提(任一不满足都搭不起来):
- Debian 服务器必须在 10.0.0.0/16 网段内(不然访问不到内网)
- 路由器有公网 IP(纯 IPv6 也行,只要域名能解析到公网可达 IP)
- 你有域名,DNS 托管在 DNSPod(本教程用 DNSPod token 申请证书;其他 DNS 商需要换 acme.sh 插件,详见 §6.9)
§0 准备工作
0.1 在 Debian 上做环境自检
cat /etc/debian_version # Debian 11/12 都行
whoami # 是 root 或能 sudo
ip -4 addr show | grep inet # 服务器内网 IP, 应该是 10.0.0.X
ip route | grep default # 默认网卡名, 一般是 eth0 / ens18 / enp1s0
timedatectl # 时间必须正确, "System clock synchronized: yes"记下网卡名(例:eth0)和内网 IP(例:10.0.0.50),后面要用。
timedatectl set-ntp true 强制开启 NTP。
0.2 拿 DNSPod API Token
- 登录 console.dnspod.cn
- 头像 → 用户中心 → API 密钥 → DNSPod Token
- 创建密钥,保存好 ID 和 Token(只显示一次)
格式:
ID: 123456
Token: abcdef1234567890abcdef1234567890§1 域名解析 + 路由器端口转发
1.1 加 A 记录
在 DNSPod 控制台:
| 主机记录 | 类型 | 记录值 |
|---|---|---|
| vpn(或任意) | A | 你的路由器公网 IP |
确认生效:
nslookup vpn.example.com # 替换成你的域名下文所有 vpn.example.com 都替换成你的实际域名。
1.2 路由器端口转发
| 协议 | 外部端口 | 内部 IP | 内部端口 |
|---|---|---|---|
| UDP | 500 | Debian IP | 500 |
| UDP | 4500 | Debian IP | 4500 |
不需要 80 端口(本教程用 DNS-01 验证,绕开 80 / 443)。
§2 装包
apt update
apt install -y strongswan strongswan-starter strongswan-pki \
libcharon-extra-plugins libcharon-extauth-plugins libstrongswan-extra-plugins \
curl socat iptables-persistentstrongswan-starter。新版 Debian 上 strongswan 元包不再自动带 starter,缺了它就没有 ipsec 命令和 strongswan-starter.service。见 §6.3 的现象。
iptables-persistent 安装时弹的两个保存框选 Yes。
装完自检:
ipsec --version
# 应输出: Linux strongSwan U6.x.x ...
systemctl list-unit-files | grep strongswan
# 应看到 strongswan-starter.service这一步不需要启动 strongSwan。后续步骤是先把证书 + 配置写到磁盘,strongSwan 只在最后启动时一次性读入,服务在不在跑无所谓。
§3 申请 ZeroSSL 证书(不用 Let's Encrypt)
这是这次踩得最痛的坑,务必看说明:
2025-2026 起 Let's Encrypt 把所有中间证书都换成了 YR1/YR2/YE1/YE2 系列,挂在 LE 自家新根 Root YR / Root YE 下。安卓 / 大部分国产 ROM 信任库里没有这两个根,客户端会直接拒绝:no trusted public key found for 'vpn.example.com' + AUTH_FAILED。
--preferred-chain "ISRG Root X1" 也救不回来,LE 不再给新签的证书提供 X1 老链。
ZeroSSL 同样免费、90 天自动续期、acme.sh 完美支持,签出来的证书挂在 USERTrust RSA Certification Authority(1999 年部署的根,所有设备包括十年前的旧安卓都信任),不会踩这个雷。
3.1 装 acme.sh
# [!] 邮箱必须是纯 ASCII(英文/数字), 中文会导致 ACME 注册失败 (见 §6.1)
curl https://get.acme.sh | sh -s email=your-email@example.com
source ~/.bashrc3.2 切默认 CA 到 ZeroSSL
~/.acme.sh/acme.sh --set-default-ca --server zerossl3.3 配置 DNSPod Token
export DP_Id="123456" # §0.2 拿到的 ID
export DP_Key="abcdef..." # §0.2 拿到的 Tokenacme.sh 会自动把这俩存进 ~/.acme.sh/account.conf,以后续期自动读,不用每次 export。3.4 签证书
# 必须 RSA 2048, 不要 ECC (见 §6.6 解释)
~/.acme.sh/acme.sh --issue --dns dns_dp -d vpn.example.com --keylength 2048acme.sh 会自动通过 DNSPod API 加 TXT 记录 → 等 ZeroSSL 验证 → 自动删 TXT。全程不需要 80 端口。
3.5 确保 strongSwan 目标目录存在
# 新版 strongSwan 包不一定自动建子目录, 不建会导致 install-cert 报 "No such file" (见 §6.2)
mkdir -p /etc/ipsec.d/cacerts /etc/ipsec.d/certs /etc/ipsec.d/private3.6 安装证书到 strongSwan 路径 + 设置自动 reload
~/.acme.sh/acme.sh --install-cert -d vpn.example.com \
--key-file /etc/ipsec.d/private/privkey.pem \
--fullchain-file /etc/ipsec.d/certs/fullchain.pem \
--ca-file /etc/ipsec.d/cacerts/chain.pem \
--reloadcmd "systemctl restart strongswan-starter"
chmod 600 /etc/ipsec.d/private/privkey.pem这条命令同时记录了 reloadcmd,以后 acme.sh 自动续期会自动复制新证书 + 自动重启 strongSwan,完全免维护。
3.7 验证 issuer 是 ZeroSSL
openssl x509 -in /etc/ipsec.d/certs/fullchain.pem -noout -issuer- 期望:
issuer=C=AT, O=ZeroSSL GmbH, CN=ZeroSSL RSA DV SSL CA 2 - 失败:
issuer=... CN=YR1/YR2/YE1→ §3.2 没切 ZeroSSL,回去重做
§4 strongSwan 配置
4.1 主配置 /etc/ipsec.conf
mv /etc/ipsec.conf /etc/ipsec.conf.bak
nano /etc/ipsec.conf粘贴(vpn.example.com 换成你的域名):
config setup
charondebug="ike 1, knl 1, cfg 0"
uniqueids=no
conn ikev2-vpn
auto=add
compress=no
type=tunnel
keyexchange=ikev2
fragmentation=yes
forceencaps=yes
dpdaction=clear
dpddelay=300s
rekey=no
left=%any
leftid=@vpn.example.com
leftcert=fullchain.pem
leftsendcert=always
# ↓↓↓ 分流核心: 只有这个网段走 VPN ↓↓↓
leftsubnet=10.0.0.0/16
right=%any
rightid=%any
rightauth=eap-mschapv2
# VPN 客户端 IP 池, 别和内网网段冲突
rightsourceip=10.10.10.0/24
# 用路由器 DNS, 解析内网设备名; 不想要解析内网名可改 1.1.1.1
rightdns=10.0.0.1
rightsendcert=never
eap_identity=%identity
# 宽松套件, 兼容老安卓 / 苹果 / Win, 不要在末尾加 ! (见 §6.4)
ike=aes256gcm16-prfsha384-ecp384,aes256gcm16-prfsha256-modp2048,aes256-sha256-modp2048,aes256-sha384-modp2048,aes256-sha1-modp2048,aes128-sha256-modp2048,aes128-sha1-modp1024
esp=aes256gcm16,aes256-sha384,aes256-sha256,aes256-sha1,aes128-sha256,aes128-sha14.2 私钥引用 + 用户密码 /etc/ipsec.secrets
nano /etc/ipsec.secrets# 服务器证书的私钥
# RSA 因为 §3.4 我们用 --keylength 2048 签的就是 RSA
# (如果你执意用了 ECC, 这里改成 ECDSA; 但本教程不推荐 ECC, 见 §6.6)
: RSA "privkey.pem"
# VPN 用户(可以多行, 密码至少 12 位+ 大小写 + 数字 + 符号)
zhangsan : EAP "ZsKe92!QvLm8rWp3"
lisi : EAP "X9$mP4kRtY8nB2Lv"chmod 600 /etc/ipsec.secrets: RSA "privkey.pem" 冒号前空格不能少,前面 : 是 strongSwan 的语法标记。少了会被当成用户名。
生成强密码工具:
openssl rand -base64 16§5 系统转发 + iptables NAT
5.1 永久开启 IP 转发
nano /etc/sysctl.conf找到或添加:
net.ipv4.ip_forward=1
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.all.send_redirects=0立即生效:
sysctl -p5.2 iptables 规则
WAN=eth0 # 改成 §0.1 记的网卡名
# VPN 客户端访问内网时伪装成服务器自己, 内网设备能正常回包, 不用动路由器路由表
iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -d 10.0.0.0/16 -o $WAN -j MASQUERADE
# 放行 VPN 转发流量
iptables -A FORWARD -s 10.10.10.0/24 -j ACCEPT
iptables -A FORWARD -d 10.10.10.0/24 -j ACCEPT
# MSS clamp, 解决某些客户端的 MTU 问题
iptables -t mangle -A FORWARD -m policy --pol ipsec --dir in -p tcp \
-m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \
-j TCPMSS --set-mss 1360
# 持久化(装 iptables-persistent 的目的就是为了这个)
netfilter-persistent save§6 启动 + 验证
6.1 启动 strongSwan
systemctl enable strongswan-starter
systemctl restart strongswan-starter
systemctl status strongswan-starter6.2 关键自检
ipsec listall | head -40必须看到这两节有内容:
List of Private Keys:
RSA 2048 bits, ... ← 私钥加载成功
List of X.509 End Entity Certificates:
subject: "CN=vpn.example.com" ← 证书加载成功
issuer: "C=AT, O=ZeroSSL ..." ← 必须是 ZeroSSL, 不是 YR1/YR2/YE任一缺失 → 跳到 §7 故障排查。
6.3 验证证书自动续期
# acme.sh 安装时已经写了 cron, 看一眼:
crontab -l | grep acme
# 强制续期一次预演(不会真换证):
~/.acme.sh/acme.sh --renew -d vpn.example.com --force看到 Cert success 和 Run reload cmd: systemctl restart strongswan-starter 就稳了,之后再也不用管证书。
§7 客户端配置
7.1 Apple(iOS / iPadOS / macOS)— 最简单 [推荐]
设置 → 通用 → VPN 与设备管理 → 添加 VPN 配置:
| 字段 | 填什么 |
|---|---|
| 类型 | IKEv2 |
| 描述 | 随便,自己看的 |
| 服务器 | vpn.example.com |
| 远程 ID | vpn.example.com(必须和上一行一致,不带 @) |
| 本地 ID | 留空 |
| 用户鉴定 | 用户名 |
| 用户名 / 密码 | §4.2 配的 |
[x] 苹果自动遵守服务端 leftsubnet,只 10.0.0.0/16 走 VPN,什么都不用做
[x] 没有"不安全"标签(苹果不对 IKEv2 做协议歧视)
7.2 Android — 推荐装 strongSwan App [推荐]
下载 strongSwan VPN Client,新建配置:
- Server:
vpn.example.com - VPN Type:IKEv2 EAP (Username/Password)
- Username / Password:同上
- CA certificate:
Select automatically - 设置 →
Split tunneling:仅10.0.0.0/16
不推荐安卓自带 VPN,会显示"较不安全"红标(系统对 EAP-MSCHAPv2 协议本身的偏见,与你的配置安全性无关)。strongSwan App 不显示这标签,且兼容性更好。
如果一定要用自带:
- 类型选
IKEv2/IPSec MSCHAPv2 - IPSec identifier 填
vpn.example.com(不带 @,必须和证书域名一致,不填或填错就连不上) - 各厂商 ROM 表现不一致,某些版本会有玄学 bug,出问题直接换 strongSwan App
7.3 Windows 10/11
设置 → 网络 → VPN → 添加 VPN:
- 提供商:Windows(内置)
- 类型:IKEv2
- 信息类型:用户名和密码
- 用户名 / 密码:同上
Set-VpnConnection -Name "你的连接名" -SplitTunneling $True
Add-VpnConnectionRoute -ConnectionName "你的连接名" -DestinationPrefix 10.0.0.0/16§8 踩坑全集(超重要)
按出现概率排序,带症状 + 原因 + 修法。
8.1 acme.sh 邮箱含中文 → invalidContact
"type": "urn:ietf:params:acme:error:invalidContact",
"detail": "Error validating contact(s) :: contact email contains non-ASCII characters"原因:把示例里的中文(如"你的邮箱")原样填了。
修法:
~/.acme.sh/acme.sh --register-account -m 真实英文邮箱@example.com8.2 acme.sh 安装证书报 No such file
/etc/ipsec.d/cacerts/chain.pem: No such file or directory原因:新版 strongSwan 包不自动建子目录。
修法:
mkdir -p /etc/ipsec.d/cacerts /etc/ipsec.d/certs /etc/ipsec.d/private本教程 §3.5 已经包含这一步,新搭不会踩。
8.3 ipsec 命令 not found
-bash: ipsec: command not found原因:新版 Debian 上 strongswan 元包不再自动带 strongswan-starter(ipsec 命令和服务都在 starter 里)。
修法:
apt install -y strongswan-starter本教程 §2 的安装命令已显式列出。
8.4 no IKE config found / 协议谈不拢
服务器日志:
no IKE config found for ..., sending NO_PROPOSAL_CHOSEN原因:ike=...! 末尾的 ! 是"严格模式",客户端提的套件不在列表里就直接拒。
修法:去掉 !,加更多兼容套件。本教程 §4.1 的配置已经是宽松版,不要在末尾自己加 !。
8.5 no private key found / 服务器侧 AUTH_FAILED
服务器日志:
no private key found for 'vpn.example.com'原因 A:ipsec.secrets 写的类型(RSA / ECDSA)和实际私钥文件类型不一致。
原因 B:ipsec.secrets 干脆缺了 : RSA "privkey.pem" 这行(默认 Debian 自带的文件只有注释,你以为改过了其实没改)。
修法:
# 看私钥实际类型
head -1 /etc/ipsec.d/private/privkey.pem
# BEGIN RSA PRIVATE KEY → secrets 必须写 RSA
# BEGIN EC PRIVATE KEY → secrets 必须写 ECDSA
# 看 secrets 完整内容(别用 head -3, 看不到实际配置行)
cat /etc/ipsec.secrets确认有 : RSA "privkey.pem" 这行,前面 : 不能丢,不能写成 :RSA 也不能写成 RSA "privkey.pem"。
8.6 客户端报 no trusted public key found / AUTH_FAILED — 最大的坑
客户端日志(strongSwan App 或服务器看到对方 INFORMATIONAL):
using untrusted intermediate certificate "C=US, O=Let's Encrypt, CN=YR2"
no issuer certificate found for "... YR2"
issuer is "C=US, O=ISRG, CN=Root YR"
no trusted RSA/ECDSA public key found for 'vpn.example.com'原因:Let's Encrypt 2025-2026 起的新中间证书 YR1/YR2/YE1/YE2 挂在 Root YR / Root YE 下,客户端信任库里没有。
修法:换 ZeroSSL。本教程 §3 默认就是 ZeroSSL,新搭不会踩。
老配置迁移:
~/.acme.sh/acme.sh --set-default-ca --server zerossl
~/.acme.sh/acme.sh --issue --dns dns_dp -d vpn.example.com --keylength 2048 --force
~/.acme.sh/acme.sh --install-cert -d vpn.example.com \
--key-file /etc/ipsec.d/private/privkey.pem \
--fullchain-file /etc/ipsec.d/certs/fullchain.pem \
--ca-file /etc/ipsec.d/cacerts/chain.pem \
--reloadcmd "systemctl restart strongswan-starter"
# 验证
openssl x509 -in /etc/ipsec.d/certs/fullchain.pem -noout -issuer
# 必须是 ZeroSSL, 不是 LE8.7 安卓自带 VPN 显示"较不安全"
这不是 bug。安卓系统对 IKEv2/IPSec MSCHAPv2 协议本身打的标签(认为离线爆破有理论风险)。
实际你的连接安全没问题 — MSCHAPv2 被包在 AES-256-GCM 隧道里,攻击者根本看不到 MSCHAPv2 原始包,谈不上爆破。
解决:装 strongSwan App,不显示这标签。
8.8 移动网络下 IKE 分片被丢
服务器日志显示发了 3 个分片,客户端没回包就重试:
sending packet: ... (1248 bytes)
sending packet: ... (1248 bytes)
sending packet: ... (537 bytes)
deleting half open IKE_SA ... after timeout原因:运营商 / CGNAT 把大 UDP 包丢了。
修法:/etc/strongswan.d/charon-custom.conf:
charon { fragment_size = 1000 }然后 systemctl restart strongswan-starter。不行就降到 800、600。
但是:这次调试发现 90% 的"分片好像丢了"其实是 §8.6 的证书信任问题(客户端收到了但拒绝了,反应非常快像没收到一样)。先排查证书,再怀疑分片。
8.9 在家连不上自己的公网域名 (hairpin/loopback)
手机连家里 WiFi 时连不上 VPN,切到 4G 立刻能连。
原因:消费级路由器很多不支持 "NAT loopback / hairpin",内网设备访问自家公网 IP 会失败。
解决方法:
- 在家用 4G 验证 VPN 配置对没对(排除路由器问题)
- 在家时不用 VPN(本来就在内网了)
- 高级:路由器后台找"NAT 回流 / hairpin NAT"开关打开
§9 教程没办法替你解决的"环境坑"
这些是网络环境层面的问题,不是配置错,无法在 Debian 上修:
- CGNAT / 双层 NAT:路由器拿到的就是内网 IP(常见
100.64.0.0/10段),公网到不了路由器。让运营商给公网 IPv4,或者用 IPv6 + DDNS。 - ISP 封 UDP 500/4500:极少数省份的运营商真这么干。换运营商,或者换 WireGuard(端口可任意)。
- GFW 干扰:跨境 VPN 偶尔会被识别。降低 PSK 大小、用非标准端口能缓解一些。
- 公司 / 学校防火墙拦截 IPSec:跟你 Debian 没关系,改不了。
§10 日常运维
加用户
echo 'newuser : EAP "新强密码"' >> /etc/ipsec.secrets
systemctl restart strongswan-starter删用户
nano /etc/ipsec.secrets
# 删那一行, 保存
systemctl restart strongswan-starter看当前在线的客户端
ipsec statusall实时跟日志
journalctl -u strongswan-starter -f看证书到期日
~/.acme.sh/acme.sh --list
openssl x509 -in /etc/ipsec.d/certs/fullchain.pem -noout -dates手动续期(平时不需要,cron 会自己跑)
~/.acme.sh/acme.sh --renew -d vpn.example.com --force§11 安全说明
11.1 为什么不用 PSK
| 维度 | EAP-MSCHAPv2(本教程) | PSK | RSA 证书互认 |
|---|---|---|---|
| 服务器身份验证 | [OK] 证书 | [X] 无,中间人攻击风险 | [OK] 证书 |
| 客户端身份验证 | [OK] 用户名密码,可分人 | [X] 共用 key,不可分人 | [OK] 证书 |
| 吊销机制 | [OK] 改密码 | [X] 全员换 key | [OK] CRL |
| 多用户管理 | [OK] 加一行 | [X] 全员换 | [!] 每人发证 |
| 配置复杂度 | [*] 简单 | [*] 极简 | [**] 麻烦 |
PSK 看似简单,但没有服务器身份验证 → 任何能影响 DNS / 路由的人(ISP、公共 WiFi 管理员、家里其他用户)都能做中间人。不推荐。
11.2 密码强度
- 至少 12 位
- 大小写 + 数字 + 符号
- 别用字典词、生日、电话号
生成:
openssl rand -base64 1611.3 MSCHAPv2 实际安全性
- 协议本身有"理论上"的离线爆破弱点
- 但 MSCHAPv2 包在 IKEv2 的 AES-256-GCM 加密隧道里跑,攻击者根本截获不到 MSCHAPv2 包
- 服务器证书(ZeroSSL)保证客户端连的就是真服务器,中间人挡得住
- 结论:在 IKEv2 这一层的保护下,EAP-MSCHAPv2 实际安全 ≈ 证书认证
§12 关键文件位置速查
| 文件 | 作用 | 谁维护 |
|---|---|---|
/etc/ipsec.conf | strongSwan 连接定义 | 你 |
/etc/ipsec.secrets | 私钥引用 + 用户密码 | 你 |
/etc/ipsec.d/private/privkey.pem | 服务器证书私钥 | acme.sh 自动 |
/etc/ipsec.d/certs/fullchain.pem | 末端证书 + 中间证书 | acme.sh 自动 |
/etc/ipsec.d/cacerts/chain.pem | 中间证书 | acme.sh 自动 |
~/.acme.sh/vpn.example.com/ | acme.sh 自己的证书目录 | acme.sh 自动 |
~/.acme.sh/account.conf | DNSPod token 等持久化配置 | acme.sh 自动 |
/etc/strongswan.d/charon-custom.conf | 可选: fragment_size 等调优 | 你(按需) |
/etc/sysctl.conf | IP 转发开关 | 你 |
§13 完工检查清单
按顺序对一遍,任一项不通就回去对应章节查:
[ ] ipsec --version 输出版本号 (§2)
[ ] nslookup vpn.example.com 返回路由器公网 IP (§1.1)
[ ] 路由器 UDP 500/4500 已转发 (§1.2)
[ ] openssl x509 ... -issuer 显示 ZeroSSL (§3.7)
[ ] cat /etc/ipsec.secrets 第一行有 : RSA "privkey.pem" (§4.2)
[ ] sysctl net.ipv4.ip_forward 显示 1 (§5.1)
[ ] iptables -t nat -L POSTROUTING -v 看到 MASQUERADE 规则 (§5.2)
[ ] systemctl status strongswan-starter 显示 active (running) (§6.1)
[ ] ipsec listall 看到 RSA 私钥 + 你的证书 (§6.2)
[ ] 手机连上后 ipsec statusall 显示 ikev2-vpn[N] established (§7)
[ ] 手机连上后能访问 http://10.0.0.1(内网验证)
[ ] 手机连上后访问 ip.sb 显示手机原本运营商 IP(分流验证,不应显示服务器 IP)
