Traditional IPsec VPN deployments in Linux rely on policy-based security associations (SAs). In a policy-based setup, the kernel inspects outgoing packets against a Security Policy Database (SPD); if traffic matches the defined subnets, it is encrypted and encapsulated. While effective for simple point-to-point connections, policy-based IPsec becomes unwieldy when scaling across complex multi-branch enterprise networks, dynamic routing protocols (such as BGP or OSPF), and custom traffic shaping queues.
To eliminate these limitations, Linux supports Virtual Tunnel Interfaces (VTI). VTI delivers a route-based IPsec implementation by representing the encrypted VPN tunnel as a standard virtual network interface in the Linux networking stack (e.g. vti0). Packets routed through the VTI interface are automatically mapped to matching IPsec security associations via an encryption key (Security Parameter Index mark). This tutorial explains how to create, configure, and manage route-based VTI tunnels in Linux using ip tunnel, ip link, and standard routing commands.
Route-Based VTI vs Policy-Based IPsec: Key Differences
Understanding the architecture of route-based networking helps demonstrate why VTI is the preferred modern tunneling standard:
- Standard Linux Routing: Because VTI exposes a dedicated interface name, you can direct traffic into the tunnel using standard Linux routing commands (
ip route add) or dynamic routing daemons (such as FRRouting/Quagga for BGP and OSPF). - Firewall and QoS Integration: You can apply
iptablesornftablesrules directly to the interface name (e.g.,-o vti0) and attach traffic shaping queues (tc) directly to the virtual device. - No GRE Encapsulation Overhead: Unlike GRE-over-IPsec (which encapsulates original packets inside both a GRE header and an ESP header), VTI integrates directly with the kernel’s XFRM framework. It avoids adding a 24-byte GRE header to every packet, reducing MTU fragmentation risks.
Prerequisites and Kernel Module Verification
Creating VTI devices requires the Linux kernel ip_vti module (or ip6_vti for IPv6). Before configuring the tunnel, verify that the module is loaded:
# Check if the VTI kernel module is loaded
lsmod | grep ip_vti
# If not loaded, load the module into the active kernel
sudo modprobe ip_vti
# Ensure the module persists across system reboots
echo "ip_vti" | sudo tee -a /etc/modules-load.d/vti.conf
Step 1: Create the VTI Tunnel Interface
VTI tunnels are instantiated using the ip tunnel add command with the mode parameter set to vti. In this example, assume the following network topology:
- Local Public Endpoint (Local Host):
203.0.113.10 - Remote Public Endpoint (Peer Gateway):
198.51.100.25 - Interface Name:
vti1 - VTI Key / XFRM Mark:
42(A numeric identifier linking the interface to the IPsec SA)
Execute the following command to create the tunnel interface:
# Create the VTI tunnel device
sudo ip tunnel add vti1 mode vti local 203.0.113.10 remote 198.51.100.25 key 42
Note: The key parameter specifies the mark value that the Linux kernel attaches to packets routed through this device. Your IPsec daemon (such as strongSwan or Libreswan) must be configured with a matching mark = 42 in its connection definition.
Step 2: Assign IP Addresses and Bring the Interface Up
Once the VTI tunnel device is registered in the kernel, assign point-to-point IP addresses to the tunnel endpoints and bring the link state up:
# Assign a local tunnel IP address (e.g. 10.50.0.1/30)
sudo ip addr add 10.50.0.1/30 dev vti1
# Bring the interface online
sudo ip link set vti1 up
Verify that the interface is created and in an operational state:
# Inspect the interface status
ip link show dev vti1
# Inspect the IP configuration
ip addr show dev vti1
# Inspect the tunnel parameters
ip tunnel show vti1
The command output will confirm that vti1 is operating in mode vti/ip with the assigned endpoints and key mark.
Step 3: Configure Routing Over the VTI Interface
Because VTI is a true routed interface, steering traffic into the encrypted tunnel is as simple as adding an ordinary static route. Suppose the remote branch network uses the subnet 172.16.10.0/24. You can route that subnet across the tunnel:
# Add a static route for the remote subnet via the VTI gateway
sudo ip route add 172.16.10.0/24 dev vti1
# Alternatively, route via the peer tunnel IP address
sudo ip route add 172.16.10.0/24 via 10.50.0.2 dev vti1
To verify the kernel routing table for the destination:
# Verify which interface handles outbound packets to the remote network
ip route get 172.16.10.100
Step 4: Configure MTU and TCP MSS Clamping
Because IPsec ESP encapsulation adds header overhead to each packet, the effective Maximum Transmission Unit (MTU) of a VTI tunnel is slightly lower than the physical interface’s default 1500 bytes. To avoid fragmentation and dropped TCP packets:
# Set an appropriate MTU on the VTI interface (typically 1400 - 1440 bytes)
sudo ip link set dev vti1 mtu 1420
# Clamp TCP MSS to prevent fragmentation over forwarded connections (using iptables)
sudo iptables -t mangle -A FORWARD -o vti1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
How to Monitor and Troubleshoot VTI Tunnels
If traffic is not passing across the tunnel, use the following diagnostic commands to locate the bottleneck:
- Check Packet Counters: Execute
ip -s link show dev vti1to see if transmit (TX) and receive (RX) packets are incrementing. If TX increments but RX remains zero, traffic is leaving the local host but not being returned by the remote gateway. - Inspect IPsec XFRM State: Verify that active security associations exist with the required mark by running
sudo ip xfrm stateandsudo ip xfrm policy. Ensure themarkfield matches thekeyspecified during tunnel creation. - Monitor Link Events in Real Time: Run
ip monitor linkorip monitor routein a separate terminal window to catch link state changes or interface flapping.
How to Delete or Decommission a VTI Interface
When the tunnel is no longer required or requires reconfiguration, take down the interface and remove it from the kernel:
# Bring the link down
sudo ip link set vti1 down
# Remove the tunnel interface
sudo ip tunnel del vti1