Modern 4G/LTE networks implement carrier-grade NAT (CGNAT), where multiple customers share a single public IP address. This breaks traditional VPN server setups because:
- No inbound connections can reach devices behind CGNAT
- Dynamic DNS becomes ineffective
- Port forwarding at the hotspot level doesn't help
The solution requires reversing the connection model. Instead of clients connecting to the server, we make the server initiate outbound connections to:
- A persistent cloud relay server
- Client devices with stable IPs
- A rendezvous service coordinating connections
Here's how to configure OpenVPN in "reverse client" mode:
# Server config (on your Pi)
client
remote your-relay-server.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
tls-client
auth SHA256
cipher AES-256-CBC
# Client config (on relay server)
mode server
tls-server
ifconfig 10.8.0.1 10.8.0.2
push "route 10.8.0.0 255.255.255.0"
WireGuard's simpler model works well with periodic keepalives:
# Pi configuration (mobile endpoint)
[Interface]
PrivateKey = [PI_PRIVATE_KEY]
Address = 10.0.0.2/24
[Peer]
PublicKey = [RELAY_PUBLIC_KEY]
Endpoint = relay.example.com:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
For maximum reliability, implement a connection broker:
# Python example using AWS IoT Core as broker
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
client.subscribe("vpn/connection/request")
def on_message(client, userdata, msg):
if msg.topic == "vpn/connection/request":
initiate_vpn_connection(msg.payload.decode())
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("AWS_IOT_ENDPOINT", 8883, 60)
client.loop_forever()
For solar-powered operation:
- Aggressive connection timeout settings
- Exponential backoff on retries
- Packet size optimization to reduce 4G data usage
- Connection coalescing (bundle multiple services over single VPN)
When building portable Raspberry Pi servers on 4G networks, we face a fundamental networking limitation: Carrier-Grade NAT (CGNAT). Unlike traditional broadband connections where you get a public IP, 4G/LTE networks place devices behind multiple layers of NAT, making inbound connections impossible. This creates significant challenges for:
- Remote SSH access to field-deployed devices
- VPN server functionality on mobile networks
- IoT deployments requiring persistent connections
The solution involves establishing persistent outbound connections from your NAT-trapped device to a publicly accessible relay server. Here's a proven architecture:
[Public VPS] ←→ [4G Raspberry Pi]
↑
[Client Devices]
WireGuard's low overhead makes it ideal for battery-powered setups. Configure the VPS as follows:
# /etc/wireguard/wg0.conf on VPS
[Interface]
Address = 10.0.0.1/24
PrivateKey = VPS_PRIVATE_KEY
ListenPort = 51820
# Raspberry Pi peer
[Peer]
PublicKey = PI_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25
On the Raspberry Pi:
# /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.2/24
PrivateKey = PI_PRIVATE_KEY
# VPS peer
[Peer]
PublicKey = VPS_PUBLIC_KEY
Endpoint = vps.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Create a systemd service to maintain the tunnel:
# /etc/systemd/system/wg-tunnel.service
[Unit]
Description=WireGuard Reverse Tunnel
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/wg-quick up wg0
ExecStop=/usr/bin/wg-quick down wg0
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
Set up SSH port forwarding on the VPS:
# On VPS, add to SSH config
Host pi-tunnel
HostName 10.0.0.2
Port 22
User pi
ServerAliveInterval 60
For environments where WireGuard isn't suitable:
- SSH Reverse Tunneling:
# On Raspberry Pi autossh -M 0 -N -R 2222:localhost:22 user@vps.example.com
- ngrok Alternative:
# Using bore (Rust alternative) bore local 22 --to bore.pub --port 2222
For solar-powered deployments:
- Set MTU to 1280 to reduce packet size
- Adjust PersistentKeepalive based on network conditions
- Implement connection quality monitoring with:
#!/bin/bash
while true; do
if ! ping -c 1 10.0.0.1 &> /dev/null; then
wg-quick down wg0
wg-quick up wg0
fi
sleep 60
done