This lab builds an IPsec site-to-site tunnel between two OPNsense
routers using IKEv2 with pre-shared keys. By the end you will have
a working tunnel that encrypts traffic between two LANs, with both
ends reporting ESTABLISHED on Phase 1 and INSTALLED on Phase 2.
The lab is symmetric. The same set of steps runs on both routers, with one column of values that differs between ends (the remote IP, the remote LAN). Use a notebook or two terminals side-by-side to keep the local and remote columns straight.
Objective
By the end of this lab, you can:
- Configure an IKEv2 Phase 1 with PSK, AES-256-GCM and SHA-256.
- Define a Phase 2 covering two local-and-remote subnets.
- Add the firewall rules needed for IPsec to terminate and pass traffic.
- Read
ipsec statusallto confirm Phase 1 isESTABLISHEDand Phase 2 isINSTALLED. - Run a ping across the tunnel from each side and confirm the ESP traffic on the WAN.
Requirements
You need:
- Two OPNsense instances — call them
OPN-AandOPN-B. - A WAN link between them. In nested mode, this is a host-only network shared by both firewalls.
- A LAN behind each.
OPN-ALAN is10.10.10.0/24;OPN-BLAN is10.10.20.0/24. - A host on each LAN that can ping. The DHCP-scope lab’s reservation scheme gives you a stable target on each side.
- Outbound UDP/500 and UDP/4500 plus IP protocol 50 (ESP) between the two WANs. Confirm before troubleshooting.
| Side | Hostname | WAN IP | LAN subnet | LAN gateway |
|---|---|---|---|---|
| A | OPN-A | 198.51.100.1 | 10.10.10.0/24 | 10.10.10.1 |
| B | OPN-B | 198.51.100.2 | 10.10.20.0/24 | 10.10.20.1 |
Tasks
Task 1: Lay out the parameters
Write the parameters down before opening the GUI. The most
common IPsec failure is a Phase 2 mismatch where one end has
10.10.10.0/24 ↔ 10.10.20.0/24 and the other has the source
and destination reversed.
Phase 1 (IKE SA):
Remote gateway : 198.51.100.1 (the other side's WAN)
Authentication : Pre-Shared Key
PSK : <32+ random bytes>
Encryption : AES-256-GCM
Integrity : SHA-256 (or GCM-internal, since GCM is AEAD)
DH group : 14 (2048-bit MODP)
Lifetime : 28800 s (8h)
Version : IKEv2
DPD : enabled, 10s delay, 60s timeout
NAT-T : enabled
Phase 2 (CHILD SA):
Local subnet : 10.10.10.0/24 (side A) | 10.10.20.0/24 (side B)
Remote subnet : 10.10.20.0/24 (side A) | 10.10.10.0/24 (side B)
Encryption : AES-256-GCM
PFS : group 14
Lifetime : 3600 s (1h)
Generate a strong PSK once and use the same string on both ends:
# On a Linux host, or on either OPNsense shell
openssl rand -base64 48
Paste that into both routers’ Phase 1 PSK fields. If the strings
differ by even one character, the IKE_AUTH exchange fails with
AUTHENTICATION_FAILED and the logs are not helpful about which
end has the wrong string.
Task 2: Enable IPsec on both routers
VPN → IPsec → Tunnel Settings on each router:
| Field | Value |
|---|---|
| Enable IPsec | on |
| Prefer older IPsec SAs | off (default; safer) |
| Disable auto-add | off (lets strongSwan install SPDs) |
Save. The daemon (charon) is enabled but no tunnel exists yet.
Task 3: Configure Phase 1 on both routers
VPN → IPsec → Phase 1 → + Add on OPN-A:
| Field | Value |
|---|---|
| Connection method | Default |
| Key Exchange version | IKEv2 |
| Internet Protocol | IPv4 |
| Interface | WAN |
| Remote gateway | 198.51.100.2 |
| Description | to OPN-B |
| Authentication Method | Pre-Shared Key |
| My identifier | My IP address (198.51.100.1) |
| Peer identifier | Peer IP address (198.51.100.2) |
| Pre-Shared Key | (paste the PSK from Task 1) |
| Encryption algorithm | AES-256-GCM |
| Hash algorithm | SHA-256 |
| DH Group | 14 |
| Lifetime | 28800 |
| Rekey time | (blank — use lifetime) |
| Reauth time | (blank) |
| DPD | enabled; delay 10, timeout 60, action restart |
| NAT Traversal | enabled |
On OPN-B, the symmetric configuration with the local/remote columns swapped:
| Field | Value |
|---|---|
| Remote gateway | 198.51.100.1 |
| My identifier | My IP address (198.51.100.2) |
| Peer identifier | Peer IP address (198.51.100.1) |
| (everything else identical) |
Save and apply on each. Confirm charon saw the change:
# On each OPNsense
ipsec statusall
You should see IKE_SA to the remote IP, status STATE_V2_CONNECTING
or ESTABLISHED. If you see no row at all, charon did not load the
config — restart the daemon:
ipsec restart
Task 4: Configure Phase 2 on both routers
VPN → IPsec → Phase 2 → + Add on OPN-A:
| Field | Value |
|---|---|
| Mode | Tunnel |
| Description | to-OPN-B-LAN |
| Local subnet type | LAN subnet |
| Local subnet | 10.10.10.0/24 |
| Remote subnet type | Network |
| Remote subnet | 10.10.20.0/24 |
| Encryption algorithm | AES-256-GCM |
| Hash algorithm | SHA-256 (or AEAD-only if GCM) |
| PFS group | 14 |
| Lifetime | 3600 |
On OPN-B, the symmetric configuration:
| Field | Value |
|---|---|
| Local subnet | 10.10.20.0/24 |
| Remote subnet | 10.10.10.0/24 |
Apply on both ends.
Task 5: Firewall rules on the IPsec interface
The IPsec interface on OPNsense is a group interface. By default it has no rules; traffic that arrives from the tunnel is dropped before Phase 2 can route it.
On each OPNsense: Firewall → Rules → IPsec:
| Action | Source | Destination | Protocol |
|---|---|---|---|
| pass | 10.10.10.0/24 | 10.10.20.0/24 | any |
| pass | 10.10.20.0/24 | 10.10.10.0/24 | any |
These mirror the Phase 2 selectors. In production, tighten per-port and per-host.
Confirm:
pfctl -sr | grep -A1 'ipsec'
Task 6: Trigger the tunnel and watch the negotiation
From a host on OPN-A’s LAN, ping a host on OPN-B’s LAN:
ping -c 4 10.10.20.50
The first ping triggers Phase 2 negotiation; expect 1-3 seconds of latency on the first echo. Subsequent pings should be sub-millisecond after the SA is installed.
On OPN-A, watch the negotiation:
tail -f /var/log/charon.log
You should see:
... IKE_SA to-OPN-B[1] established between 198.51.100.1[198.51.100.1]...198.51.100.2[198.51.100.2]
... CHILD_SA to-OPN-B-LAN{1} established with SPIs ...
... sending DPD vendor ID
... received DPD vendor ID
Task 7: Read the SA table
The ipsec statusall output is the authoritative answer:
ipsec statusall
A healthy tunnel looks like:
Routed Connections:
to-OPN-B{1}: 198.51.100.1[198.51.100.1]...198.51.100.2[198.51.100.2] IKEv2
to-OPN-B-LAN{2}: 10.10.10.0/24 === 10.10.20.0/24
Security Associations (1 up, 0 connecting):
to-OPN-B[1]: ESTABLISHED 5 minutes ago, ...
to-OPN-B-LAN{2}: INSTALLED, ESP in UDP SPIs: ...
Two things to look for:
- The routed connection lists both ends and the IKE version.
- The CHILD_SA is
INSTALLED, notrekeyingor empty.
If Phase 1 is ESTABLISHED but Phase 2 is missing, the Phase 2
proposal does not match. If Phase 2 shows rekeying repeatedly,
the PFS group or the lifetime is misaligned.
Task 8: Capture ESP on the wire
The signature of a working IPsec tunnel is bidirectional ESP traffic on the WAN. Capture from OPN-A’s WAN:
sudo tcpdump -i em0 -nn -s 0 -c 50 proto 50 or udp port 500 or udp port 4500
While the capture runs, ping across the tunnel from a host on OPN-A’s LAN. You should see:
- A burst of UDP/500 and UDP/4500 packets as the SA is established or rekeyed.
- A stream of ESP packets (IP protocol 50, no visible port) carrying the encrypted traffic.
If the capture shows only IKE packets (UDP/500 and UDP/4500) and no ESP, the negotiation never finished Phase 2. If the capture shows only outbound ESP with no reply, the IPsec firewall rule on the remote side is dropping the return.
Task 9: Verify both directions
The bidirectional check is the trap-proof evidence. From each LAN, ping the other:
# From a host on OPN-A's LAN
ping -c 4 10.10.20.50
# From a host on OPN-B's LAN
ping -c 4 10.10.10.50
Both must succeed. If one direction works and the other does not, the issue is asymmetric — usually a missing firewall rule on one end, or a Phase 2 selector that covers only one direction.
Task 10: Test rekeying
Rekeying is where IKEv2 shows its advantage over IKEv1. Drop the SA and watch it rebuild:
# On OPN-A
ipsec down to-OPN-B-LAN
ipsec up to-OPN-B-LAN
A clean rekey shows a new CHILD_SA with new SPIs in
ipsec statusall and no errors in charon.log. If rekeying
fails after a lifetime expiry, your PFS group or lifetime is
mismatched with the peer — both ends must agree on PFS group
or the rekeyed SA will not be accepted.
Validation
ipsec statusallon both ends showsIKE_SA ESTABLISHEDandCHILD_SA INSTALLED.- A ping from a host on OPN-A’s LAN to OPN-B’s LAN succeeds.
- A ping from a host on OPN-B’s LAN to OPN-A’s LAN succeeds.
tcpdump -i em0 proto 50shows ESP traffic during a cross-tunnel ping.charon.logshows no errors during a clean rekey.pfctl -sr | grep ipsecshows the firewall rules.
Expected Result
An IPsec site-to-site tunnel between OPN-A and OPN-B that
encrypts traffic between 10.10.10.0/24 and 10.10.20.0/24.
Both routers report Phase 1 ESTABLISHED and Phase 2 INSTALLED.
A ping from either LAN reaches the other. Rekeying after the
Phase 2 lifetime works without operator intervention.
Troubleshooting
Phase 1 stays in CONNECTING.
UDP/500 or UDP/4500 is being blocked between the WANs. Confirm
with tcpdump -i em0 port 500 — you should see at least one
outbound packet from OPN-A. If the capture shows outbound only,
check upstream firewalls and OPNsense’s WAN firewall rule for
UDP/500 and UDP/4500 inbound.
Phase 1 establishes, Phase 2 fails with NO_PROPOSAL_CHOSEN.
The Phase 2 encryption/hash/PFS/lifetime does not match. Diff
the GUI on both ends. AES-256-GCM on one end and AES-256-CBC on
the other will not negotiate; SHA-256 on one and SHA-384 on the
other will not negotiate.
Phase 1 and Phase 2 establish, but ping times out.
The IPsec interface on one end has no firewall rule. Confirm
pfctl -sr | grep ipsec on both ends.
Tunnel works for 60 minutes, then drops.
Phase 2 lifetime expiry, but the rekey is failing. Confirm both
ends have the same lifetime and PFS group. Watch charon.log
during a rekey window.
Ping works from one direction only.
A firewall rule is missing on one end, or the LAN host on the
broken side has no route back through OPNsense. Confirm routing
with netstat -rn or ip route get 10.10.20.50 from the LAN
host.
MTU-related packet loss or fragmentation. IPsec adds 50-90 bytes of overhead (ESP + IP + UDP-encap + padding). Symptoms: ping works for small packets, fails for larger ones; web loads partially. Reduce the WAN MTU to 1400 or set the MSS clamp on the LAN firewall rule to 1380.
Cleanup
Tear down both ends and verify.
# On each OPNsense shell
ipsec down to-OPN-B-LAN
ipsec down to-OPN-B
# Then via GUI:
# VPN → IPsec → Phase 1 → delete both entries
# VPN → IPsec → Phase 2 → delete both entries
# VPN → IPsec → Tunnel Settings → uncheck Enable IPsec → Apply
# Confirm no SAs are left
ipsec statusall
# Remove the firewall rules
# Firewall → Rules → IPsec → delete both rules
# Confirm the IPsec firewall group has no rules
pfctl -sr | grep -E 'ipsec|10.10.10.0|10.10.20.0' || \
echo "no IPsec rules left"
The PSK lives in /usr/local/etc/swanctl/conf.d/*.conf and is
not exported in plaintext config.xml. Wipe the file or move the
firewall out of service to clear the secret.
What you learned
- IPsec site-to-site is a symmetric configuration. Each end fills Phase 2 from its own perspective, and the two ends agree on encryption, hash, PFS, and lifetime.
- IKEv2 rekeys silently in the background; the SA on the wire changes without dropping traffic. The trap is that rekeying reveals configuration drift that a one-shot negotiation might tolerate.
- The IPsec interface is a group interface with no default rules. Even a fully-negotiated tunnel will not pass traffic until the firewall rules on both ends are in place.
- MTU and MSS matter. ESP adds overhead that breaks large packets; reduce MTU or clamp MSS to compensate.