GRE on Linux: ip tunnel add Commands and Examples

Configure GRE tunnels on Linux with ip tunnel add. Cisco-to-Linux interop, MTU, MSS clamping, IPsec, gretap (L2), and persistence with systemd-network

The Linux kernel has supported GRE tunnels natively since the 2.0 series, and the modern iproute2 userland makes setting one up genuinely simple. If you operate routers from a Cisco perspective and find yourself bridging to a Linux box (a cloud VM, a virtual appliance, an open-source firewall, a mid-range MikroTik analogue running ROS, or your own laptop in a lab), this is the article that translates the configuration model from one to the other. Same protocol, different syntax.

Part of the PingLabz GRE Tunnels: The Complete Guide cluster.

When You Need Linux GRE

Three common cases:

  • Cloud VMs as tunnel endpoints. A Linux EC2 instance, an Azure VM, or a GCP Compute Engine VM acting as a GRE termination for spoke traffic. Common for cloud-on-ramps that pre-date managed VPN gateways.
  • Open-source routers and firewalls. Linux-based platforms (VyOS, OPNsense, pfSense, FRRouting on Debian) all expose GRE through the same kernel interface. The CLI varies; the underlying mechanism is identical.
  • Building lab tunnels without Cisco gear. When you want to demonstrate GRE behavior, build a packet capture, or test a routing-protocol-over-GRE design without spinning up CSR1000v instances, two Linux VMs with ip tunnel commands work fine.

The Linux GRE implementation interoperates cleanly with Cisco for the core functionality (encapsulation, decapsulation, basic tunneling). It does not natively implement Cisco GRE keepalives, so liveness detection across a Cisco-Linux GRE pair has to come from the routing protocol or a separate mechanism.

Basic GRE Tunnel on Linux

Equivalent to the Cisco config from the config lab. Two endpoints, one tunnel between them.

# ---- linux-1 (198.51.100.1) ----
sudo ip tunnel add tun0 mode gre \
    local 198.51.100.1 \
    remote 203.0.113.1 \
    ttl 255
sudo ip addr add 10.0.0.1/30 dev tun0
sudo ip link set tun0 up

# ---- linux-2 (203.0.113.1) ----
sudo ip tunnel add tun0 mode gre \
    local 203.0.113.1 \
    remote 198.51.100.1 \
    ttl 255
sudo ip addr add 10.0.0.2/30 dev tun0
sudo ip link set tun0 up

ip tunnel add creates the GRE interface. The local and remote are the underlay endpoints (the equivalent of Cisco's tunnel source and tunnel destination). The ip addr add assigns the overlay IP. The ip link set tun0 up brings the interface administratively up.

The Cisco-style tunnel naming is just convention; you can call the interface anything (gre0, tun0, mytun). Some distributions reserve gre0 for a fallback interface; explicit naming is safer.

Verifying the Tunnel

linux-1$ ip -d link show tun0
3: tun0@NONE:  mtu 1476 qdisc noqueue state UNKNOWN
    link/gre 198.51.100.1 peer 203.0.113.1
    promiscuity 0
    gre remote 203.0.113.1 local 198.51.100.1 ttl 255 \
    pmtudisc

linux-1$ ip addr show tun0
3: tun0@NONE:  mtu 1476 qdisc noqueue state UNKNOWN group default qlen 1000
    link/gre 198.51.100.1 peer 203.0.113.1
    inet 10.0.0.1/30 scope global tun0
       valid_lft forever preferred_lft forever

linux-1$ ping -c 5 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=4.21 ms

The MTU 1476 is Linux doing the same math Cisco does: 1500 minus 24 bytes of GRE+IP overhead. The POINTOPOINT,NOARP flags reflect that GRE is a point-to-point Layer 3 link with no ARP needed (the next-hop is implicitly the other end of the tunnel).

Adding Routes Through the Tunnel

Linux GRE tunnels do not auto-add routes. Add explicit static routes for any subnet that should reach across the tunnel:

sudo ip route add 192.168.2.0/24 via 10.0.0.2 dev tun0

Or, for dynamic routing, use FRRouting (the modern Linux routing daemon, used by VyOS and others). FRR's OSPF or BGP can run on the tunnel interface just like Cisco's:

vtysh# conf t
vtysh(config)# router ospf
vtysh(config-router)# network 10.0.0.0/30 area 0
vtysh(config-router)# network 192.168.1.0/24 area 0

MTU and MSS on Linux

The same MTU concerns from the GRE MTU article apply. To set the inner MTU and MSS:

sudo ip link set tun0 mtu 1400

# MSS clamping in iptables (older systems):
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -o tun0 \
    -j TCPMSS --clamp-mss-to-pmtu

# MSS clamping in nftables (modern systems):
sudo nft add rule inet mangle forward oifname tun0 \
    tcp flags syn tcp option maxseg size set rt mtu

--clamp-mss-to-pmtu tells the kernel to compute the MSS dynamically from the path MTU. That is more flexible than hard-coding 1360 the way Cisco's ip tcp adjust-mss typically does, and it tracks if you change the tunnel MTU later. The functional outcome is the same: TCP endpoints negotiate segment sizes that fit through the tunnel without fragmentation.

Liveness on Linux GRE

Linux's native GRE has no Cisco-style keepalives. Three options for liveness detection:

  • Routing-protocol dead-timers. If you are running OSPF / BGP across the tunnel, the routing protocol's hello and dead timers serve as liveness detection. Tune them aggressively (OSPF hello 1, dead 4) for fast detection.
  • BFD. Bidirectional Forwarding Detection in FRRouting works on tunnel interfaces. Sub-second detection, lower overhead than tightly-tuned routing-protocol timers, and standardized so it interoperates with Cisco BFD.
  • Userland keepalive scripts. A simple cron-driven script that pings the remote tunnel IP and triggers a recovery action (route update, alert) on failure. Crude but effective for small deployments.

For a Cisco-to-Linux tunnel, the cleanest answer is to disable Cisco GRE keepalives on the Cisco side and let the routing protocol or BFD provide liveness. Mixing Cisco keepalives with a Linux end that does not understand them just produces confusing one-way "down" events on the Cisco router.

GRE Variants on Linux

Linux supports several GRE encapsulation modes. The mode parameter to ip tunnel add determines which.

modeWhat it carriesCisco equivalent
greIPv4 over IPv4 (the default)tunnel mode gre ip
ip6greIPv4 / IPv6 over IPv6tunnel mode gre ipv6
gretapEthernet (L2) over IP - L2 tunnelingtunnel mode ethernet gre
ip6gretapEthernet over IPv6(IOS XE limited)

gretap is the interesting variant for engineers used to Cisco. It carries Ethernet frames over GRE, providing a Layer 2 stretch between two Linux endpoints. This is the same idea as Cisco's L2TPv3 or EoMPLSoGRE in service-provider designs but without the SP overhead. Useful for cloud overlay networking and labs.

Optional GRE Header Fields

Linux supports the GRE Key field (RFC 2890) for distinguishing multiple parallel tunnels with the same source/destination, and the Sequence Number field for in-order delivery (rarely used).

sudo ip tunnel add tun0 mode gre \
    local 198.51.100.1 \
    remote 203.0.113.1 \
    key 12345 \
    ttl 255

The Key value must match on both ends. Linux accepts a numeric (4-byte) Key or a dotted-quad notation. For DMVPN-style multipoint setups Linux has limited native support; ip tunnel can build point-to-point tunnels but does not directly implement NHRP. Quagga / FRRouting plus userland helpers can build something DMVPN-shaped, but it is more involved than the Cisco equivalent.

Adding IPsec on Linux

Linux IPsec (the kernel XFRM framework with strongSwan or libreswan in userland) wraps GRE the same way Cisco IPsec does. The pattern: build the GRE tunnel, configure IPsec to protect IP protocol 47 between the two underlay IPs.

# /etc/ipsec.conf with strongSwan
conn gre
    left=198.51.100.1
    right=203.0.113.1
    type=transport
    leftprotoport=gre
    rightprotoport=gre
    auto=start
    authby=psk
    ike=aes256-sha256-modp2048
    esp=aes256-sha256

This is the moral equivalent of the Cisco crypto-map approach: classify GRE traffic between specific endpoints, encrypt it. The transport mode reuses the existing GRE outer IP rather than adding another. For a deeper IPsec-on-Linux walkthrough, the strongSwan documentation is the canonical reference; for the Cisco perspective, see GRE over IPsec on IOS XE.

Cisco to Linux GRE

The minimal interop config:

! Cisco R1
interface Tunnel0
 ip address 10.0.0.1 255.255.255.252
 ip mtu 1400
 ip tcp adjust-mss 1360
 tunnel source 198.51.100.1
 tunnel destination 203.0.113.1
 tunnel mode gre ip
 ! Note: no 'keepalive' - Linux peer does not understand it
# Linux peer
sudo ip tunnel add tun0 mode gre local 203.0.113.1 remote 198.51.100.1 ttl 255
sudo ip link set tun0 mtu 1400
sudo ip addr add 10.0.0.2/30 dev tun0
sudo ip link set tun0 up
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -o tun0 \
    -j TCPMSS --clamp-mss-to-pmtu

Both ends should report up after the link state propagates. ping 10.0.0.2 from R1 and ping 10.0.0.1 from the Linux box should both succeed. This is the cleanest interop case; complications arise when you want OSPF (FRR works fine), GRE keepalives (skip them), or DMVPN (not natively on Linux).

Making It Persistent

The ip tunnel add commands above are runtime-only. They disappear on reboot. To persist, use whatever the distro's network management framework expects:

Distro / frameworkPersistence file
Debian / Ubuntu /etc/network/interfaces (legacy)auto tun0 + iface tun0 inet static stanza with up ip tunnel add ...
NetworkManager (modern)nmcli connection add type ip-tunnel ...
systemd-networkd.netdev file with [Tunnel] section, .network file for IP address
VyOS / OPNsense / pfSenseVendor-specific CLI / GUI

For systemd-networkd (modern Debian / Ubuntu / RHEL), the cleanest approach:

cat > /etc/systemd/network/10-gre.netdev << 'EOF'
[NetDev]
Name=tun0
Kind=gre

[Tunnel]
Local=198.51.100.1
Remote=203.0.113.1
TTL=255
EOF

cat > /etc/systemd/network/10-gre.network << 'EOF'
[Match]
Name=tun0

[Network]
Address=10.0.0.1/30
EOF

sudo systemctl restart systemd-networkd

Summary

Linux GRE is the same protocol Cisco IOS XE implements, configured through ip tunnel commands or distro-specific persistence files. The wire format is identical, the encapsulation overhead is identical, and Linux interoperates cleanly with Cisco at the basic-tunneling level. The differences are in the surrounding ecosystem: Linux does not natively implement Cisco GRE keepalives, native DMVPN/NHRP support is limited, and the routing-protocol layer is FRRouting rather than IOS XE.

For a Cisco-to-Linux tunnel, build the basic GRE on both ends, set MTU 1400 and MSS clamping on both ends, run a routing protocol over the top, and skip Cisco-specific keepalives. The resulting tunnel performs the same as Cisco-to-Cisco. Full PingLabz GRE coverage is at the cluster pillar.

Read next

© 2025 Ping Labz. All rights reserved.