大部分由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

  1. 登录 console.dnspod.cn
  2. 头像 → 用户中心 → API 密钥 → DNSPod Token
  3. 创建密钥,保存好 ID 和 Token(只显示一次)

格式:

ID:    123456
Token: abcdef1234567890abcdef1234567890

§1 域名解析 + 路由器端口转发

1.1 加 A 记录

在 DNSPod 控制台:

主机记录类型记录值
vpn(或任意)A你的路由器公网 IP

确认生效:

nslookup vpn.example.com   # 替换成你的域名

下文所有 vpn.example.com 都替换成你的实际域名。

1.2 路由器端口转发

协议外部端口内部 IP内部端口
UDP500Debian IP500
UDP4500Debian IP4500
不需要 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-persistent

必须显式列出 strongswan-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 ~/.bashrc

3.2 切默认 CA 到 ZeroSSL

~/.acme.sh/acme.sh --set-default-ca --server zerossl

3.3 配置 DNSPod Token

export DP_Id="123456"          # §0.2 拿到的 ID
export DP_Key="abcdef..."      # §0.2 拿到的 Token
acme.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 2048

acme.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/private

3.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-sha1

4.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 -p

5.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-starter

6.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 successRun reload cmd: systemctl restart strongswan-starter 就稳了,之后再也不用管证书


§7 客户端配置

7.1 Apple(iOS / iPadOS / macOS)— 最简单 [推荐]

设置通用VPN 与设备管理添加 VPN 配置:

字段填什么
类型IKEv2
描述随便,自己看的
服务器vpn.example.com
远程 IDvpn.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
  • 信息类型:用户名和密码
  • 用户名 / 密码:同上

Windows 默认走全局流量,必须用管理员 PowerShell 改成分流:

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.com

8.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, 不是 LE

8.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(本教程)PSKRSA 证书互认
服务器身份验证[OK] 证书[X] 无,中间人攻击风险[OK] 证书
客户端身份验证[OK] 用户名密码,可分人[X] 共用 key,不可分人[OK] 证书
吊销机制[OK] 改密码[X] 全员换 key[OK] CRL
多用户管理[OK] 加一行[X] 全员换[!] 每人发证
配置复杂度[*] 简单[*] 极简[**] 麻烦

PSK 看似简单,但没有服务器身份验证 → 任何能影响 DNS / 路由的人(ISP、公共 WiFi 管理员、家里其他用户)都能做中间人。不推荐。

11.2 密码强度

  • 至少 12 位
  • 大小写 + 数字 + 符号
  • 别用字典词、生日、电话号

生成:

openssl rand -base64 16

11.3 MSCHAPv2 实际安全性

  • 协议本身有"理论上"的离线爆破弱点
  • 但 MSCHAPv2 包在 IKEv2 的 AES-256-GCM 加密隧道里跑,攻击者根本截获不到 MSCHAPv2 包
  • 服务器证书(ZeroSSL)保证客户端连的就是真服务器,中间人挡得住
  • 结论:在 IKEv2 这一层的保护下,EAP-MSCHAPv2 实际安全 ≈ 证书认证

§12 关键文件位置速查

文件作用谁维护
/etc/ipsec.confstrongSwan 连接定义
/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.confDNSPod token 等持久化配置acme.sh 自动
/etc/strongswan.d/charon-custom.conf可选: fragment_size 等调优你(按需)
/etc/sysctl.confIP 转发开关

§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)

Last modification:June 6, 2026
If you think my article is useful to you, please feel free to appreciate