[ Reaching a Network Behind Starlink with WireGuard ]
[ Overview |
Why port forwarding fails |
Your options |
What you need |
Step 1: the VPS |
Step 2: the site |
On OPNsense |
Step 3: laptops and phones |
Step 4: test it |
The IPv6 route |
Lock it down |
When it doesn't work |
FAQ ]
[ Overview ]
Starlink gets you online almost anywhere with a view of the sky. Getting back in from the outside is the hard part. A residential Starlink sits behind carrier-grade NAT, so there's no public IPv4 address to forward a port to, and Starlink's own router has no port forwarding anyway. Starlink is my backup line at home, and I carry a Starlink Mini when I travel. At home the Starlink router still sits in front of my firewall, so it's double NAT on top of CGNAT. When a primary link drops, anything that waits for an incoming connection stops working. The fix is to turn the connection around. Nothing has to reach in if the site behind Starlink dials out. A small VPS with a real public IP sits in the middle, the site keeps a WireGuard tunnel open to it, my laptop and phone connect to the same VPS, and the VPS routes between them:
Both arrows point at the VPS. Nothing ever connects to the Starlink side, so CGNAT, double NAT and a public address that keeps changing stop mattering. And because the site is the one dialing, the tunnel comes back by itself after an outage, over whichever internet line is up at the time. Every address in this guide is an example. 203.0.113.10 is a documentation address: use your VPS's real IP, and pick your own subnets.
[ Why port forwarding fails ]
Check what you have first. Compare the WAN address your router shows with the address the internet sees: curl -4 ifconfig.me If the router's WAN address falls in 100.64.0.0/10 (100.64.x.x through 100.127.x.x), you're behind carrier-grade NAT. That range is set aside for it in RFC 6598. Your public address belongs to Starlink's equipment and is shared with other customers, so nothing you forward at home will ever see an incoming connection. Bypass mode doesn't change that. It only switches off the Starlink router's own NAT, so your router's WAN port gets the 100.64.x.x address directly. Starlink's CGNAT is still upstream, and inbound IPv4 still stops there. The Public IP option on the Priority plans (below) is the only IPv4 way past it. IPv6 doesn't go through it at all. A traceroute shows it too: a few hops in, you'll pass an address like 100.64.0.1, Starlink's CGNAT. The hops after it show where your traffic leaves Starlink's network (the reverse DNS names usually include the city), and that can be a long way from where your public address geolocates. That matters later, when you pick where the VPS goes.
[ Your options ]
A public IPv4 Starlink offers one only on Local and Global Priority
plans: you switch the IP policy to Public IP in your
account. It survives reboots, but it can change if
you move the dish or after a software update.
(Starlink's article)
IPv6 Starlink hands out IPv6 too, and IPv6 has no NAT. It
needs your own router in bypass mode, and it only
works from networks that have IPv6. More below.
A VPS hub works on any plan, from any network, through any
amount of NAT. This guide.
A mesh VPN Tailscale, Headscale, NetBird or ZeroTier. They punch
through NAT for you and fall back to a relay when
they can't.
This guide uses the VPS hub. It needs nothing from Starlink's side, and plain
WireGuard runs on almost every router, computer and phone.
[ What you need ]
A VPS any provider's smallest Linux plan. It only moves packets
for a few devices. It needs a public IPv4 address that
doesn't change.
The site something behind Starlink that runs WireGuard and stays
on: your own router (OPNsense, OpenWrt, MikroTik and most
others can), or a Raspberry Pi on the LAN.
Clients the WireGuard app on phones, Windows and macOS, or
wireguard-tools on Linux.
Pick your subnets now, and pick ones that won't clash with networks you'll
join later. If the site's LAN is 192.168.1.0/24 (the Starlink router's
default) or 192.168.0.0/24, sooner or later you'll sit on hotel or a
friend's Wi-Fi using the same range, and your tunnel loses. This guide uses:
10.99.0.0/24 the tunnel: VPS .1, site .2, clients from .10 up
192.168.50.0/24 the LAN behind Starlink
[ Step 1: the VPS ]
On a fresh Ubuntu or Debian VPS, everything in this step runs as root:
sudo -i
apt update && apt install wireguard ufw qrencode
Every device gets its own key pair: the VPS, the site, and each laptop and
phone. Each peer also gets a preshared key, an extra symmetric key on top of
the normal handshake. I make them all here in one folder only root can read,
because it's easier to follow. Generating each key on its own device and
copying only the public half over is better, and the WireGuard apps can do
that.
umask 077
mkdir -p /etc/wireguard/keys && cd /etc/wireguard/keys
for n in vps site laptop phone; do
wg genkey | tee $n.key | wg pubkey > $n.pub
done
for n in site laptop phone; do wg genpsk > $n.psk; done
In the configs below, (vps.key) means "paste the contents of that file".
Here's /etc/wireguard/wg0.conf on the VPS:
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = (vps.key)
# the site behind Starlink
[Peer]
PublicKey = (site.pub)
PresharedKey = (site.psk)
AllowedIPs = 10.99.0.2/32, 192.168.50.0/24
# laptop
[Peer]
PublicKey = (laptop.pub)
PresharedKey = (laptop.psk)
AllowedIPs = 10.99.0.10/32
# phone
[Peer]
PublicKey = (phone.pub)
PresharedKey = (phone.psk)
AllowedIPs = 10.99.0.11/32
AllowedIPs is the routing table. On the VPS it says which addresses live
behind each peer: the site gets its tunnel address plus the whole LAN behind
it, and each client gets only its own address. A packet for 192.168.50.20
goes to the site, and a packet for 10.99.0.10 goes to the laptop.
Turn on forwarding, open the port and start it: echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard.conf sysctl --system ufw allow OpenSSH ufw allow 51820/udp ufw route allow in on wg0 out on wg0 ufw enable systemctl enable --now wg-quick@wg0 Allow SSH before you enable ufw, or you lock yourself out. If your provider also has a firewall in its control panel, open UDP 51820 there too. The route rule is the only forwarding the VPS does: from the tunnel back into the tunnel. Nothing from the tunnel goes out to the internet, so the hub can't turn into a free exit for anyone holding a key. Without ufw, and with the FORWARD policy set to drop, the same rule goes in [Interface]: PostUp = iptables -A FORWARD -i wg0 -o wg0 -j ACCEPT PostDown = iptables -D FORWARD -i wg0 -o wg0 -j ACCEPT
[ Step 2: the site behind Starlink ]
This is the end that dials out. On a Linux router, or a Pi with WireGuard installed, /etc/wireguard/wg0.conf: [Interface] Address = 10.99.0.2/24 PrivateKey = (site.key) [Peer] PublicKey = (vps.pub) PresharedKey = (site.psk) Endpoint = 203.0.113.10:51820 AllowedIPs = 10.99.0.0/24 PersistentKeepalive = 25 PersistentKeepalive is the line that makes the whole thing work. CGNAT forgets a UDP mapping after a short time with no traffic. A keepalive every 25 seconds keeps the path from the VPS back to the site open, so the VPS can reach the site whenever a client needs it. 25 seconds is the interval the WireGuard docs suggest for getting through NAT.
There's no ListenPort. Nothing ever connects to this end, so WireGuard can
pick any port it likes. Start it:
sudo systemctl enable --now wg-quick@wg0
sudo wg show
Within a few seconds, wg show should list a handshake with the VPS.
The LAN also needs a way back to 10.99.0.0/24. Which way depends on where
WireGuard runs:
On the router nothing more for routing. LAN devices already
send everything to the router, and the router knows
10.99.0.0/24 is down the tunnel. Allow wg0 to the
LAN in its firewall.
On a Pi the LAN's devices don't know where 10.99.0.0/24
is, and Starlink's router can't add a static route.
So the Pi masquerades: tunnel traffic reaches the
LAN from the Pi's own address.
For the Pi, turn on forwarding:
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
and add NAT to its wg0.conf [Interface]. The Pi's only other network is the
LAN, so I don't name an interface:
PostUp = iptables -t nat -A POSTROUTING -s 10.99.0.0/24 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.99.0.0/24 -j MASQUERADE
If the Pi runs its own firewall, let it forward from wg0 to the LAN.
[ On OPNsense ]
If your firewall runs OPNsense, here's the same site config in its web UI,
under VPN, WireGuard:
1. Peers: add the VPS. Its public key, the preshared key, Allowed IPs
10.99.0.0/24, Endpoint address 203.0.113.10, Endpoint port 51820,
Keepalive interval 25.
2. Instances: add one. Let it generate a key pair, leave Listen port
empty, set Tunnel address 10.99.0.2/24, and pick the VPS peer. Its
public key goes in the VPS's site [Peer], in place of site.pub.
3. Turn WireGuard on and apply.
4. Interfaces, Assignments: assign the new wg device and enable it. Leave
the IP settings empty; the instance already has them.
5. Firewall, Rules, on that new interface: pass from 10.99.0.0/24 to
whatever on the LAN should be reachable. Start narrow.
6. VPN, WireGuard, Status: the VPS peer should show a recent handshake.
The instance adds a route for each peer's Allowed IPs, so the LAN reaches
10.99.0.0/24 with no extra setup. If your OPNsense sits behind Starlink's
router instead of taking over in bypass mode, nothing here changes. It only
dials out, and double NAT doesn't care.
[ Step 3: laptops and phones ]
The laptop's config, laptop.conf: [Interface] Address = 10.99.0.10/32 PrivateKey = (laptop.key) [Peer] PublicKey = (vps.pub) PresharedKey = (laptop.psk) Endpoint = 203.0.113.10:51820 AllowedIPs = 10.99.0.0/24, 192.168.50.0/24 That's a split tunnel. Only the tunnel and the site's LAN go through WireGuard; everything else uses whatever network you're on.
The phone gets the same file with phone.key, phone.psk and 10.99.0.11. To load it, show it as a QR code on the VPS and scan it with the WireGuard app: qrencode -t ansiutf8 < phone.conf Then delete phone.conf and phone.key from the VPS. The phone has its copy. I leave DNS out of the client config. If you point it at a resolver on the site, every lookup the phone makes depends on the Starlink end being up, and the phone loses DNS whenever the site does. I use IP addresses for the handful of things I reach over the tunnel.
[ Step 4: test it ]
Test from a network that isn't the site's: a phone on cellular with Wi-Fi
off is easiest. From inside the site's own LAN, everything works whether the
tunnel does or not, and that tells you nothing.
On the VPS wg show each peer's latest handshake
should be under 2 minutes old
On a client ping 10.99.0.1 the VPS
ping 10.99.0.2 the site, through the hub
ping 192.168.50.1 the site's LAN
If the first ping works and the second doesn't, the problem is between the
VPS and the site. When it doesn't work goes through the usual causes.
[ The IPv6 route ]
IPv6 has no NAT, so in theory you skip the VPS and point clients straight
at the site. Starlink hands an IPv6 prefix (usually a /56, though some
people report smaller ones) to a router that asks for one. In practice there
are catches:
- Without bypass mode, the Starlink router still hands IPv6 to a router
behind it, so outgoing IPv6 works either way. Incoming doesn't: it
drops unsolicited IPv6 before it reaches your router, and it has no
settings to open ports. Bypass mode takes it out of the path, so your
own router's firewall decides.
- The prefix can change. Starlink says moving the dish or a software
update can change your addresses, so you need dynamic DNS keeping an
AAAA record current.
- The client's network has to have IPv6 too. The big US cell carriers
do; plenty of hotel, office and cafe Wi-Fi doesn't.
If you want it anyway, the site's router becomes an ordinary WireGuard
server: it gets a ListenPort and a [Peer] for each client, and its firewall
allows that UDP port to the router's own IPv6 address on the WAN side.
Clients point at the AAAA name. I keep the VPS as the path that always
works.
[ Lock it down ]
- The VPS sees your traffic. Each hop is encrypted, but the hub
decrypts and re-encrypts in the middle. Treat it as part of your
network: SSH keys only, updates on, nothing else listening. Anything
sensitive should still use SSH or HTTPS inside the tunnel.
- Port scans see nothing. WireGuard doesn't answer packets that
don't carry a valid key, so UDP 51820 looks closed from outside.
- One key per device. A lost phone is one [Peer] block to delete
on the VPS, then systemctl restart wg-quick@wg0.
- The site decides what's reachable. The VPS forwards anything
between peers, so the site's firewall is what keeps a client to the
one host it needs. Start with only that host.
- Clients can reach each other through the hub. If you don't
want that, replace the VPS's broad route rule with one per
destination:
ufw route allow in on wg0 out on wg0 to 192.168.50.0/24
ufw route allow in on wg0 out on wg0 to 10.99.0.2
ufw route delete allow in on wg0 out on wg0
ufw keeps state, so replies coming back to a client still get
through.
[ When it doesn't work ]
No handshake wrong public key or endpoint, UDP 51820 blocked
by the VPS provider's own firewall, or the
preshared key differs between the two ends.
Handshake, no LAN the VPS's site peer is missing the LAN in
AllowedIPs, ip_forward is off, the ufw route
rule is missing, the site's firewall drops wg0
to the LAN, or the Pi isn't masquerading.
Ping works, pages hang MTU. Add MTU = 1380 to [Interface] on the
client and the site, then test with a packet
that can't be split (Linux):
ping -M do -s 1352 192.168.50.1
Dies after idle the site has no PersistentKeepalive, so CGNAT
dropped the mapping.
Down after an outage it shouldn't stay down: the site's next
keepalive brings the tunnel back within about
half a minute of Starlink returning. If it
doesn't come back after a power cut, check
that wg-quick@wg0 is enabled at boot.
VPS got a new IP every config points at the old one. WireGuard
looks up a hostname endpoint only when it
starts, so a DNS name doesn't fix this by
itself. Use your provider's static or reserved
IP.
[ FAQ ]
Can you port forward on Starlink? Not on residential or Roam plans. They sit behind carrier-grade NAT, and Starlink's router has no port forwarding. A public IPv4 address is only offered on Local and Global Priority plans. Does this work with a Starlink Mini? Yes. Nothing here depends on the dish. The site side is whatever router or Pi sits behind it, and since it only dials out, it works on the Mini's built-in router or with the Mini in bypass mode. How big does the VPS need to be? The smallest plan any provider sells is plenty for a few devices. If you'll move big files, check the monthly transfer allowance: everything you pull from the site passes through the VPS. Will a changing Starlink IP break the tunnel? No. The site connects out to the VPS, and WireGuard follows a peer to its new address with the next packet. Only the VPS needs an address that doesn't change. Can the VPS see my traffic? Yes. Each hop is encrypted, but the hub decrypts and re-encrypts traffic in the middle. Treat it as part of your network, and keep SSH or HTTPS on anything sensitive. Why not just use Tailscale? You can, and it's easier. Tailscale and the others punch through NAT for you and relay when they can't. With your own VPS, plain WireGuard means no account and no coordination server that isn't yours. Headscale is the self-hosted middle ground. It's also a lot more software. Tailscale published 17 security bulletins between January and October 2026. Several were in Tailscale SSH, where a peer could get root access its ACLs didn't allow (TS-2026-006, TS-2026-009) or reach root-only sockets (TS-2026-014). Serve could reach the tailscaled local API socket (TS-2026-016), and OAuth access tokens ended up in audit logs (TS-2026-003). None touched the WireGuard tunnels themselves, all were fixed, and every one is public on Tailscale's security bulletins page, which counts in its favor. If you use it, keep every client updated and leave Tailscale SSH and Serve off unless you need them. Plain WireGuard has none of those extras to go wrong. Can my phone send all its internet through the VPS too? Yes. Set the phone's AllowedIPs to 0.0.0.0/0, then let the VPS NAT tunnel traffic out: a MASQUERADE rule for 10.99.0.0/24 on its public interface, and a ufw route rule from wg0 to that interface. The site's LAN stays reachable as before. Does the hub add latency? Some. Every packet goes through the VPS, so put it near where your Starlink traffic leaves Starlink's network, not near your house. A traceroute shows where that is, and it can be far from where the address geolocates.
[ See Also ]
Off-Grid Communications # my Starlink Mini on a DeWalt battery
Tunneling with SSH & Proxychains # the other way into a network
Defense in Depth # hardening the VPS you put in the middle
WireGuard quick start # the official docs
Starlink: public IP # which plans can get one
Guides (parent) # the rest of the write-ups
