Skip to content

Instantly share code, notes, and snippets.

@marfillaster
Last active May 11, 2026 13:57
Show Gist options
  • Select an option

  • Save marfillaster/7580167fb1721b7a43002daf3382e6e5 to your computer and use it in GitHub Desktop.

Select an option

Save marfillaster/7580167fb1721b7a43002daf3382e6e5 to your computer and use it in GitHub Desktop.
MikroTik RouterOS v7 — IPv6-over-WireGuard Relay via Routed-/48 VPS

IPv6-over-WireGuard relay — VPS with a routed /48 + MikroTik

This is the simpler counterpart of the Vultr/on-link /64 recipe. Use this gist when your VPS provider routes a real prefix to your instance (e.g. WebHorizon). Use the older gist when your provider only assigns on-link IPv6 (e.g. Vultr) and you have to NDP-proxy.

When the prefix is routed, ndppd disappears, the reserved-IP fee disappears, and the address plan opens up — you have 65k /64s to carve up however you want.

Cost vs the Vultr variant

Item Vultr (on-link /64) This (routed /48)
VPS plan $5/mo $3/mo (e.g. WebHorizon SG, 1 TB)
Reserved IPv6 $3/mo $0 (already routed)
Bandwidth overage see Vultr's published rate $0.0025/GB
LAN address plan one /64 one /48 — 65k /64s
Extra daemon on VPS ndppd none
Total fixed $8/mo $3/mo

Resulting layout

                        Internet
                           │
                ┌──────────┴──────────┐
                │   VPS (Ubuntu)      │
                │   <VPS_IP>          │
                │ enp3s0:             │
                │   <UPSTREAM /128>    ← provider link addr (untouched)
                │   default v6 via provider gateway
                │ wg0: <LAN_PREFIX>:0::1/64    ← WG transit /64 from your /48
                └──────────┬──────────┘
                           │ WireGuard, UDP/51820
                           │
                ┌──────────┴──────────┐
                │  MikroTik v7        │
                │ wg-host:            │
                │   <LAN_PREFIX>:0::2/64
                │ bridge (LAN):       │
                │   <LAN_PREFIX>:1::1/64  (SLAAC to clients)
                │   <ULA_PREFIX>::1/64    (ULA, SLAAC + DNS)
                └──────────┬──────────┘
                           │
                        LAN clients
                        (SLAAC <LAN_PREFIX>:1::/64 + ULA + RDNSS)

For multiple VLANs/segments, allocate further /64s from the same /48:

<LAN_PREFIX>:0::/64  — WG transport
<LAN_PREFIX>:1::/64  — main LAN
<LAN_PREFIX>:10::/64 — guest VLAN
<LAN_PREFIX>:11::/64 — IoT VLAN
…

Placeholders

Substitute these before pasting:

Placeholder Where to get it
<VPS_IP> VPS provider panel → instance public IPv4
<VPS_NIC> Provider's NIC name (enp3s0, ens3, etc. — ip -o link to find)
<UPSTREAM_ADDR> / <UPSTREAM_GW> The /128 link address + gateway the provider configured (if not auto-configured via RA)
<LAN_PREFIX> The /48 (or /56) routed to your VPS — e.g. 2001:db8 if /48 is 2001:db8::/48
<ULA_PREFIX> A locally-generated ULA /64, e.g. fdXX:XXXX:XXXX:1. Generate the /48 with python3 -c 'import secrets; h=secrets.token_hex(5); print(f"fd{h[:2]}:{h[2:6]}:{h[6:]}".lower())' and append :1 (or any subnet id) for the /64.
<VPS_PRIVKEY> / <VPS_PUBKEY> wg genkey | tee server.key | wg pubkey on the VPS
<MT_PUBKEY> After creating the wireguard interface on MikroTik, /interface/wireguard/print shows it
<LAN_BRIDGE> MikroTik LAN bridge name — bridge in defconf

Examples below use <LAN_PREFIX>=2001:db8, <ULA_PREFIX>=fd00:dead:beef:1. These are documentation prefixes from RFC 3849 / RFC 4193 — substitute your own.

Provider note: choose a routed-prefix VPS

This recipe assumes the provider routes a prefix to your instance. Confirm in the panel that your IPv6 allocation says "routed /48" or "routed /56" (or that the prefix is a different /48 than the on-link transport subnet). If it's a single on-link /64 you can't subnet, you're in Vultr-land — see the other gist.

Reference setup used to test this recipe:

  • WebHorizon SG — routed /48, $3/mo, 1 TB included, $2.50/TB overage

Other providers may also route prefixes on certain plans. Check your provider's panel and docs (look for phrasing like "routed /48", "routed /56", or "delegated prefix") before assuming. Don't infer behavior from the on-link /64 they hand you for the instance itself.

1. VPS paste (run as root on Ubuntu)

set -e
apt-get update -qq
apt-get install -y -qq wireguard

cat >/etc/sysctl.d/99-wg-relay.conf <<'EOF'
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1
net.ipv6.conf.<VPS_NIC>.accept_ra = 2
EOF
sysctl --system >/dev/null

umask 077
mkdir -p /etc/wireguard
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
VPS_PRIVKEY=$(cat /etc/wireguard/server.key)

cat >/etc/wireguard/wg0.conf <<EOF
[Interface]
PrivateKey = ${VPS_PRIVKEY}
Address    = <LAN_PREFIX>:0::1/64
ListenPort = 51820
MTU        = 1420

[Peer]
# MikroTik — the NAT-traversing side runs PersistentKeepalive; not needed here.
PublicKey  = <MT_PUBKEY>
AllowedIPs = <LAN_PREFIX>:0::2/128, <LAN_PREFIX>:1::/64
EOF

# Firewall: allow WG, allow forwarding for the LAN prefix
ufw allow 51820/udp comment "WireGuard"
ufw route allow in on wg0
ufw route allow out on wg0
ufw reload || true

systemctl enable --now wg-quick@wg0

# Print VPS public key — you need it on the MikroTik side
echo "VPS public key: $(cat /etc/wireguard/server.pub)"

No ndppd. Because the /48 is routed, the provider's gateway sends packets for any address in it directly to your VPS. The kernel then forwards them through wg0 based on AllowedIPs. Done.

2. MikroTik paste (RouterOS v7)

# Create WG interface (RouterOS auto-generates its private key)
/interface/wireguard add name=wg-host listen-port=51820 mtu=1420

# Get the MikroTik's public key, then add the VPS as a peer
/interface/wireguard/peers add interface=wg-host name=vps \
    public-key="<VPS_PUBKEY>" \
    endpoint-address=<VPS_IP> endpoint-port=51820 \
    allowed-address=::/0 \
    persistent-keepalive=25s

# Address on WG transport /64
/ipv6/address add address=<LAN_PREFIX>:0::2/64 interface=wg-host advertise=no

# Address on LAN bridge — RA will pick this up automatically
/ipv6/address add address=<LAN_PREFIX>:1::1/64 interface=<LAN_BRIDGE> advertise=yes
/ipv6/address add address=<ULA_PREFIX>::1/64    interface=<LAN_BRIDGE> advertise=yes comment="ULA RFC 4193"

# Default v6 route via the VPS
/ipv6/route add dst-address=::/0 gateway=<LAN_PREFIX>:0::1%wg-host \
    comment="webhorizon primary"

# RA on bridge (skip if defconf RA is already covering bridge)
/ipv6/nd add interface=<LAN_BRIDGE> advertise-dns=yes dns=<ULA_PREFIX>::1 \
    managed-address-configuration=no other-configuration=no

# Allow WG-side inbound on input chain (defconf drops non-LAN input)
/ipv6/firewall/filter add chain=input action=accept in-interface=wg-host \
    comment="accept input from VPS WG peer" \
    place-before=[find where chain=input and comment="defconf: drop everything else not coming from LAN"]

3. Smoke test

On the VPS:

wg show                                       # latest handshake should be < 3 min
ping6 -c 2 <LAN_PREFIX>:0::2                  # WG transit reachable (MT side)
ping6 -c 2 <LAN_PREFIX>:1::1                  # MT LAN gateway reachable via wg0

On the MikroTik:

/ping 2606:4700:4700::1111 count=3            # Cloudflare via VPS

On a LAN client:

ip -6 addr | grep <LAN_PREFIX>                # confirm SLAAC picked up the /64
ping6 -c 3 2606:4700:4700::1111
curl -6 -s https://test-ipv6.com/json/        # expect "score":"10/10"

Cost recap

Line item Cost
VPS (routed /48, SG, 1 TB transfer) $3.00 / mo
Bandwidth overage if exceeded $0.0025/GB
Total $3.00 / mo

Compare with the Vultr variant ($8/mo) — $5/mo saved and a bigger prefix.

When to use which gist

Situation Use
Provider gives a routed /48 or /56 this gist
Provider only does on-link /64 (Vultr) Vultr gist
You need SLAAC on more than one VLAN this gist
You want a low-cost 24/7 SG relay with bundled bandwidth and a routed prefix this gist
You're stuck with Vultr for other reasons Vultr gist
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment