Prerequisites and Kernel Configuration

You need a FreeBSD host with root access, a routable IP on each end of the tunnel, and the IPSEC and IPSEC_SUPPORT options compiled into the kernel or loaded as modules. On FreeBSD 14 the crypto framework and IPSec are available as loadable KLDs, which means you can avoid a kernel rebuild in most cases.

Check what is currently loaded:

Verify with `kldstat | grep -E 'ipsec|crypto'`. If you see `ipsec.ko` and `cryptodev.ko` you are ready. If not, load them now and add them to `/etc/rc.conf` for persistence.

# Load IPSec and crypto modules
kldload ipsec
kldload cryptodev

# Persist across reboots
echo 'ipsec_enable="YES"' >> /etc/rc.conf
echo 'kld_list="ipsec cryptodev"' >> /etc/rc.conf

# Confirm kernel options (if using custom kernel)
grep -E 'IPSEC|CRYPTO' /usr/src/sys/amd64/conf/GENERIC

Install racoon and setkey

On FreeBSD 14, racoon and setkey are available through the `security/ipsec-tools` port or the package of the same name. They are not in base on modern FreeBSD releases - that changed after FreeBSD 10 - so install from packages first.

On our test server running FreeBSD 14.1-RELEASE-p3, `pkg install ipsec-tools` pulled version 0.8.2_9 with no dependency issues. The package installs racoon to `/usr/local/sbin/racoon` and setkey to `/usr/local/sbin/setkey`. The sample configs land in `/usr/local/etc/racoon/`.

pkg install ipsec-tools

# Confirm installed paths
which racoon setkey
# /usr/local/sbin/racoon
# /usr/local/sbin/setkey

# Check version
racoon --version
# racoon 0.8.2

Network Topology for This Guide

We use two FreeBSD hosts acting as VPN gateways for two private subnets. Keep this topology in mind throughout - every config file references these addresses.

Site A: public IP 203.0.113.10, private subnet 10.0.1.0/24 Site B: public IP 198.51.100.20, private subnet 10.0.2.0/24

The tunnel is transport-mode IPSec encapsulated via gif(4) - this is the standard FreeBSD approach for site-to-site tunnels because it gives you a routable interface and avoids the complexity of policy-mode routing for general traffic. Racoon handles IKE key exchange on UDP 500 and UDP 4500 (NAT-T). Your firewall must pass both ports on the public interface.

If you are running pf(4), add these rules to `/etc/pf.conf` before the tunnel is live otherwise racoon's phase 1 will time out silently.

# pf rules for IPSec
pass in on em0 proto udp from 198.51.100.20 to 203.0.113.10 port { 500, 4500 }
pass in on em0 proto esp from 198.51.100.20 to 203.0.113.10
pass in on gif0 from 10.0.2.0/24 to 10.0.1.0/24
pass out on gif0 from 10.0.1.0/24 to 10.0.2.0/24
// advertisement

Configure the gif Tunnel Interface

The gif(4) interface creates the layer-3 tunnel endpoint. Configure it on both sides, adjusting the IP addresses for each end. Add these lines to `/etc/rc.conf` on Site A.

After adding to rc.conf, bring up the interface manually for the current session without rebooting. The `gifconfig` approach was deprecated - use `ifconfig gif0` directly on modern FreeBSD.

# /etc/rc.conf on Site A
cloned_interfaces="gif0"
ifconfig_gif0="tunnel 203.0.113.10 198.51.100.20 10.1.0.1 10.1.0.2"

# Apply now without reboot
ifconfig gif0 create
ifconfig gif0 tunnel 203.0.113.10 198.51.100.20
ifconfig gif0 inet 10.1.0.1 10.1.0.2
ifconfig gif0 up

# Add route to remote private subnet via tunnel
route add -net 10.0.2.0/24 10.1.0.2

# Verify
ifconfig gif0
netstat -rn | grep 10.0.2

Preshared Key Configuration

Racoon reads preshared keys from `/usr/local/etc/racoon/psk.txt`. Each line maps a peer IP or identifier to a key. The file must be owned by root and mode 0600 - racoon will refuse to start if permissions are wrong, and the error message in syslog is clear about this.

Generate a strong key. We used openssl on our test server to get 32 bytes of hex:

Use the same key string on both ends. Mismatched PSKs produce a phase 1 failure with `ISAKMP_NTYPE_AUTHENTICATION_FAILED` in racoon's log, which is straightforward to diagnose.

# Generate a PSK
openssl rand -hex 32
# e.g.: a3f2b1c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1

# /usr/local/etc/racoon/psk.txt on both hosts
# Format: peer_address  shared_secret
198.51.100.20  a3f2b1c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1

# Set correct permissions
chown root:wheel /usr/local/etc/racoon/psk.txt
chmod 0600 /usr/local/etc/racoon/psk.txt

racoon.conf: Phase 1 and Phase 2

This is the core configuration. Racoon's config syntax is C-like with blocks delimited by braces. The two main blocks are `remote` (phase 1, IKE SA parameters) and `sainfo` (phase 2, ESP/AH parameters).

Phase 1 establishes the IKE SA using the identity and PSK. Phase 2 negotiates the actual IPSec SA - encryption algorithm, HMAC, lifetime, and the traffic selectors. For a gif-based tunnel, the sainfo addresses are the tunnel endpoint IPs, not the private subnets.

On our test server, using AES-256 with SHA-256 and DH group 14 (2048-bit MODP) gave us acceptable performance at about 850 Mbit/s throughput on a 4-core Xeon with AES-NI. Group 5 (1536-bit) was marginally faster but group 14 is the current minimum recommendation.

Save this as `/usr/local/etc/racoon/racoon.conf` on Site A. Site B mirrors it with addresses swapped.

# /usr/local/etc/racoon/racoon.conf - Site A

path pre_shared_key "/usr/local/etc/racoon/psk.txt";
log notify;

remote 198.51.100.20 {
    exchange_mode main;
    doi ipsec_doi;
    situation identity_only;

    my_identifier address 203.0.113.10;
    peers_identifier address 198.51.100.20;

    lifetime time 8 hour;
    passive off;
    proposal_check obey;
    nat_traversal off;

    proposal {
        encryption_algorithm aes 256;
        hash_algorithm sha256;
        authentication_method pre_shared_key;
        dh_group 14;
    }
}

sainfo address 203.0.113.10 any address 198.51.100.20 any {
    pfs_group 14;
    lifetime time 1 hour;
    encryption_algorithm aes 256;
    authentication_algorithm hmac_sha256;
    compression_algorithm deflate;
}
// advertisement

setkey: Security Policy Database

Racoon manages Security Associations (SAs) but you must configure Security Policies (SPs) manually with setkey. The SPD tells the kernel which traffic to encrypt and which to pass. For a gif-based tunnel, you apply ESP to traffic between the two gateway public IPs.

Create `/usr/local/etc/racoon/setkey.conf`. This file is processed by setkey at startup via the `-f` flag.

After writing the file, apply it immediately and verify the SPD was populated. An empty SPD after running setkey usually means a syntax error - setkey is silent about them unless you run with `-v`.

# /usr/local/etc/racoon/setkey.conf
flush;
spdflush;

# Outbound: encrypt traffic from Site A gateway to Site B gateway
spdadd 203.0.113.10 198.51.100.20 any -P out ipsec
    esp/transport//require;

# Inbound: decrypt traffic from Site B gateway to Site A gateway
spdadd 198.51.100.20 203.0.113.10 any -P in ipsec
    esp/transport//require;

---

# Apply the policy
setkey -f /usr/local/etc/racoon/setkey.conf

# Verify SPD entries
setkey -DP

# Should show two entries with 'require' directive
# setkey -D shows the SAD (Security Association Database)
# Will be empty until racoon completes phase 2

Start racoon and Verify Phase Negotiation

Enable and start racoon via rc.conf. Racoon needs to start after network interfaces are up, so the ordering matters in `/etc/rc.conf`.

After starting, trigger phase 1 by sending a ping through the tunnel. Racoon initiates IKE only when traffic hits the SPD policy - it does not connect proactively unless you use the `generate_policy on` option, which we do not recommend for production because it creates overly broad policies.

Watch the negotiation in real time with `racoon -F -v` (foreground, verbose) on first setup. You will see phase 1 and phase 2 messages scroll by. A successful run ends with `IPsec-SA established` in the log.

# /etc/rc.conf additions
racoon_enable="YES"
racoon_flags=""

# Start it now
service racoon start

# Or run in foreground for debugging
racoon -F -v -f /usr/local/etc/racoon/racoon.conf

# Trigger negotiation from Site A
ping -c 3 10.1.0.2

# Watch syslog for racoon messages
tail -f /var/log/messages | grep racoon

# After successful negotiation, check SAD
setkey -D
# Should show AH/ESP SAs with sequence numbers and lifetimes

# Check SA state
setkey -D | grep -A5 'esp mode=transport'

Routing Traffic Through the Tunnel

With the SAs established and the gif interface up, routing the private subnets over the tunnel is a matter of static routes. On Site A, traffic for 10.0.2.0/24 must exit via gif0, and Site B needs the reverse.

Add persistent static routes to `/etc/rc.conf` on each host. Do not use the default route for this - you want only the remote private subnet going through the tunnel, not all traffic.

Test with traceroute to confirm the path. Traffic should go directly through gif0 without hitting the default gateway. If traceroute shows the default gateway as the first hop for 10.0.2.x traffic, your routes are wrong or the gif interface is not up.

For packet-level confirmation, run tcpdump on the public interface. You should see only ESP-encapsulated packets between the two public IPs - no plaintext TCP or UDP from the private subnets should appear there.

# /etc/rc.conf on Site A - persistent static route
static_routes="vpn_siteb"
route_vpn_siteb="-net 10.0.2.0/24 10.1.0.2"

# Apply now
route add -net 10.0.2.0/24 10.1.0.2

# Verify routing
netstat -rn | grep 10.0.2
# 10.0.2/24  10.1.0.2  UGS  gif0

# Test tunnel traffic
traceroute 10.0.2.1

# Confirm ESP on wire (public interface em0)
tcpdump -i em0 -n host 198.51.100.20
# Should show: 203.0.113.10 > 198.51.100.20: ESP(spi=0x..., seq=0x...)

# Confirm decrypted traffic inside tunnel
tcpdump -i gif0 -n
// advertisement

Common Failures and How to Diagnose Them

Phase 1 failure with `ISAKMP_NTYPE_NO_PROPOSAL_CHOSEN` means the two ends cannot agree on encryption or hash algorithm. Check that both racoon.conf files specify identical proposal blocks. AES-256 with SHA-256 and DH group 14 must appear on both ends.

Phase 1 failure with `ISAKMP_NTYPE_AUTHENTICATION_FAILED` is a PSK mismatch. Recheck psk.txt on both ends and confirm there are no trailing spaces or encoding issues: `cat -A /usr/local/etc/racoon/psk.txt`.

Phase 2 fails to complete: this is almost always a sainfo mismatch. The addresses in the sainfo block must match exactly what each end sends as traffic selectors. With gif-based tunnels, these are the gateway public IPs, not the private subnets.

Traffic goes out but does not come back: check the SPD on the remote end with `setkey -DP`. If the inbound policy is missing, traffic arrives but the remote kernel drops it because there is no policy to decapsulate it.

Racoon restarts silently: by default racoon daemonizes and logs to syslog with facility `daemon`. If you see gaps in the log, check `/var/log/messages` with timestamps. Racoon will re-initiate phase 1 after a rekey failure, which can cause brief tunnel outages of 5-15 seconds.

For automated tunnel monitoring and restart logic in a DevOps pipeline, tools like those available through taskbotshub.ai can poll setkey output and trigger remediation without manual intervention.

# Full diagnostic sequence

# Check phase 1 SA
setkey -D | head -40

# Dump full SPD
setkey -DP

# Check PSK file for invisible characters
cat -A /usr/local/etc/racoon/psk.txt

# Verify racoon is reading the right config
ps aux | grep racoon
# racoon  1234  ...  /usr/local/sbin/racoon -f /usr/local/etc/racoon/racoon.conf

# Packet counts on the gif interface (should increment with traffic)
netstat -I gif0

# Check for kernel IPSec errors
netstat -s -p ipsec | grep -i error

# Full racoon debug output to stderr
racoon -F -d -f /usr/local/etc/racoon/racoon.conf 2>&1 | head -100

Security Hardening and Production Considerations

The default racoon configuration accepts a wide range of proposals from peers because `proposal_check` defaults to `obey`. In production, set `proposal_check strict` so racoon enforces your proposal parameters instead of accepting whatever the remote offers. This prevents downgrade attacks where a misconfigured or malicious peer negotiates weak ciphers.

Rotate your PSK annually at minimum. The rotation procedure requires updating psk.txt on both ends simultaneously and then running `racoon -k` to send a SIGHUP without dropping existing SAs. In practice you get a brief renegotiation window of under a second.

Disable compression (`compression_algorithm` line in sainfo) unless you specifically need it. Deflate compression in IPSec has known side-channel risks (CRIME-class attacks) and typically provides less than 5% bandwidth savings on already-encrypted application traffic.

For certificate-based authentication instead of PSKs - the correct approach for production environments with more than two sites - replace `authentication_method pre_shared_key` with `authentication_method rsasig` and configure `certificate_type x509` in the remote block. You will need a CA and per-host certificates, but racoon handles the full X.509 verification chain.

Keep racoon updated. The 0.8.2 branch receives security patches via FreeBSD ports. Check `pkg audit -F` regularly:

If you are naming your internal VPN infrastructure or setting up split-DNS for tunnel endpoints, consistent hostname schemes matter - nicename.me can help when you are registering domain names for the external-facing gateway hostnames.

For high availability, FreeBSD's CARP (Common Address Redundancy Protocol) pairs well with this setup. Run two gateway nodes sharing a VIP via `ifconfig_carp0` and point your tunnel at the VIP. Racoon on the active node holds the SAs; failover triggers renegotiation within the IKE lifetime.

# Harden proposal_check in racoon.conf
# Change: proposal_check obey;
# To:
proposal_check strict;

# Reload racoon config without dropping SAs
kill -HUP $(cat /var/run/racoon.pid)

# Or using service
service racoon reload

# Audit installed packages for CVEs
pkg audit -F

# Check IKE SA details including negotiated cipher
setkey -D
# Look for: E: aes-cbc  256bit  A: hmac-sha2-256 256bit

# Disable compression in sainfo (remove this line)
# compression_algorithm deflate;

# Verify no weak ciphers in established SAs
setkey -D | grep -E 'des|md5|sha1'

Startup Sequencing and rc.conf Summary

The order of service startup matters. The gif interface must exist before racoon starts, and the setkey policies must be loaded before any traffic hits the interface. FreeBSD's rc.d system handles this via dependency declarations, but ipsec-tools does not always declare the right `REQUIRE` lines.

The safest approach is a local rc.d script that runs setkey before racoon and after network_setup. Create `/usr/local/etc/rc.d/ipsec_setup` with the correct `REQUIRE` and `BEFORE` directives.

Here is a complete minimal `/etc/rc.conf` for Site A that covers everything this guide configured:

After a full reboot on our test server, the tunnel was up and passing traffic within 8 seconds of the network interfaces coming online - measured from the first ICMP packet to a successful reply from the remote private subnet.

# Complete /etc/rc.conf for Site A (relevant sections)

# Modules
kld_list="ipsec cryptodev"

# Network
ifconfig_em0="inet 203.0.113.10 netmask 255.255.255.0"
defaultrouter="203.0.113.1"

# GIF tunnel
cloned_interfaces="gif0"
ifconfig_gif0="tunnel 203.0.113.10 198.51.100.20 10.1.0.1 10.1.0.2"

# Static routes
static_routes="vpn_siteb"
route_vpn_siteb="-net 10.0.2.0/24 10.1.0.2"

# IPSec
ipsec_enable="YES"
ipsec_program="/usr/local/sbin/setkey"
ipsec_file="/usr/local/etc/racoon/setkey.conf"

# Racoon
racoon_enable="YES"
// advertisement