StrongSwan Site-to-Site VPN: Step-by-Step Guide
A StrongSwan site-to-site VPN securely joins two private networks across an untrusted network. The gateways negotiate IKEv2, authenticate each other, install IPsec Security Associations and Linux XFRM policies, then forward only the approved subnets. This guide builds that current path from inventory through rollback.
This guide was technically reviewed on 12 August 2026 against current StrongSwan 6.0 documentation and current RHEL networking guidance. Package names, service units, plugin paths, firewall ownership, and cloud routing differ by distribution and platform. Discover them on the actual gateways before applying changes.
Understand the topology
The worked example uses documentation-only public ranges and non-overlapping private LANs. Replace every address and interface with the approved inventory. Do not paste the example into a reachable host unchanged.
| Component | Site A | Site B |
|---|---|---|
| IKE identity | site-a.example.net | site-b.example.net |
| Public or NAT-facing address | 198.51.100.10 | 203.0.113.20 |
| Private gateway address | 10.10.0.1 | 10.20.0.1 |
| Protected LAN | 10.10.0.0/24 | 10.20.0.0/24 |
| WAN interface example | eth0 | eth0 |
| LAN interface example | eth1 | eth1 |
Private ranges must not overlap. If both sites already use the same range, policy-based selectors cannot distinguish them. Renumbering is the clean solution. Selective NAT can be designed for an unavoidable overlap, but it adds identity, logging, policy, and troubleshooting complexity and is outside a safe first build.
Choose the control interface and authentication
Use VICI and swanctl as the single control plane on each gateway. Verify that the selected package provides the VICI plugin, swanctl, the expected configuration directory, and one matching charon service unit.
| Choice | Use | Boundary |
|---|---|---|
swanctl and VICI | Primary procedure in this guide | Requires the VICI plugin, correct package, and matching service unit. |
| PSK | Small controlled deployment or interoperability need | Generate high entropy, distribute out of band, scope to explicit identities, rotate, and never publish. |
| X.509 certificates | Preferred for scalable production identity and independent rotation | Requires CA, issuance, private-key protection, identity validation, renewal, and revocation operations. |
Step 1: record access, scope, and rollback
Record both gateway owners, public and private addresses, NAT position, route tables, default gateways, protected subnets, expected protocols, availability window, peer contacts, firewall owner, cloud controls, MTU, monitoring destination, and rollback authority. Confirm console or out-of-band access before changing routing, forwarding, firewall, or VPN services.
date -u
hostnamectl
cat /etc/os-release
ip -brief address
ip route show table all
ip rule show
ss -lunp
sysctl net.ipv4.ip_forward
systemctl --failed
nft list ruleset
firewall-cmd --list-all 2>/dev/null
Capture the current host state first. If StrongSwan was previously installed, copy only the configuration paths that actually exist into a separate pre-existing directory. On a fresh host this loop copies nothing, which is expected. It does not hide a real copy failure.
sudo install -d -m 0700 /root/vpn-change-backup/pre-existing
for vpn_path in /etc/strongswan.conf /etc/swanctl /etc/strongswan; do
if sudo test -e "$vpn_path"; then
sudo cp -a -- "$vpn_path" /root/vpn-change-backup/pre-existing/
fi
done
sudo sh -c 'ip route show table all > /root/vpn-change-backup/routes-before.txt'
sudo sh -c 'ip rule show > /root/vpn-change-backup/rules-before.txt'
sudo sh -c 'nft list ruleset > /root/vpn-change-backup/nftables-before.txt'
sudo find /root/vpn-change-backup -type f -exec sha256sum {} +
The root shell is required for the output redirection into /root. Writing sudo ip route ... > /root/file is not sufficient because the calling shell opens the destination before sudo starts. A configuration copy does not back up external firewall, cloud routes, DNS, certificates, secrets held elsewhere, or the running network state.
Step 2: verify reachability and NAT
Each gateway must reach the other peer on UDP 500 and UDP 4500. Native ESP uses IP protocol 50, not TCP or UDP. When NAT traversal is active, ESP is encapsulated in UDP 4500. Confirm upstream security groups, routers, firewalls, and port forwarding. One-to-one NAT must consistently map the expected gateway.
ip route get 203.0.113.20
ping -c 3 203.0.113.20
sudo tcpdump -ni eth0 'udp port 500 or udp port 4500 or ip proto 50'
A failed ping does not prove IKE is unreachable because ICMP may be filtered. The packet capture is passive, but it can expose peer addresses and negotiation metadata. Restrict capture access and retention.
Step 3: install StrongSwan and discover the service
RHEL-family example
sudo dnf install epel-release
sudo dnf install strongswan
rpm -q strongswan
rpm -ql strongswan | grep -E '/swanctl$|swanctl.conf|vici'
systemctl list-unit-files '*strongswan*'
swanctl --version
Ubuntu or Debian-family example
sudo apt update
sudo apt install strongswan-swanctl strongswan-pki
dpkg-query -W 'strongswan*'
systemctl list-unit-files '*strongswan*'
swanctl --version
Repository and package layouts change. Use supported repositories and verify package signatures. Start only the unit provided for the chosen VICI/charon deployment after configuration validation. A common unit name is strongswan, but do not assume it:
systemctl list-unit-files '*strongswan*'
systemctl cat strongswan 2>/dev/null
sudo systemctl enable --now strongswan
sudo systemctl status strongswan --no-pager
If the discovered unit has another name, use that exact unit. Do not install or enable a second daemon merely to make the example command succeed.
Back up the installed defaults before editing
After installation and before creating the site-to-site profile, save the package-provided baseline separately. This gives a fresh host something meaningful to restore and keeps it distinct from any pre-existing configuration captured in Step 1.
sudo install -d -m 0700 /root/vpn-change-backup/post-install-defaults
for vpn_path in /etc/strongswan.conf /etc/swanctl /etc/strongswan; do
if sudo test -e "$vpn_path"; then
sudo cp -a -- "$vpn_path" /root/vpn-change-backup/post-install-defaults/
fi
done
sudo find /root/vpn-change-backup/post-install-defaults \
-type f -exec sha256sum {} +
Step 4: enable forwarding without opening the gateway
A site-to-site gateway must forward IPv4 packets. This does not grant firewall permission by itself. Persist the setting in a dedicated file and verify it:
sudo install -m 0644 /dev/null /etc/sysctl.d/90-site-to-site-vpn.conf
sudo sh -c 'printf "%s\n" "net.ipv4.ip_forward = 1" > /etc/sysctl.d/90-site-to-site-vpn.conf'
sudo sysctl --system
sysctl net.ipv4.ip_forward
If the VPN gateway is not the LAN's default gateway, add a route on the actual LAN router for the remote subnet through the VPN gateway. StrongSwan documentation also recommends disabling ICMP redirects on a non-default gateway so hosts are not told to use an unsuitable route:
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
Step 5: permit IKE and only approved forwarded traffic
Allow inbound UDP 500 and 4500 from the exact peer address. If native ESP is possible, permit IP protocol 50 from that peer. In the forward path, allow only 10.10.0.0/24 to 10.20.0.0/24 and the required return direction, narrowed further by protocol and port when the business flow permits.
Integrate rules with the existing firewall owner. Do not paste a new table or chain over a live ruleset. For firewalld, inspect the built-in IPsec service and the zone bound to the WAN connection before adding it:
firewall-cmd --get-active-zones
firewall-cmd --info-service=ipsec
firewall-cmd --zone=public --list-all
# Apply only after confirming the zone, source restriction, and rollback.
sudo firewall-cmd --permanent --zone=public --add-service=ipsec
sudo firewall-cmd --reload
An unrestricted service rule may admit IKE from the internet. Prefer source-scoped policy or rich rules where the firewall design supports them. Verify with packet captures and counters rather than assuming a successful reload created the intended boundary.
Step 6: exempt tunnel traffic from source NAT
If either gateway already masquerades outbound LAN traffic, packets intended for the remote protected subnet must bypass source NAT before the general masquerade rule. Otherwise the source address changes before the XFRM policy lookup and the traffic may not match the tunnel policy.
# Compatibility example from StrongSwan documentation.
# Insert only into an iptables-managed firewall after reviewing rule order.
sudo iptables -t nat -I POSTROUTING 1 \
-s 10.10.0.0/24 -d 10.20.0.0/24 \
-m policy --pol ipsec --dir out -j ACCEPT
sudo iptables -t nat -S POSTROUTING
sudo iptables -t nat -L POSTROUTING -n -v --line-numbers
For nftables or firewalld, express the same requirement in the system's existing persistent configuration: return or accept matching site-to-site traffic before a broader masquerade action. Do not mix transient iptables commands with an nftables or firewalld owner and assume they will survive reload or reboot.
Step 7: generate and transfer a fresh PSK
Generate the PSK once on an approved system. Transfer it to the second gateway through an authenticated confidential channel. Do not send it in a ticket, chat, shell history, article, or world-readable file.
umask 077
openssl rand -hex 32
sudo install -d -m 0700 /etc/swanctl/conf.d
sudo install -m 0600 /dev/null /etc/swanctl/conf.d/site-to-site.conf
The command prints 32 random bytes as 64 hexadecimal characters. In swanctl.conf, prefix that value with 0x. The placeholders below are intentionally not usable secrets.
Step 8: configure Site A with swanctl
# /etc/swanctl/conf.d/site-to-site.conf on Site A
connections {
site-a-to-site-b {
version = 2
local_addrs = 198.51.100.10
remote_addrs = 203.0.113.20
proposals = aes256gcm16-prfsha384-ecp384
dpd_delay = 30s
local {
auth = psk
id = site-a.example.net
}
remote {
auth = psk
id = site-b.example.net
}
children {
site-a-lan-to-site-b-lan {
local_ts = 10.10.0.0/24
remote_ts = 10.20.0.0/24
esp_proposals = aes256gcm16-ecp384
start_action = trap
dpd_action = restart
close_action = trap
}
}
}
}
secrets {
ike-site-a-site-b {
id-a = site-a.example.net
id-b = site-b.example.net
secret = 0x<REPLACE_WITH_THE_SAME_64_HEX_CHARACTERS>
}
}
Step 9: configure Site B with swanctl
# /etc/swanctl/conf.d/site-to-site.conf on Site B
connections {
site-b-to-site-a {
version = 2
local_addrs = 203.0.113.20
remote_addrs = 198.51.100.10
proposals = aes256gcm16-prfsha384-ecp384
dpd_delay = 30s
local {
auth = psk
id = site-b.example.net
}
remote {
auth = psk
id = site-a.example.net
}
children {
site-b-lan-to-site-a-lan {
local_ts = 10.20.0.0/24
remote_ts = 10.10.0.0/24
esp_proposals = aes256gcm16-ecp384
start_action = trap
dpd_action = restart
close_action = trap
}
}
}
}
secrets {
ike-site-a-site-b {
id-a = site-a.example.net
id-b = site-b.example.net
secret = 0x<REPLACE_WITH_THE_SAME_64_HEX_CHARACTERS>
}
}
The proposal is a deliberate strong starting point, not a universal interoperability guarantee. Confirm that both peers provide the required plugins with swanctl --list-algs. If the other gateway does not support it, agree on the strongest mutually supported proposal through a recorded change. Do not fall back to SHA-1 or MODP-1024 merely to make the tunnel green.
Step 10: load and validate before sending traffic
sudo swanctl --load-all
sudo swanctl --list-conns
sudo swanctl --list-algs
sudo swanctl --list-pols
sudo swanctl --stats
journalctl -u strongswan -n 100 --no-pager
--load-all changes the running StrongSwan configuration. Run it during the approved change window after checking the file permissions and diff. A parsing or plugin error must be corrected before initiation.
Step 11: initiate and prove both directions
With start_action = trap, matching traffic can trigger negotiation. You can also initiate the named child explicitly from one site:
# Site A
sudo swanctl --initiate --child site-a-lan-to-site-b-lan
sudo swanctl --list-sas
sudo swanctl --list-pols
ip -s xfrm state
ip -s xfrm policy
# Source address must belong to the local traffic selector.
ping -I 10.10.0.1 -c 4 10.20.0.1
Test from a host behind each gateway as well as from the gateways. A gateway ping without -I may use the public address and bypass the protected selector. Prove application ports, not just ICMP. Confirm increasing XFRM byte counters in both directions and capture the outer traffic:
sudo tcpdump -ni eth0 'host 203.0.113.20 and (udp port 500 or udp port 4500 or ip proto 50)'
sudo tcpdump -ni eth1 'net 10.20.0.0/24'
Move from PSK to certificate authentication
A shared PSK proves that a peer knows the same secret, but it does not provide independent gateway credentials. Certificates let each gateway protect its own private key, rotate independently, and authenticate through a controlled trust anchor. Use a private CA or an approved certificate service with documented issuance, renewal, revocation, algorithm, subject alternative name, and private-key storage policy.
| Control | PSK deployment | Certificate deployment |
|---|---|---|
| Peer credential | One high-entropy shared value | Independent private key and end-entity certificate per gateway |
| Compromise response | Coordinate replacement on both peers | Revoke or distrust affected certificate and replace that gateway credential |
| StrongSwan local auth | auth = psk | auth = pubkey plus the gateway certificate |
| StrongSwan remote auth | Exact remote identity and matching PSK scope | auth = pubkey, exact expected identity, and approved CA constraint |
| Files | Secret in protected configuration | Certificate under /etc/swanctl/x509/, CA under x509ca/, private key under private/ |
| Operational expiry | Policy-driven rotation date | Not-before, not-after, renewal and revocation monitoring |
Do not generate a production CA and gateway keys by copying an unaudited shell block from a web page. Define key type and strength, name constraints, certificate profiles, protected issuance, backup, renewal automation, and revocation first. Load the files with swanctl --load-creds, inspect them with swanctl --list-certs, and confirm the configured identities match certificate subject alternative names.
Test certificate authentication as a separate connection name or in a maintenance window. Keep the PSK path available only as an explicitly time-bounded rollback until certificate negotiation, rekey, reboot persistence, monitoring, and failure handling pass. Then remove the PSK from both gateways and the secret-management channel, reload credentials, and prove that PSK authentication can no longer establish the tunnel.
Step 12: monitor rekeying, liveness, and capacity
Alert on IKE and CHILD SA state, repeated negotiation failure, authentication failure, proposal mismatch, DPD restart loops, rekey failure, traffic-selector mismatch, replay errors, XFRM drops, packet loss, latency, tunnel throughput, CPU, memory, and log/disk health. Baseline expected traffic in each direction so a tunnel that is technically up but carrying no business traffic is visible.
| Signal | Evidence | Response |
|---|---|---|
| No IKE SA | Journal, UDP 500/4500 capture, peer reachability | Check firewall, NAT, identity, PSK/certificate, and proposal. |
| IKE up, no CHILD SA | swanctl --list-sas, selector logs | Compare local and remote traffic selectors and child proposal. |
| SA up, one-way traffic | XFRM counters, LAN routes, forward/NAT counters | Check return route, forwarding, firewall state, and NAT exemption. |
| Small packets work, large fail | Size-controlled ping, capture, ICMP too-big | Measure path MTU and apply a justified MSS/MTU correction. |
| Frequent rekeys fail | Proposal and lifetime logs on both peers | Align supported algorithms and investigate time/load or packet loss. |
Safe rollback
Rollback should remove only the new connection and its scoped firewall, NAT, routing, and sysctl changes. Do not flush all XFRM state or replace the entire firewall when other VPNs or services share the host.
sudo swanctl --terminate --ike site-a-to-site-b
sudo mv /etc/swanctl/conf.d/site-to-site.conf \
/root/vpn-change-backup/site-to-site.conf.failed
sudo swanctl --load-all
sudo swanctl --list-conns
sudo swanctl --list-sas
sudo swanctl --list-pols
Restore the exact approved prior firewall and sysctl state through their owning systems. Verify local internet access, LAN routing, existing VPNs, exposed listeners, and remote administration. The Site B IKE name is site-b-to-site-a; use the actual loaded name on that gateway.
Troubleshooting workflow
| Failure | Evidence to collect | Likely boundary |
|---|---|---|
| No packets reach peer | ip route get, WAN capture, upstream counters | Route, security group, firewall, NAT, provider filtering |
AUTH_FAILED | Local/remote IDs, secret scope, certificate chain, peer log | Wrong identity, PSK mismatch, certificate/trust failure |
NO_PROPOSAL_CHOSEN | Loaded algorithms and proposals on both peers | No mutually supported IKE or ESP suite |
TS_UNACCEPTABLE | Loaded local/remote selectors on both peers | Subnet reversal, mask, overlap, or peer policy |
| One-way LAN traffic | XFRM counters, forward counters, route from destination host | Missing return route, firewall state, NAT, asymmetric routing |
| Tunnel drops behind NAT | UDP 4500 flow, DPD logs, NAT mapping timeout | NAT traversal or keepalive/firewall state |
| Large transfers stall | Packet size tests, TCP capture, PMTUD ICMP | MTU/MSS or filtered ICMP, not necessarily cryptography |
Do not respond to negotiation failure by opening the firewall to all sources, enabling weak proposals, disabling certificate validation, flushing all XFRM policies, or rebooting both gateways. Change one evidenced boundary, test, and roll back if the expected signal does not improve.
Production acceptance checklist
- Both protected networks are non-overlapping and the inventory reflects actual interfaces, NAT, and route ownership.
- Console access, backups, health checks, rollback steps, owners, and maintenance window are recorded.
- Only the peer can reach required IKE/ESP paths, and forwarding permits only approved cross-site flows.
- IP forwarding is enabled deliberately; ICMP redirects and NAT exemption match the topology.
- The deployment uses one control plane, current
swanctl/VICI for the new build, and no duplicate starter profile. - PSK or certificate material is fresh, protected, scoped to identities, transferred securely, and rotatable.
- IKE and ESP proposals are supported by both peers and exclude weak algorithms.
- Both CHILD SAs, XFRM policies, route decisions, counters, and application flows pass in both directions.
- Rekey, DPD recovery, service restart behavior, reboot persistence, and failure alerts have been tested.
- Monitoring detects tunnel-down, tunnel-up-but-idle, one-way traffic, repeated negotiation, capacity, and certificate or secret expiry.
For safe shell automation around checks and rollback, use the Bash Features guide. For protected remote administration of the gateways, use the SSH client operations guide. For host-level firewall planning, use the Advanced Policy Firewall guide.
Official references
- StrongSwan configuration quickstart and site-to-site case
- StrongSwan IKEv2 configuration examples
- StrongSwan swanctl.conf reference
- StrongSwan swanctl command reference
- StrongSwan algorithm proposals
- StrongSwan security recommendations
- StrongSwan forwarding, routing, and NAT guidance
- RHEL 9 networking and static-route configuration with NetworkManager


