StrongSwan Site-to-Site VPN: Step-by-Step Guide

· Published · 13 min read

Two private office networks connected through plain VPN gateways by one monitored encrypted site-to-site tunnel

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.

ComponentSite ASite B
IKE identitysite-a.example.netsite-b.example.net
Public or NAT-facing address198.51.100.10203.0.113.20
Private gateway address10.10.0.110.20.0.1
Protected LAN10.10.0.0/2410.20.0.0/24
WAN interface exampleeth0eth0
LAN interface exampleeth1eth1

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.

ChoiceUseBoundary
swanctl and VICIPrimary procedure in this guideRequires the VICI plugin, correct package, and matching service unit.
PSKSmall controlled deployment or interoperability needGenerate high entropy, distribute out of band, scope to explicit identities, rotate, and never publish.
X.509 certificatesPreferred for scalable production identity and independent rotationRequires 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.

Unsafe when run blindly: changing the default route, enabling forwarding on a multi-homed host, replacing a firewall ruleset, restarting remote networking, or stopping an active VPN can disconnect both sites or expose unintended paths. Verify target, preserve console access, stage one boundary at a time, define health checks, and have an automatic or operator-controlled rollback.

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.

ControlPSK deploymentCertificate deployment
Peer credentialOne high-entropy shared valueIndependent private key and end-entity certificate per gateway
Compromise responseCoordinate replacement on both peersRevoke or distrust affected certificate and replace that gateway credential
StrongSwan local authauth = pskauth = pubkey plus the gateway certificate
StrongSwan remote authExact remote identity and matching PSK scopeauth = pubkey, exact expected identity, and approved CA constraint
FilesSecret in protected configurationCertificate under /etc/swanctl/x509/, CA under x509ca/, private key under private/
Operational expiryPolicy-driven rotation dateNot-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.

SignalEvidenceResponse
No IKE SAJournal, UDP 500/4500 capture, peer reachabilityCheck firewall, NAT, identity, PSK/certificate, and proposal.
IKE up, no CHILD SAswanctl --list-sas, selector logsCompare local and remote traffic selectors and child proposal.
SA up, one-way trafficXFRM counters, LAN routes, forward/NAT countersCheck return route, forwarding, firewall state, and NAT exemption.
Small packets work, large failSize-controlled ping, capture, ICMP too-bigMeasure path MTU and apply a justified MSS/MTU correction.
Frequent rekeys failProposal and lifetime logs on both peersAlign 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

FailureEvidence to collectLikely boundary
No packets reach peerip route get, WAN capture, upstream countersRoute, security group, firewall, NAT, provider filtering
AUTH_FAILEDLocal/remote IDs, secret scope, certificate chain, peer logWrong identity, PSK mismatch, certificate/trust failure
NO_PROPOSAL_CHOSENLoaded algorithms and proposals on both peersNo mutually supported IKE or ESP suite
TS_UNACCEPTABLELoaded local/remote selectors on both peersSubnet reversal, mask, overlap, or peer policy
One-way LAN trafficXFRM counters, forward counters, route from destination hostMissing return route, firewall state, NAT, asymmetric routing
Tunnel drops behind NATUDP 4500 flow, DPD logs, NAT mapping timeoutNAT traversal or keepalive/firewall state
Large transfers stallPacket size tests, TCP capture, PMTUD ICMPMTU/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

Related technical notes

Mail queue investigation separating deferred traffic, SMTP responses, route health, and safe diagnostic evidenceLinux, MTA & Security · Jul 20, 2026 · 3 min read

Postfix Queue Backlog: A Safe Diagnostic Runbook

Diagnose a Postfix backlog from queue age, destination groups, SMTP responses, host health, DNS, and route evidence before changing retries or concurrency.

Technical review

Need this checked against your own sending system?

Share the domain, headers, bounces, provider warning, logs, or infrastructure symptom and NitWings will identify the practical next step.

Schedule a Technical Review
Advertisement