Skip to content

Instantly share code, notes, and snippets.

@krzys-h
Last active February 8, 2025 18:22
Show Gist options
  • Save krzys-h/2d6d58ba54971d24fa17d37d547e33fc to your computer and use it in GitHub Desktop.
Save krzys-h/2d6d58ba54971d24fa17d37d547e33fc to your computer and use it in GitHub Desktop.
Public IP for a home lab using a VPS as a proxy
  1. Buy a second IP on your VPS ([PUBLIC_IP])
  2. Do NOT set it up on any local interface. Instead, set up a Wireguard server which will let you use the IP on the other end of the tunnel, and set up IP forwarding and proxy ARP such that the VPS will present it to the provider network as if it was local. Example:
    cat > /etc/wireguard/wg0.conf <<EOF
    [Interface]
    ListenPort = ...
    PrivateKey = ...
    
    [Peer]
    AllowedIPs = [PUBLIC_IP]/32
    PublicKey = ...
    EOF
    echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
    echo "net.ipv4.conf.all.proxy_arp = 1" >> /etc/sysctl.conf  # Important! Makes the public IP from the other side of the tunnel visible to the VPS provider network through ARP
    sysctl -p
    systemctl enable wg-quick@wg0
    systemctl start wg-quick@wg0
    # also make sure iptables rules permit forwarding
  3. Set up a wireguard client on the router. Provide 0.0.0.0/0 as the allowed IPs from the server (the whole internet can talk to you through the tunnel)
    /interface wireguard add name=wireguard1
    /interface wireguard peers add interface=wireguard1 endpoint-address=... endpoint-port=... allowed-address=0.0.0.0/0 public-key="yxfbQfQoXzJd6N4YsgYQUbHiUfMUngW6g2OuwfB8OWM=" persistent-keepalive=1m
    
  4. Make sure that connections from this tunnel on the router are treated as WAN, add an IP address:
    /interface list member add interface=wireguard1 list=WAN
    /ip address add address=[PUBLIC IP]/32 interface=wireguard1
    
  5. Set up policy based routing, to ensure all packets from [PUBLIC IP] to the internet go through the tunnel:
    /routing table add name=public fib
    /routing rule add src-address=[PUBLIC IP] action=lookup table=main min-prefix=1
    /routing rule add src-address=[PUBLIC IP] action=lookup table=public
    /ip route add add dst-address=0.0.0.0/0 gateway=%wireguard1 routing-table=public
    
    Note: This setup does a lookup in the main table for anything except the default gateway first, and only then goes to the tunnel. This makes ping [PUBLIC IP] from the LAN work.
  6. TODO: What we did so far lets you connect to the router, but figuring out proper routing to make port forwarded things work turned out to be quite hard. This is the config I had so far, it worked, but something was going wrong and the port forwarded server responded unreliably:
    # Track connections coming from the tunnel, and make sure the responses go back into the tunnel
    /ip firewall mangle
    add action=mark-connection chain=prerouting in-interface=wireguard1 new-connection-mark=public passthrough=yes
    add action=mark-routing chain=prerouting connection-mark=public in-interface=!wireguard1 new-routing-mark=public passthrough=yes
    add action=mark-routing chain=output connection-mark=public new-routing-mark=public passthrough=yes
    # Standard port forwarding rules
    /ip firewall nat
    add action=dst-nat chain=dstnat dst-address=[PUBLIC IP] dst-port=80 protocol=tcp to-addresses=10.10.10.7 to-ports=80
    add action=dst-nat chain=dstnat dst-address=[PUBLIC IP] dst-port=443 protocol=tcp to-addresses=10.10.10.7 to-ports=443
    # Hairpin NAT rules to make http://[PUBLIC_IP]/ work from LAN
    /ip firewall nat
    add action=masquerade chain=srcnat dst-address=10.10.10.7 dst-port=80 protocol=tcp src-address=10.10.10.0/24
    add action=masquerade chain=srcnat dst-address=10.10.10.7 dst-port=443 protocol=tcp src-address=10.10.10.0/24
    

Since the setup with public IP on the router was being unreliable with port forwards for whatever reason, I opted to put the public IP on one of the VMs directly instead.

These are the alternate version of the instruction above to achieve this:

  1. Buy a second IP on your VPS ([PUBLIC_IP])

  2. Do NOT set it up on any local interface. Instead, set up a Wireguard server which will let you use the IP on the other end of the tunnel, and set up IP forwarding and proxy ARP such that the VPS will present it to the provider network as if it was local. Example:

    cat > /etc/wireguard/wg0.conf <<EOF
    [Interface]
    ListenPort = ...
    PrivateKey = ...
    
    [Peer]
    AllowedIPs = [PUBLIC_IP]/32
    PublicKey = ...
    EOF
    echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
    echo "net.ipv4.conf.all.proxy_arp = 1" >> /etc/sysctl.conf  # Important! Makes the public IP from the other side of the tunnel visible to the VPS provider network through ARP
    sysctl -p
    systemctl enable wg-quick@wg0
    systemctl start wg-quick@wg0
    # also make sure iptables rules permit forwarding
  3. Set up a wireguard client on the VM. Provide 0.0.0.0/0 as the allowed IPs from the server (the whole internet can talk to you through the tunnel)

    cat > /etc/wireguard/wg0.conf <<EOF
    [Interface]
    PrivateKey = ...
    Address = [PUBLIC_IP]/32
    Table = 123
    PostUp = ip rule add from [PUBLIC_IP] lookup 123
    PreDown = ip rule delete from [PUBLIC_IP] lookup 123
    
    [Peer]
    PublicKey = ...
    AllowedIPs = 0.0.0.0/0
    Endpoint = ...
    EOF
    systemctl enable wg-quick@wg0
    systemctl start wg-quick@wg0

    Note how we added policy routing rule like in the router example above, but this time I don't care about supporting DSTNATed connections so looking at the source IP is enough.

    Alternatively, you can try removing the Table, PostUp and PreDown lines to make the VM route all its traffic through the tunnel (the default wg-quick behavior should make it work, I think).

  4. Set up firewall as appropariate for a VM exposed to the internet with a public IP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment