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 AHost B
Public IP1.2.3.45.6.7.8
GRE tunnel IP10.0.0.1/3010.0.0.2/30
Private subnet192.168.1.0/24192.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.