Skip to content

RPoVPN

TIP

Reverse Proxy-over-VPN (RPoVPN)

When self‑hosting services such as game servers from your home IP address, it can be risky to open ports on your network and publicly advertise this address. One preferred solution is to configure a reverse proxy over VPN (RPoVPN). This configures a second server likely in the cloud as the gateway, acting as the front‑end IP address for all traffic entering the network.

In a basic text topology, the service setup should look like the following:

Home server <--> VPN Tunnel exchanging TCP traffic <--> Gateway (VPS)

There are a few ways this can be achieved; within this guide we will use Nginx Proxy Manager and Traefik.

Prerequisites

  1. A local server such as a game server. In this example we will be using port 25565.
  2. VPS server, e.g. an Ubuntu VPS in same or different country with static IPv4 address and ability to open ports on their firewall.
  3. A VPN tunnel link - WireGuard is recommended as the most popular protocol with the lowest latency. This can be achieved with a tailnet including both devices: https://tailscale.com/

Configuring RPoVPN

!!! info Both clients are also able to reverse proxy web traffic (HTTP/HTTPS). However, for this example we will leave this out and focus only on forwarding TCP through the Nginx Stream module. They can also be configured to work alongside web traffic.

Below are the guides on how to forward TCP traffic over a pre-configured VPN tunnel.

Nginx Proxy Manager

Docker Compose

yaml
version: "3.8"
services:
  app:
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
     #  These ports are in format <host-port>:<container-port>
     # - 80:80 # Public HTTP Port only - enable for HTTP(s) traffic
     # - 443:443 # Public HTTPS Port - enable for HTTP(s) traffic
      - 81:81 # Admin Web Port
     # All ports below that need to be opened
      - 25565:25565 # Minecraft TCP stream
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
networks:
  proxy:
    external: true

Once configured, run docker compose up -d to start the container.

Once NPM is set up and active, the Streams section can be found under the Proxy header. Here you can create a new Stream, setting the incoming port and forwarding host.

Set the Host to the IPv4 address of the home device at the other end of the VPN, alongside the port of the service that the home device is exposing. In this instance 25565 is active on my home network.

Once enabled, you should now be able to access the public address of the gateway (VPS) server and port. All traffic will be routed through this public IP address keeping your home network address safe while also preventing the need to open the ports on your router's firewall.

Traefik

Docker Compose

yaml
version: "3"
services:

  traefik:
    container_name: traefik
    image: traefik:latest
    ports:
    #  - 80:80 - Disabled as we are not forwarding web traffic from this host
    #  - 443:443
  - 25565:25565 # open Minecraft port
    volumes:
      - ./files:/etc/traefik/
    restart: unless-stopped

networks:
  proxy:
    external: true

Once Traefik is configured, we will need to manually create two files.

First create a traefik.yml file located in the same folder as the Compose YAML.

yaml
touch traefik.yml

Secondly, create a rules.yml (this can be anything you like, as long as it is referenced in the traefik.yml file).

yaml
touch rules.yml

Edit the following files to be configured as shown below:

traefik.yml

yaml
# files/traefik.yml

entryPoints:
  minecraft:
    address: ":25565"
# If you wanted another forward, below would be the format
#  minecraft2:
#    address: ":25566"

serversTransport:
  insecureSkipVerify: true

providers:
  file:
  filename: /etc/traefik/config.yml # rules.yml file reference
    watch: true

config.yml

yaml
# files/config.yml
tcp:
  routers:
    minecraft_router:
      entryPoints:
        - minecraft
      rule: "HostSNI(`*`)"
      service: minecraft_service

# If you wanted another forward, below would be the format
#    minecraft2_router:
#      entryPoints:
#        - minecraft2
#      rule: "HostSNI(`*`)"
#      service: minecraft2_service


  services:
    minecraft_service:
      loadBalancer:
        servers:
          - address: "100.64.0.1:25565"

# If you wanted another forward, below would be the format
#    minecraft2_service:
#      loadBalancer:
#        servers:
#          - address: "100.64.0.1:25566"

A nest of technical knowledge.