GRE Tunnel Configuration on Linux
GRE (Generic Routing Encapsulation) is a tunneling protocol that encapsulates network packets within IP packets, enabling point-to-point connectivity between networks across IP infrastructure. This guide covers creating GRE tunnels on Linux, configuring routing, handling MTU considerations, adding IPsec encryption, and making tunnels persistent.
Prerequisites
- Ubuntu 20.04/22.04 or CentOS/Rocky Linux 8+
- Root or sudo access
- Two Linux hosts with public or private IP reachability between them
- GRE module support (built into most distribution kernels)
Create a Basic GRE Tunnel
Network setup for examples:
| Host A | Host B | |
|---|---|---|
| Public IP | 1.2.3.4 | 5.6.7.8 |
| GRE tunnel IP | 10.0.0.1/30 | 10.0.0.2/30 |
| Private subnet | 192.168.1.0/24 | 192.168.2.0/24 |
# Load the GRE kernel module (usually already loaded)
sudo modprobe ip_gre
lsmod | grep gre
# On Host A (public IP: 1.2.3.4):
sudo ip tunnel add gre0 \
mode gre \
local 1.2.3.4 \ # Host A's public IP
remote 5.6.7.8 \ # Host B's public IP
ttl 255
# Assign the tunnel IP
sudo ip addr add 10.0.0.1/30 dev gre0
sudo ip link set gre0 up
# On Host B (public IP: 5.6.7.8):
sudo ip tunnel add gre0 \
mode gre \
local 5.6.7.8 \
remote 1.2.3.4 \
ttl 255
sudo ip addr add 10.0.0.2/30 dev gre0
sudo ip link set gre0 up
# Test the tunnel (from Host A):
ping -c 3 10.0.0.2
Verify tunnel details:
# Show tunnel parameters
ip tunnel show gre0
ip -d link show gre0
# Check the tunnel interface
ip addr show gre0
Routing Over the GRE Tunnel
Route private subnets between the two sites through the tunnel:
# On Host A — add route to Host B's private subnet via the tunnel
sudo ip route add 192.168.2.0/24 via 10.0.0.2 dev gre0
# On Host B — add route to Host A's private subnet
sudo ip route add 192.168.1.0/24 via 10.0.0.1 dev gre0
# Enable IP forwarding on both hosts
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
# Verify routing (from Host A):
ping -c 3 192.168.2.1 # A host on Host B's private network
# Trace the path
traceroute 192.168.2.1
# Should show: 10.0.0.2 as first hop, then the destination
MTU and Fragmentation
GRE adds 24 bytes of overhead (20 bytes IP header + 4 bytes GRE header). On a standard 1500 byte MTU network:
# Set MTU to 1476 to avoid fragmentation (1500 - 24 = 1476)
sudo ip link set gre0 mtu 1476
# Verify the MTU
ip link show gre0 | grep mtu
# Test with the correct MTU using ping with DF bit
ping -c 3 -M do -s 1448 10.0.0.2 # 1448 data + 28 ICMP/IP = 1476
# Path MTU discovery — check for ICMP fragmentation needed
sudo tcpdump -i eth0 -n "icmp[icmptype]=3 and icmp[icmpcode]=4"
# If packets are being silently dropped due to MTU:
# Force TCP MSS clamping via iptables
sudo iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# For environments where ICMP is blocked (preventing PMTUD):
sudo iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss 1436 # Conservative: 1476 - 40 (TCP+IP headers)
GRE Keepalives
Linux doesn't natively support GRE keepalives in the kernel, but you can simulate them:
# Option 1: Use a periodic ping to detect tunnel failure
# Create a monitoring script
sudo tee /usr/local/bin/gre-monitor.sh > /dev/null <<'EOF'
#!/bin/bash
REMOTE_IP="10.0.0.2"
TUNNEL="gre0"
LOGFILE="/var/log/gre-monitor.log"
while true; do
if ! ping -c 2 -W 2 $REMOTE_IP > /dev/null 2>&1; then
echo "$(date): GRE tunnel DOWN - remote $REMOTE_IP unreachable" >> $LOGFILE
# Restart tunnel
ip link set $TUNNEL down
ip link set $TUNNEL up
echo "$(date): GRE tunnel restarted" >> $LOGFILE
fi
sleep 30
done
EOF
sudo chmod +x /usr/local/bin/gre-monitor.sh
# Run as a systemd service
sudo tee /etc/systemd/system/gre-monitor.service > /dev/null <<'EOF'
[Unit]
Description=GRE Tunnel Monitor
After=network.target
[Service]
ExecStart=/usr/local/bin/gre-monitor.sh
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable gre-monitor
sudo systemctl start gre-monitor
GRE over IPv6 (GRE6)
# Create a GRE tunnel over IPv6
# Load the IPv6 GRE module
sudo modprobe ip6_gre
# On Host A (IPv6: 2001:db8::1):
sudo ip tunnel add gre6tunnel \
mode ip6gre \
local 2001:db8::1 \
remote 2001:db8::2
sudo ip addr add 10.0.0.1/30 dev gre6tunnel
sudo ip link set gre6tunnel up
# On Host B (IPv6: 2001:db8::2):
sudo ip tunnel add gre6tunnel \
mode ip6gre \
local 2001:db8::2 \
remote 2001:db8::1
sudo ip addr add 10.0.0.2/30 dev gre6tunnel
sudo ip link set gre6tunnel up
# Test
ping -c 3 10.0.0.2
IPsec Encryption Over GRE
GRE provides no encryption. Add IPsec transport mode to encrypt the GRE traffic:
# Install strongSwan for IPsec
sudo apt install -y strongswan strongswan-pki
# Generate a pre-shared key
openssl rand -base64 32
# Configure IPsec (on both hosts)
sudo tee /etc/ipsec.conf > /dev/null <<'EOF'
config setup
charondebug="ike 1, knl 1"
conn gre-tunnel
authby=psk
type=transport # Transport mode (encrypts GRE, not original IP)
keyexchange=ikev2
left=1.2.3.4 # Host A public IP
right=5.6.7.8 # Host B public IP
leftprotoport=gre # Encrypt only GRE protocol (IP protocol 47)
rightprotoport=gre
auto=start
EOF
sudo tee /etc/ipsec.secrets > /dev/null <<'EOF'
1.2.3.4 5.6.7.8 : PSK "your-pre-shared-key-here"
EOF
sudo chmod 600 /etc/ipsec.secrets
# Start IPsec
sudo systemctl enable strongswan
sudo systemctl start strongswan
sudo ipsec up gre-tunnel
# Verify IPsec SA is established
sudo ipsec statusall | grep -A 5 "gre-tunnel"
# Verify encryption is happening
sudo ip xfrm policy
sudo ip xfrm state
Persistent GRE Tunnel Configuration
Ubuntu with systemd-networkd:
# /etc/systemd/network/10-gre-tunnel.netdev
sudo tee /etc/systemd/network/10-gre-tunnel.netdev > /dev/null <<'EOF'
[NetDev]
Name=gre0
Kind=gre
Description=GRE tunnel to Host B
[Tunnel]
Local=1.2.3.4
Remote=5.6.7.8
TTL=255
EOF
# /etc/systemd/network/11-gre-tunnel.network
sudo tee /etc/systemd/network/11-gre-tunnel.network > /dev/null <<'EOF'
[Match]
Name=gre0
[Network]
Address=10.0.0.1/30
[Route]
Destination=192.168.2.0/24
Gateway=10.0.0.2
EOF
sudo systemctl enable systemd-networkd
sudo systemctl restart systemd-networkd
CentOS/Rocky with network scripts:
# /etc/sysconfig/network-scripts/ifcfg-gre0
sudo tee /etc/sysconfig/network-scripts/ifcfg-gre0 > /dev/null <<'EOF'
DEVICE=gre0
BOOTPROTO=none
ONBOOT=yes
TYPE=GRE
MY_INNER_IPADDR=10.0.0.1
PREFIX=30
MY_OUTER_IPADDR=1.2.3.4
PEER_OUTER_IPADDR=5.6.7.8
TTL=255
EOF
sudo ifup gre0
Troubleshooting
Tunnel created but no ping:
# Verify the remote IP is reachable on the underlay
ping 5.6.7.8
# Check GRE module is loaded
lsmod | grep gre
# Verify firewall allows IP protocol 47 (GRE)
sudo iptables -L INPUT -n | grep -i "proto 47\|gre"
# UFW: allow GRE
sudo ufw allow proto gre
# iptables directly:
sudo iptables -A INPUT -p gre -j ACCEPT
# Check tunnel is up
ip link show gre0
ip tunnel show gre0
Routing works but traffic is slow:
# Check for fragmentation (likely MTU issue)
ping -c 10 -M do -s 1448 10.0.0.2
# If this fails, reduce the packet size until it succeeds to find PMTU
# Set appropriate MTU
sudo ip link set gre0 mtu 1476
# Enable PMTU discovery for TCP
sudo sysctl -w net.ipv4.tcp_mtu_probing=1
GRE tunnel disappears after reboot:
# Ensure configuration uses systemd-networkd or network-scripts
# Verify the .netdev and .network files exist
ls /etc/systemd/network/*gre*
# Check systemd-networkd is enabled
sudo systemctl is-enabled systemd-networkd
Conclusion
GRE tunnels provide a versatile, protocol-agnostic point-to-point encapsulation mechanism for connecting networks across IP infrastructure. While GRE alone provides no security, combining it with IPsec transport mode delivers encrypted site-to-site connectivity. For production deployments, always configure appropriate MTU values to prevent fragmentation and set up persistent configuration to survive reboots.


