Raspberry Pi 4 configured as a split-tunnel VPN gateway — non-RU traffic exits via AmneziaWG VPN, Russian IP ranges exit direct via ISP, transparent to all LAN devices.
Документация на русском | Technical Reference
The RPi acts as the default gateway for all LAN devices. Traffic is split into two paths:
- Non-RU traffic exits through the AmneziaWG VPN tunnel (
awg0) - Russian IP ranges (updated daily from
russia.iplist.opencck.org) exit direct via ISP - LAN devices require no individual configuration — the split is fully transparent
Internet
↓
Router (192.168.1.1) — ISP uplink
↓ eth0
RPi4 (192.168.1.254) — VPN gateway
↓
LAN devices (default gateway = 192.168.1.254 via router DHCP)
Non-RU → awg0 → AmneziaWG VPN (endpoint: <VPN_SERVER_IP>:<port>)
RU CIDRs → eth0 → ISP direct (via 192.168.1.1)
Add to ~/.ssh/config on your Mac:
Host pi4
HostName 192.168.1.254
User ar
IdentityFile ~/.ssh/id_ed25519
Verify: ssh pi4 "echo ok" — must succeed without a password prompt.
VPN keys (.env.secrets):
cp .env.secrets.example .env.secrets
# Fill in AWG_PRIVATE_KEY, AWG_PUBLIC_KEY, AWG_PRESHARED_KEY (44-char base64 each)AmneziaWG config template (src/configs/amnezia.key.template.txt): holds server-specific obfuscation parameters (Jc, Jmin, Jmax, S1, S2, H1–H4) and the endpoint. Copy the [Interface] and [Peer] blocks from your AmneziaWG server's client config, then replace the key values with {{PrivateKey}}, {{PublicKey}}, {{PresharedKey}} placeholders — deploy.sh substitutes them at deploy time.
Network config (.env): committed to the repo, safe to edit. Update if your network differs from defaults:
SSH_HOST="pi4" # SSH alias for the RPi (from ~/.ssh/config)
RPI_LAN_IP=192.168.1.254 # RPi LAN IP
KEENETIC_GW=192.168.1.1 # ISP gateway (your router)
VPN_SERVER_IP=<your-server-ip> # AmneziaWG server IP — set in .env.secrets
CRON_UPDATE_HOUR=5 # Hour (0–23) for daily RU list refresh
Optional — ISP-bypass custom routes (src/configs/isp-routes-custom.txt): CIDRs that bypass VPN and exit via ISP, added on top of the auto-downloaded RU list. Create from example when needed:
cp src/configs/isp-routes-custom.txt.example src/configs/isp-routes-custom.txtOptional — VPN-force custom routes (src/configs/vpn-routes-custom.txt): CIDRs forced through VPN even if present in the auto-downloaded RU list. Highest-priority override. Create from example when needed:
cp src/configs/vpn-routes-custom.txt.example src/configs/vpn-routes-custom.txtAfter editing either custom-route file, use bash src/deploy-routes.sh instead of a full bash src/deploy.sh. It SCPs only the two custom-route files and runs routing.sh --no-update — skipping AmneziaWG install, key validation, and systemd setup. Takes seconds instead of minutes.
Optional — RU list exclusions (src/configs/ru-list-exclude.txt): CIDRs to strip from the downloaded RU list server-side (use when the RU list incorrectly includes a range you want tunneled). Create from example when needed:
cp src/configs/ru-list-exclude.txt.example src/configs/ru-list-exclude.txtAll three files are gitignored. Full workflow: docs/REFERENCE.md.
Gateway — set RPi as the LAN default gateway:
http://192.168.1.1→ Home network → Segments → Default → IP parameters- Set Gateway address to
192.168.1.254→ Save - LAN devices apply on next DHCP renewal (or disconnect/reconnect Wi-Fi)
DNS — required for domain names in splitgate status:
- Home network → Segments → Default → DNS server → set to
192.168.1.254→ Save
Rollback: clear Gateway address in router (set back to 192.168.1.1).
bash src/deploy.sh # deploy all files + activate routing
bash src/deploy.sh --no-run # deploy files only (use before tunnel is up)
bash src/deploy-routes.sh # fast push of custom-route files only (after editing isp-routes-custom.txt or vpn-routes-custom.txt)ssh pi4 "sudo awg-quick up awg0" # bring up VPN tunnel (manual — not idempotent)
ssh pi4 "sudo awg show" # verify peer handshakeRun on RPi via SSH. splitgate is the dispatcher CLI at /usr/local/bin/splitgate.
| Command | What it does |
|---|---|
splitgate status |
Recent connections with VPN/ISP routing and org info |
splitgate watch |
Real-time traffic stream |
splitgate rollback |
Undo VPN gateway entirely |
splitgate routing |
Rebuild split-tunnel routes |
splitgate update |
Refresh RU IP list now |
# Show recent connections
ssh pi4 "splitgate status"
ssh pi4 "splitgate status --via=vpn --last=100"
ssh pi4 "splitgate status --summary" # top-20 orgs by connection count
# Watch real-time traffic
ssh pi4 "splitgate watch"
ssh pi4 "splitgate watch --src 192.168.1.x --tag VPN"
# Rollback
ssh pi4 "splitgate rollback"Full CLI reference, verify routing, troubleshooting →
splitgate-watch.service runs watch-routes.py --daemon as a persistent systemd service, writing
connection logs to /etc/splitgate/logs/watch-YYYY-MM-DD.log (a new file each day, 14-day rotation).
Deployed by deploy.sh Stage 28.
# Service control
ssh pi4 "systemctl status splitgate-watch"
ssh pi4 "sudo systemctl restart splitgate-watch"
# Check today's log
ssh pi4 "tail -f /etc/splitgate/logs/watch-$(date +%F).log"
# Find ISP routes that failed to connect (✗ = not found in conntrack)
ssh pi4 "grep '[ISP] ✗' /etc/splitgate/logs/watch-$(date +%F).log"Each log line shows routing tag, connection status, source/destination, protocol:port, and org:
2026-05-29T10:14:00 [ISP] ✓ 192.168.1.237 → yandex.ru TCP:443 | TELETECH, RU
2026-05-29T10:14:05 [ISP] ✗ 192.168.1.237 → github.com TCP:443 | FASTLY, US
✓ = connection found in conntrack (ESTABLISHED/TIME_WAIT); ✗ = not found (UDP connections always show ✗).
Tuning workflow — if [ISP] ✗ lines point to RU CIDRs going via ISP that actually should go via VPN, uncomment the candidate block in src/configs/vpn-routes-custom.txt and re-run routing.sh.
| File | Location on RPi | Purpose |
|---|---|---|
install.log |
/etc/splitgate/logs/install.log |
Output from routing.sh and update-vpn-routes — download source, excluded CIDRs, route counts |
watch-YYYY-MM-DD.log |
/etc/splitgate/logs/watch-2026-05-29.log |
Daily connection log written by splitgate-watch.service in daemon mode |
watch-error.log |
/etc/splitgate/logs/watch-error.log |
stderr from watch-routes.py --daemon (startup errors, Python exceptions) |
# View install/routing log
ssh pi4 "sudo tail -20 /etc/splitgate/logs/install.log"
ssh pi4 "sudo grep vpn-routes /etc/splitgate/logs/install.log | tail -10"
# View today's watch log
ssh pi4 "sudo tail -f /etc/splitgate/logs/watch-$(date +%F).log"