| 1 | --- |
| 2 | title: TCP/UDP Tunneling |
| 3 | description: Tunnel raw TCP and UDP services like game servers through Portal. |
| 4 | --- |
| 5 | |
| 6 | # TCP/UDP Tunneling |
| 7 | |
| 8 | Portal supports dedicated raw TCP and UDP relay modes in addition to the default |
| 9 | HTTPS stream mode. Use these modes for services that need a public port instead |
| 10 | of a browser HTTPS hostname. |
| 11 | |
| 12 | ## Overview |
| 13 | |
| 14 | The default stream path is best for web services: |
| 15 | |
| 16 | ```bash |
| 17 | portal expose 3000 |
| 18 | ``` |
| 19 | |
| 20 | For protocols that do not fit a public HTTPS URL, use one of the port modes: |
| 21 | |
| 22 | - **Dedicated raw TCP**: allocates a public TCP port on the relay and bridges raw |
| 23 | TCP to your local service. |
| 24 | - **UDP relay**: allocates a public UDP port on the relay and carries datagrams |
| 25 | over the tunnel backhaul to your local UDP service. |
| 26 | |
| 27 | Both modes require the relay server to have a port range configured and the |
| 28 | matching transport enabled. |
| 29 | |
| 30 | ## Relay Configuration |
| 31 | |
| 32 | Enable TCP and UDP transports on your relay with these environment variables: |
| 33 | |
| 34 | | Variable | Default | Description | |
| 35 | |----------|---------|-------------| |
| 36 | | `TCP_ENABLED` | `false` | Enable raw TCP port allocation | |
| 37 | | `UDP_ENABLED` | `false` | Enable UDP/QUIC datagram transport | |
| 38 | | `MIN_PORT` | `0` | Inclusive minimum of the port allocation range; `0` disables allocation | |
| 39 | | `MAX_PORT` | `0` | Inclusive maximum of the port allocation range; `0` disables allocation | |
| 40 | |
| 41 | `MIN_PORT` and `MAX_PORT` are shared by TCP and UDP. The protocols are |
| 42 | independent, so the same numeric port can be used by one TCP lease and one UDP |
| 43 | lease at the same time. |
| 44 | |
| 45 | Your firewall or cloud security group must allow inbound traffic on the exposed |
| 46 | range for each protocol you enable. |
| 47 | |
| 48 | ## Dedicated Raw TCP |
| 49 | |
| 50 | Raw TCP mode allocates a public TCP port on the relay. Incoming connections to |
| 51 | that port are bridged to your local TCP target. |
| 52 | |
| 53 | Configure the relay: |
| 54 | |
| 55 | ```bash |
| 56 | TCP_ENABLED=true |
| 57 | MIN_PORT=10000 |
| 58 | MAX_PORT=20000 |
| 59 | ``` |
| 60 | |
| 61 | Expose your local service: |
| 62 | |
| 63 | ```bash |
| 64 | portal expose --tcp --name myapp localhost:8080 |
| 65 | ``` |
| 66 | |
| 67 | The relay returns an assigned TCP address: |
| 68 | |
| 69 | ```text |
| 70 | TCP port: relay.example.com:12345 |
| 71 | ``` |
| 72 | |
| 73 | Clients connect directly to that address: |
| 74 | |
| 75 | ```text |
| 76 | relay.example.com:12345 |
| 77 | ``` |
| 78 | |
| 79 | No Portal client is needed on the connecting side. Any TCP client can connect. |
| 80 | Raw TCP mode does not add TLS, so use application-level encryption if the |
| 81 | protocol needs confidentiality. |
| 82 | |
| 83 | ## UDP Relay |
| 84 | |
| 85 | UDP mode allocates a public UDP port on the relay. Datagrams are carried over a |
| 86 | QUIC backhaul between the relay and the tunnel process, then forwarded to your |
| 87 | local UDP target. |
| 88 | |
| 89 | Configure the relay: |
| 90 | |
| 91 | ```bash |
| 92 | UDP_ENABLED=true |
| 93 | MIN_PORT=10000 |
| 94 | MAX_PORT=20000 |
| 95 | ``` |
| 96 | |
| 97 | Expose your local service: |
| 98 | |
| 99 | ```bash |
| 100 | portal expose --udp --udp-addr localhost:19132 --name myapp localhost:8080 |
| 101 | ``` |
| 102 | |
| 103 | `--udp-addr` is the local UDP address that receives relayed datagrams. When it is |
| 104 | omitted, Portal uses the primary target address. The primary positional target |
| 105 | is still used for stream traffic on the same lease. |
| 106 | |
| 107 | ## Minecraft Server Example |
| 108 | |
| 109 | This exposes a Minecraft Java Edition server running on `localhost:25565`. |
| 110 | |
| 111 | Relay docker-compose snippet: |
| 112 | |
| 113 | ```yaml |
| 114 | services: |
| 115 | relay: |
| 116 | image: ghcr.io/gosuda/portal:latest |
| 117 | environment: |
| 118 | TCP_ENABLED: "true" |
| 119 | MIN_PORT: "10000" |
| 120 | MAX_PORT: "20000" |
| 121 | ports: |
| 122 | - "443:443" |
| 123 | - "4017:4017" |
| 124 | - "10000-20000:10000-20000" |
| 125 | ``` |
| 126 | |
| 127 | Docker's port range syntax creates one mapping per port. Keep ranges reasonably |
| 128 | small because very large ranges can slow Docker startup. |
| 129 | |
| 130 | Expose the server: |
| 131 | |
| 132 | ```bash |
| 133 | portal expose --tcp --name minecraft localhost:25565 |
| 134 | ``` |
| 135 | |
| 136 | Output: |
| 137 | |
| 138 | ```text |
| 139 | TCP port: relay.example.com:13742 |
| 140 | ``` |
| 141 | |
| 142 | In Minecraft, add a server with this address: |
| 143 | |
| 144 | ```text |
| 145 | relay.example.com:13742 |
| 146 | ``` |
| 147 | |
| 148 | The assigned port remains stable while the lease is active and held by the same |
| 149 | identity. |
| 150 | |
| 151 | ## Combining TCP And UDP |
| 152 | |
| 153 | A single lease can carry both a raw TCP port and a UDP relay: |
| 154 | |
| 155 | ```bash |
| 156 | portal expose --tcp --udp --udp-addr localhost:19132 localhost:25565 |
| 157 | ``` |
| 158 | |
| 159 | This registers one lease with: |
| 160 | |
| 161 | - a dedicated TCP port for `localhost:25565` |
| 162 | - a dedicated UDP port forwarding datagrams to `localhost:19132` |
| 163 | |
| 164 | Both ports are drawn from the same `MIN_PORT` to `MAX_PORT` range on the relay. |
| 165 | |
| 166 | ## Limitations |
| 167 | |
| 168 | - **Port range capacity**: the relay can serve at most |
| 169 | `MAX_PORT - MIN_PORT + 1` concurrent TCP leases and the same number of UDP |
| 170 | leases. Plan the range accordingly. |
| 171 | - **No TLS on raw TCP**: raw TCP mode does not add TLS. Use application-level |
| 172 | encryption when the service requires confidentiality. |
| 173 | - **UDP max packet size**: datagrams are capped at 1350 bytes. Larger packets |
| 174 | are dropped. |
| 175 | - **Flow idle timeout**: UDP flows with no traffic for 30 seconds are cleaned up |
| 176 | on the relay. Long-lived protocols should send keepalive packets if they may |
| 177 | be idle. |